问题描述
我无法理解为什么.NET Core编组不能使用一种编组方法,而不能使用另一种编组方法。在下面的示例中,Marshal.PtrToStringAnsi
可以成功地从本机dll封送字符串,但是不能使用具有[return: MarshalAs(UnmanagedType.LPStr)]
属性的相同调用进行处理吗?还是从IntPtr进行的编组变得很幸运,并且还可以根据操作系统的内存管理来访问无效的内存?
C库代码是(编译为C而不是C ++):
static const char str[] = "Hello World";
__declspec(dllexport) const char* getstr() {
return str;
}
C#测试程序:
using System;
using System.Runtime.InteropServices;
namespace CLibraryPinvoke
{
class Program
{
public const string LIB_NAME = "CLibrary";
[DllImport(LIB_NAME,EntryPoint = "getstr")]
public static extern IntPtr getstrgood();
[DllImport(LIB_NAME,EntryPoint ="getstr")]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string getstrbad();
static void Main(string[] args)
{
Console.WriteLine($"getstr={Marshal.PtrToStringAnsi(getstrgood())}.");
// Crash.
Console.WriteLine($"getstr={getstrbad()}.");
}
}
}
执行结果:
getstr=Hello World.
CLibraryPinvoke\bin\Debug\netcoreapp3.1\CLibraryPinvoke.exe (process 31848) exited with code -1073740940.
解决方法
PtrToStringAnsi
从返回值指向的缓冲区中复制字符串。
UnmanagedType.LPStr
从返回值指向的缓冲区中复制字符串,然后释放它with CoTaskMemFree
。用static const char[]
释放CoTaskMemFree
会使程序崩溃。
这是因为返回值的语义是保存结果的内存是在被调用方分配的。
与documented一样,如果内存是在被叫方分配的,但是不应该与CoTaskMemFree
一起释放,则必须将其编组为IntPtr
并使用适当的方法自己释放它。
由static const char[]
备份的内存的合适方法是将其保留。
这要求您的函数的用户了解您函数的实现细节,这不是一件好事。当然,您可以将函数记录为永远不应该释放的返回内存,但是它可能不是一个非常有用的函数,并且,如果将来要更改其行为,您将不得不返回其中一个。可能有static const char[]
个。
通过每次分配内存并让.NET运行时管理其余部分,或者使函数接受char*
参数将响应复制到其中,可以避免所有这些情况。