我试图与实现几个函数的Dll接口,其中一个函数接受一个空终止的字符串和一个int,并返回一个以null结尾的字符串.我试图与这样的方法接口:
[DllImport(dll_loc)]
[return : MarshalAs(UnmanagedType.LPStr)]
public static extern StringBuilder GetErrorMessage([MarshalAs(UnmanagedType.LPStr)]
StringBuilder message,
int error_code);
Run Code Online (Sandbox Code Playgroud)
然后我尝试调用这样的方法:
StringBuilder message = new StringBuilder(1000);
StringBuilder out2 = new StringBuilder(1000);
out2 = GetErrorMessage(message, res0);
Run Code Online (Sandbox Code Playgroud)
但是,当我尝试这个时,AccessViolationException会抛出一个告诉我我正在尝试访问受保护的内存.
我成功地声明了一个不同的方法:
[DllImport(dll_loc)]
public static extern int GetVersion([MarshalAs(UnmanagedType.LPStr)]
StringBuilder version);
Run Code Online (Sandbox Code Playgroud)
并以相同的方式调用它,但此方法不适用于当前函数调用.
我也尝试返回一个IntPtr,因为文档在技术上说该方法返回一个指向字符串缓冲区的第一个字符的指针,但无济于事.
有没有人对这里可能出现的问题有任何见解?导致dll尝试访问内存的这两种方法之间可能有什么不同呢.或者,您如何建议调试此问题?
Han*_*ant 10
返回字符串的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);
Run Code Online (Sandbox Code Playgroud)
现在很简单,调用者可以为缓冲区分配内存并释放它.C函数只是将字符串复制到缓冲区中.返回值可用于表示复制了多少个字符,或者表示需要更大的缓冲区.不要吝啬这个bufferSize论点,缓冲区溢出是致命的,并调用可怕的FatalExecutionEngineError异常,这是一个像AV一样可以解除的异常,因为直到很久之后才会检测到GC堆损坏.您在C#端使用StringBuilder,适当地初始化为非零容量.bufferSize的值.