我有一个像这样导出为 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 的调用约定和参数签名匹配目标非托管签名。”
我尝试了许多其他数据类型和编码,但一无所获! 请帮助我!
最佳答案
这是由于调用约定不匹配造成的,[DllImport] 默认是 Stdcall 但 C 编译器默认是 Cdecl。在声明中使用 CallingConvention 属性。
但这不是唯一的问题,这段代码在 Vista 和 Win7 上会崩溃。从 C 函数返回字符串很麻烦,存在内存管理问题。不清楚谁负责释放字符串缓冲区。您现在正在返回文字,但很快就会停止使用。下一站是使用 malloc() 作为返回字符串,目的是让调用者调用 free()。这是行不通的,pinvoke 编码器无法调用它,因为它不知道 C 代码正在使用什么堆。
它将调用 Marshal.FreeCoTaskMem()。这是错误的,字符串不是由 CoTaskMemAlloc() 分配的。这在 XP 和更早版本上没有引起注意,除了很难诊断由此导致的内存泄漏。并且在 Vista 和 Win7 上大放异彩,它们有更严格的内存管理器。
您需要像这样重写 C 函数:
extern "C" __declspec(dllexport)
void __stdcall Test(const char* input, char* output, int outLen);
现在调用者通过 output 参数提供缓冲区,不再需要猜测谁拥有内存。在 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 参数时要小心,以确保不会溢出缓冲区。这会破坏垃圾收集堆,导致应用程序因致命执行引擎错误而崩溃。
关于c# - 通过 C# 调用 Win32 api 失败!,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4584111/