2011-07-15 62 views
3

我想与一个Dll实现几个函数接口,其中一个接受一个空终止的字符串和一个int,并返回一个空终止的字符串。我试图用这样的方法接口:DllImport返回null终止字符串AccessViolationException

[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

在禁止某些Windows DLL时遇到类似问题。我发现参数中的一些小调整导致了巨大的改进。即使是一些错误的编组/数据类型/等等。造成巨大的分歧。你必须仔细看看你的非托管DLL接口的文档。 –

回答

10

返回字符串的C函数是内存管理问题。 C字符串需要一个数组,并且该字符串消耗后需要释放该数组的内存。这使得这些函数很难在C程序中使用,几乎不可能与pinvoke一起使用。它也是一个经典的C错误,它返回一个指向栈上字符串的指针。

pinvoke编组将试图释放返回的字符串,以避免内存泄漏。它将使用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的值。

+0

不幸的是,我不能访问通过dll定义函数调用I接口的C代码。如果我从托管代码中调用函数时忽略返回值,而只是传递一个字符串缓冲区,那么是否仍然存在内存泄漏问题?或者这会解决问题。如果有内存泄漏,我可以在返回的IntPtr上调用'Marshal.FreeHGlobal'来释放内存吗? – Patrick

+0

再看看声明,这个函数可能会简单地返回它被调用的指针。在这种情况下,你没有问题,声明它在[DllImport]声明中返回void,所以pinvoke编组不再尝试做正确的事情。使用message.ToString()来恢复返回的字符串。如果它实际上返回了一个不同的字符串指针,那么你卡住了,你不能捏住它。 –

1

System.IntPtr替换所有StringBuilder的,分配只有message VAR与System.Runtime.InteropServices.Marshal.AllocHGlobal

然后转换messageout2串与PtrToStringAuto

然后调用System.Runtime.InteropServices.Marshal.FreeHGlobal

对于我这样总是工作当处理char*wchar_t*

+0

我不确定如何做到这一点。我尝试定义为公共静态外部IntPtr GetErrorMessage(IntPtr消息,诠释error_code);然后调用:'IntPtr消息=新的IntPtr(); Marshal.AllocHGlobal(1000); IntPtr out2 = GetErrorMessage(message,res0);',但是抛出了相同的异常。难道我做错了什么? – Patrick

+0

IntPtr message = Marshal.AllocHGlobal(1000); – Djole