通过C#调用Win32 api失败!

时间:2011-01-03 11:38:22

标签: c# c++ winapi pinvoke

我有一个像这样的api导出的C ++函数:

#define WIN322_API __declspec(dllexport)
WIN322_API char* Test(LPSTR str);
WIN322_API char* Test(LPSTR str)
{
 return "hello";
}

该函数由.DEF文件正确导出为API,因为我可以在Dependency Walker工具中看到它。 现在我有一个C#测试程序:

[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的调用约定和参数签名匹配目标非托管签名。“

我尝试了很多其他数据类型和编组,但什么也没得到! 请帮助我!

6 个答案:

答案 0 :(得分:5)

它是由调用约定不匹配引起的,[DllImport]的默认值是Stdcall,但C编译器的默认值是Cdecl。在声明中使用CallingConvention属性。

但这不是唯一的问题,这段代码会在Vista和Win7上崩溃。从C函数返回一个字符串非常麻烦,存在内存管理问题。目前尚不清楚谁负责释放字符串缓冲区。你现在正在返回一个文字,但这很快就会停止有用。下一站是使用malloc()作为返回字符串,其意图是调用者调用free()。这不起作用,pinvoke marshaller无法调用它,因为它不知道C代码正在使用什么堆。

它将调用Marshal.FreeCoTaskMem()。这是错的,字符串不是由CoTaskMemAlloc()分配的。这在XP及更早版本中没有被注意到,除了这很难诊断内存泄漏。在Vista和Win7上使用kaboom,他们有更严格的内存管理器。

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

extern "C" __declspec(dllexport) 
void __stdcall Test(const char* input, char* output, int outLen);

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

[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 参数,以确保不会溢出缓冲区。这会破坏垃圾收集堆,使应用程序崩溃,导致致命执行引擎错误。

答案 1 :(得分:2)

将宏定义更改为

#define WIN322_API __declspec(dllexport) __stdcall

作为替代方案,请在导入时使用CallingConvention.Cdecl

例如,阅读here以获取有关调用约定的更多信息。

答案 2 :(得分:1)

确保您拥有正确的通话约定。您的DLL可能使用Cdecl,而C#默认使用StdCall。最好总是明确定义调用约定。

特别是当使用存在于ANSI和宽字符版本(具有A或W前缀的那些)的Windows函数时,显式指定CharSet以便使用正确的版本。

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

所以改为这个,例如:

[DllImport("c:\\win322.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string Test([MarshalAs(UnmanagedType.LPStr)] String str);

答案 3 :(得分:0)

尝试返回LPSTR,而不是char *。您可能还需要指定stdcall调用约定,这在.NET中是默认的,但我不确定您的非托管项目。

答案 4 :(得分:0)

使用Unmanagedtype.LPTStr作为输入。注意额外的T

[MarshalAs(UnmanagedType.LPStr)] String str  // your current code
[MarshalAs(UnmanagedType.LPTStr)] String str // try this code

答案 5 :(得分:0)

将.Net中的StringBuilder作为参数传递,而不是返回一个字符串(在这种情况下,它将像一个out参数)