我正在尝试与实现多个函数的Dll接口,其中一个函数接受一个空终止字符串和一个int,并返回一个以空字符结尾的字符串。我试图与这样的方法接口:
[DllImport(dll_loc)]
[return : MarshalAs(UnmanagedType.LPStr)]
public static extern StringBuilder GetErrorMessage([MarshalAs(UnmanagedType.LPStr)]
StringBuilder message,
int error_code);
然后我尝试调用这样的方法:
StringBuilder message = new StringBuilder(1000);
StringBuilder out2 = new StringBuilder(1000);
out2 = GetErrorMessage(message, res0);
然而,当我尝试这个时,会抛出AccessViolationException
告诉我我正在尝试访问受保护的内存。
我成功地宣布了一种不同的方法:
[DllImport(dll_loc)]
public static extern int GetVersion([MarshalAs(UnmanagedType.LPStr)]
StringBuilder version);
并以相同的方式调用它,但此方法不适用于当前函数调用。
我也试过返回一个IntPtr,因为文档在技术上说该方法返回一个指向字符串缓冲区的第一个字符的指针,但无济于事。
有没有人对这里可能出现的问题有任何见解?导致dll尝试访问内存的这两种方法之间可能有什么不同呢。或者,您如何建议调试此问题?
答案 0 :(得分:10)
返回字符串的C函数是内存管理问题。 C字符串需要一个数组,并且在使用该字符串后需要释放该数组的内存。这使得这些函数很难在C程序中使用,几乎不可能与pinvoke一起使用。它也是一个经典的C bug,返回指向堆栈中字符串的指针。
pinvoke marshaller将尝试释放返回的字符串,以避免内存泄漏。它将使用CoTaskMemFree()。这通常不会很好,C代码实际上使用CoTaskMemAlloc来为数组分配内存是很少见的。在XP上,这将导致无声内存泄漏。 Vista和Win7有更严格的堆管理器,如果连接了非托管调试器,它们将调用调试中断。接下来用AccessViolation轰炸程序。
您可以通过将返回类型声明为IntPtr并自行封送字符串来避免自动封送行为。通常使用Marshal.PtrToStringAnsi()。但是你仍然面临释放记忆的任务。你不能,你没有CRT堆的句柄,你不能调用free()。
返回字符串的C函数应该使用传递字符串缓冲区的参数声明。并且一个论证说缓冲区有多大。像这样:
int GetErrorMessage(int errorCode, char* buffer, size_t bufferSize);
现在很简单,调用者可以为缓冲区分配内存并释放它。 C函数只是将字符串复制到缓冲区中。返回值可用于表示复制了多少个字符,或者表示需要更大的缓冲区。不要吝啬bufferSize
参数,缓冲区溢出是致命的并且调用可怕的FatalExecutionEngineError异常,这是一个与AV不同的异常,因为直到很久以后才会检测到GC堆损坏。您在C#端使用StringBuilder,适当地初始化为非零容量。 bufferSize的值。
答案 1 :(得分:1)
将所有StringBuilder
替换为System.IntPtr
,仅分配 message
var System.Runtime.InteropServices.Marshal.AllocHGlobal
然后将message
或out2
转换为带PtrToStringAuto
然后致电System.Runtime.InteropServices.Marshal.FreeHGlobal
在处理char*
或wchar_t*
时,始终以这种方式为我工作。