c# - 通过 C# 调用 Win32 api 失败!

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

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

最佳答案

这是由于调用约定不匹配造成的,[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/

相关文章:

C# - 如何检测网站是否被访问过

c++ - 为什么以及何时删除复制构造函数和 operator=

windows - 使用 windows com 端口发送中断字节

c++ - 您将如何使用 C++/Qt5 构建无窗口应用程序?

c++ - 使用 MinGW 在 Eclipse 中 boost 单元测试不产生输出

c++ - 是否可以全局挂接窗口的创建,以便我可以控制窗口在屏幕上的放置位置?

c++ - 模拟按住一个键

c# - 如何解决 Funq IoC 中的循环依赖?

c# - 安全网络服务器 asp.net

c# - 在 Visual Studio 中将应用程序拆分为多个解决方案之前,我应该考虑哪些因素?