2

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

4

2 回答 2

5

函数签名相当麻烦。您的代码是否会崩溃取决于您运行的操作系统。XP 上没有任何反应,Vista 及更高版本会引发 AccessViolation 异常。

问题在于返回字符串的 C 函数通常需要通过返回指向存储字符串的缓冲区的指针来实现。该缓冲区需要从堆中分配,调用者需要在使用字符串后释放该缓冲区。pinvoke marshaller 实现了该契约,它在将返回的字符串指针转换为 System.String 后调用 CoTaskMemFree()。

结果总是很糟糕,C 函数几乎从不使用 CoTaskMemAlloc() 来分配缓冲区。XP 堆管理器非常宽容,它只是忽略了错误的指针。不是更高版本的 Windows,它们故意生成异常。顺便说一句,“Vista 糟透了”标签的强大推动力,程序员花了一段时间来修复他们的指针错误。如果您启用了非托管调试,那么您将从堆管理器获得诊断,警告指针无效。非常好的功能,但是当您调试托管代码时,非托管调试总是被禁用。

您可以通过将返回值声明为 IntPtr 来阻止 pinvoke 编组器尝试释放字符串。然后,您必须自己使用 Marshal.PtrToStringAnsi() 或其朋友之一来编组字符串。

您仍然有必须释放字符串缓冲区的问题。没有办法可靠地做到这一点,你不能调用正确的释放器。您唯一的希望是 C 函数实际上返回一个指向字符串文字的指针,该指针存储在数据段中并且不应该释放。这可能适用于返回错误字符串的函数,只要它没有实现任何像本地化这样的花哨的东西。const char* 返回类型令人鼓舞。

您需要对此进行测试,以确保不释放字符串缓冲区不会导致内存泄漏。很容易做到,在循环中调用这个函数十亿次。如果您没有将 IntPtr.Zero 作为返回值,并且程序不会因内存不足异常而崩溃,那么您很好。对于 64 位 pinvoke,您需要密切关注测试程序的内存消耗。

于 2012-12-18T14:01:59.673 回答
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);
于 2012-12-18T13:43:47.517 回答