使用字符串函数时,应用程序在64位DllImport上崩溃

时间:2012-12-18 11:48:32

标签: c# string dllimport

我有一个在.NET 4.0下运行的Windows窗体应用程序。此应用程序导入可用于:

的DLL
  • 32位
  • 64位

这是我的代码段:

[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的任何进一步信息,因为它是第三方产品。

2 个答案:

答案 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);