DllImport返回null终止字符串AccessViolationException
我试图与实现几个函数的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尝试访问内存的这两种方法之间可能有什么不同。 或者,您如何建议调试此问题?
返回字符串的C函数是内存管理问题。 AC字符串需要一个数组,并且在使用该字符串后需要释放该数组的内存。 这使得这些函数很难在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
参数,缓冲区溢出是致命的,并调用可怕的FatalExecutionEngineErrorexception,这是一个与AV不同的exception,因为直到很久以后才会检测到GC堆损坏。 您在C#端使用StringBuilder,适当地初始化为非零容量。 bufferSize的值。
用System.IntPtr
替换所有StringBuilder
, 仅使用System.Runtime.InteropServices.Marshal.AllocHGlobal
分配message
var。
然后使用PtrToStringAuto
将message
或out2
转换为字符串
然后调用System.Runtime.InteropServices.Marshal.FreeHGlobal
在处理char*
或wchar_t*
时,总是以这种方式为我工作。