我目前正在探索DLL导出函数和C#中的P / invoke。 我创建了非常简单的.dll:
Test.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
Test.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?
答案 0 :(得分:3)
跨越DLL边界返回字符串的函数很难从C或C ++中可靠地调用,当你从C#中执行它时,它没有任何好转。问题是调用者将如何释放字符串缓冲区。这需要为hello()而不是test()完成。一些很难猜到的东西。 hello()函数需要使用free()函数,使用用于调用malloc()的 exact 相同的分配器。这只能在DLL和调用者共享相同的CRT实现时才能工作。可能性很小。
pinvoke marshaller也会释放字符串缓冲区。并且只有合理的选择,CoTaskMemFree()。它使用COM使用的默认分配器。这没有达到目的,你的C代码没有使用CoTaskMemAlloc()。这可能的结果取决于操作系统。在Vista及以上版本中,您的程序将因AccessViolation而死亡,这些Windows版本使用严格的堆分配器来解决行为不端的程序。在XP上,你会在内存泄漏和堆损坏之间得到一些东西,听起来就像你碰到了第二个选项。
将返回值声明为IntPtr将会很好地结束。好吧,你的程序不会崩溃,你仍然有一个无法插入的内存泄漏。没有办法可靠地调用free()。或者在C代码中使用CoTaskMemAlloc(),以便pinvoke marshaller的释放调用可以正常工作。
但实际上,只是不要写这样的C代码。始终使用调用者分配的内存,因此永远不会猜测谁拥有内存。这需要类似于此的函数签名:
extern "C" __declspec(dllexport)
int hello(char* buffer, int bufferSize);