我目前正在探索 DLL 导出函数和 C# 中的 P/invoke。 我创建了非常简单的 .dll:
测试.h
#ifndef TEST_DLL_H
#define TEST_DLL_H
extern "C" __declspec(dllexport) const char * __cdecl hello ();
extern "C" __declspec(dllexport) const char * __cdecl test ();
#endif // TEST_DLL_H
测试.cpp
#include <stdlib.h>
#include "test.h"
#include <string.h>
const char* hello()
{
char *novi = (char *)malloc(51);
strcpy(novi, "Test.");
return novi;
}
const char * test()
{
return "Test.";
}
我已经编译了它并在 C# 项目中使用,如下所示:
[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr hello();
[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern string test();
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(test());
IntPtr a = hello();
MessageBox.Show(Marshal.PtrToStringAnsi(a));
}
但是它不起作用。 test()
被成功调用,我得到了正确的字符串。但是 hello()
只是挂起程序。如果我从 hello()
定义中删除 malloc 行并返回常量,则一切正常,所以我猜想我现在已经意识到 malloc 存在问题。
此外,我在某处看到当返回类型为 char* 时不应使用字符串。如果这是真的,我们为什么要使用 IntPtr?
最佳答案
跨 DLL 边界返回字符串的函数很难从 C 或 C++ 中可靠地调用,从 C# 中调用也不会得到任何改善。问题在于调用者将如何释放字符串缓冲区。这需要为 hello() 完成,但不需要为 test() 完成。很难猜测的事情。 hello() 函数需要使用 free() 函数,并使用与调用 malloc() 完全相同的分配器。仅当 DLL 和调用者共享相同的 CRT 实现时,这才有效。这种可能性很小。
pinvoke 编码器还必须释放字符串缓冲区。并且使用唯一合理的选择 CoTaskMemFree() 来实现这一点。它使用 COM 使用的默认分配器。这没有一个好的结局,您的 C 代码没有使用 CoTaskMemAlloc()。可能的结果取决于操作系统。在 Vista 及更高版本上,您的程序将因 AccessViolation 而终止,这些 Windows 版本使用严格的堆分配器,旨在使行为不当的程序崩溃。在 XP 上,您会遇到内存泄漏和堆损坏之间的问题,听起来就像您选择了第二个选项。
将返回值声明为IntPtr将会有一个好的结局。好吧,你的程序不会崩溃,你仍然有一个无法修复的内存泄漏。没有办法可靠地调用 free()。或者在 C 代码中使用 CoTaskMemAlloc() ,以便 pinvoke 编码器的释放调用能够工作。
但实际上,不要像这样编写 C 代码。始终使用调用者分配的内存,因此永远不会猜测谁拥有该内存。这需要类似于这样的函数签名:
extern "C" __declspec(dllexport)
int hello(char* buffer, int bufferSize);
关于C# 应用程序从 dll 调用方法时会阻塞,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11386011/