我有一个在.NET 4.0下运行的Windows窗体应用程序。此应用程序导入可用于:
的DLL这是我的代码段:
[DllImport("my64Bit.dll"), EntryPoint="GetLastErrorText"]
private static extern string GetLastErrorText();
// Do some stuff...
string message = GetLastErrorText();
调用此函数(为x64编译)时,应用程序崩溃。我甚至无法在Visual Studio 2012中看到任何调试消息。与32位DLL(为x86编译)相同的代码工作正常。原型是:
LPCSTR APIENTRY GetLastErrorText()
不幸的是,我没有关于DLL的任何进一步信息,因为它是第三方产品。
答案 0 :(得分:5)
功能签名非常麻烦。代码是否崩溃取决于您运行的操作系统。在XP上没有任何事情发生,Vista及更高版本会引发AccessViolation异常。
问题在于返回字符串的C函数通常需要通过返回指向存储字符串的缓冲区的指针来实现。该缓冲区需要从堆中分配,调用者需要在使用该字符串后释放该缓冲区。 pinvoke marshaller实现了该契约,它在将它转换为System.String后,在返回的字符串指针上调用CoTaskMemFree()。
总是结果不好,C函数几乎从不使用CoTaskMemAlloc()来分配缓冲区。 XP堆管理器非常宽容,它只是忽略了坏指针。不是后来的Windows版本,它们故意生成异常。对于“Vista糟透了”标签btw的强大推动者,程序员花了一段时间来修复他们的指针错误。如果您启用了非托管调试,那么您将从堆管理器获得诊断,该诊断警告指针无效。非常好的功能,但在调试托管代码时,总是禁用非托管调试。
您可以通过将返回值声明为IntPtr来阻止pinvoke marshaller尝试释放字符串。然后你必须自己用Marshal.PtrToStringAnsi()或其中一个朋友来编组字符串。
你仍然有必须释放字符串缓冲区的问题。没有办法可靠地执行此操作,您无法调用正确的解除分配器。你唯一的希望是C函数实际上返回一个指向字符串文字的指针,一个存储在数据段中的指针,应该不被释放。这可能适用于返回错误字符串的函数,前提是它没有实现像本地化这样的任何奇特的东西。 const char *返回类型令人鼓舞。
您需要对此进行测试,以确保不释放字符串缓冲区时没有内存泄漏。容易做到,循环调用此功能十亿次。如果您没有将IntPtr.Zero作为返回值,并且该程序不会因内存不足异常而失败,那么您就是好的。对于64位pinvoke,您需要密切关注测试程序的内存消耗。
答案 1 :(得分:2)
找到它。本机函数返回LPCSTR
,即C#函数不能返回字符串。相反,必须像这样返回IntPtr
:
[DllImport("my64Bit.dll"), EntryPoint="GetLastErrorText"]
private static extern IntPtr GetLastErrorText();
// Do some stuff...
IntPtr ptr = GetLastErrorText();
string s = Marshal.PtrToStringAnsi(ptr);