P调用编组const char *失败,并带有UnmanagedType.LPStr

问题描述

我无法理解为什么.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*参数将响应复制到其中,可以避免所有这些情况。