首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >通过C#调用Win32接口失败!

通过C#调用Win32接口失败!
EN

Stack Overflow用户
提问于 2011-01-03 19:38:22
回答 6查看 1.2K关注 0票数 3

我有一个导出为接口的C++函数,如下所示:

代码语言:javascript
运行
复制
#define WIN322_API __declspec(dllexport)
WIN322_API char* Test(LPSTR str);
WIN322_API char* Test(LPSTR str)
{
 return "hello";
}

该函数被.DEF文件正确地导出为API,因为我可以在依赖遍历工具中看到它。现在我有了一个C#测试程序:

代码语言:javascript
运行
复制
[DllImport("c:\\win322.dll")]

public static extern string Test([MarshalAs(UnmanagedType.LPStr)] String str);

private void Form1_Load(object sender, EventArgs e)
        {
string _str = "0221";
Test(_str); // runtime error here!

}

在调用Test()方法时,我得到错误:

“对PInvoke函数'MyClient!MyClient.Form1::Test‘的调用使堆栈不平衡。这可能是因为托管PInvoke签名与非托管目标签名不匹配。请检查PInvoke签名的调用约定和参数是否与目标非托管签名匹配。”

我尝试了许多其他数据类型和编组,但一无所获!请帮帮我!

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2011-01-03 21:07:55

确保您有正确的调用约定。您的DLL可以使用Cdecl,而C#默认使用Cdecl。最好总是显式地定义调用约定。

特别是在使用存在于ANSI和宽字符版本(带有A或W前缀的版本)中的窗口函数时,请显式指定CharSet,以便使用正确的版本。

当函数返回值时,显式封送返回值。否则,编译器将选择默认值,这可能是错误的。我怀疑这也是问题所在。

因此,请更改为以下内容,例如:

代码语言:javascript
运行
复制
[DllImport("c:\\win322.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string Test([MarshalAs(UnmanagedType.LPStr)] String str);
票数 1
EN

Stack Overflow用户

发布于 2011-01-03 21:51:04

这是由于调用约定不匹配造成的,DllImport的默认值是Stdcall,而C编译器的默认值是Cdecl。在声明中使用CallingConvention属性。

但这并不是唯一的问题,这段代码将在Vista和Win7上崩溃。从C函数返回一个字符串是相当麻烦的,有一个内存管理问题。目前还不清楚谁负责释放字符串缓冲区。你现在返回了一个文本,但这很快就不再有用了。下一步是使用malloc()作为返回字符串,目的是让调用者调用free()。这是行不通的,pinvoke编组程序不能调用它,因为它不知道C代码使用的是什么堆。

它将调用Marshal.FreeCoTaskMem()。这是错误的,字符串不是由CoTaskMemAlloc()分配的。这在XP和更早的版本上不会被注意到,除了它导致的非常难以诊断的内存泄漏。在Vista和Win7上失败了,他们有一个更严格的内存管理器。

你需要像这样重写C函数:

代码语言:javascript
运行
复制
extern "C" __declspec(dllexport) 
void __stdcall Test(const char* input, char* output, int outLen);

现在调用者提供了缓冲区,通过输出参数,不再猜测谁拥有内存。在C#声明中使用StringBuilder。

代码语言:javascript
运行
复制
[DllImport("foo.dll")]
private static extern void Test(string input, StringBuilder output, int outLen);
...
    var sb = new StringBuilder(666);
    test("bar", sb, sb.Capacity);
    string result = sb.ToString();

在C代码中使用outLen参数时要小心,这样可以确保不会使缓冲区溢出。这会破坏垃圾收集堆,并使用致命的执行引擎错误使应用程序崩溃。

票数 5
EN

Stack Overflow用户

发布于 2011-01-03 20:30:09

将宏定义更改为

代码语言:javascript
运行
复制
#define WIN322_API __declspec(dllexport) __stdcall

或者,在导入时使用CallingConvention.Cdecl

例如,有关调用约定的更多信息,请阅读here

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4584111

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档