PInvoke:在C++中分配内存,并在C#中释放内存

PInvoke: Allocate memory in C++ and free it in C#

本文关键字:内存 释放 并在 分配 C++ PInvoke      更新时间:2023-10-16

我们使用PInvoke在C#和C++之间进行互操作。

我有一个如下所示的互操作结构,在另一侧有一个相同布局的C++结构。

[StructLayout(LayoutKind.Sequential)]
public struct MeshDataStruct : IDisposable
{
    public MeshDataStruct(double[] vertices, int[] triangles , int[] surfaces)
    {
        _vertex_count = vertices.Length / 3;
        _vertices = Marshal.AllocHGlobal(_vertex_count*3*sizeof (double));
        Marshal.Copy(vertices, 0, _vertices, _vertex_count);
    }
    // .. extract data methods to double[] etc.
    private IntPtr _vertices;
    private int _vertex_count;
    public void Dispose()
    {
        if (_vertices != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(_vertices);
            _vertices = IntPtr.Zero;
        }
    }
}

现在我想添加第二个ctor

    public MeshDataStruct(bool filled_in_by_native_codee)
    {
        _vertex_count = 0;
        _vertices = IntPtr.Zero;
    }

然后用C++编写一个允许C++填充数据的方法。这将允许我们对输入和输出数据使用相同的结构。。。

然而,据我所知,AllocHGlobal在C#和C++/Cli中可用,但不是纯C++。

所以我的问题是:如何在C++中分配内存,以便通过调用Marshal.FreeHGlobal(...)在C#端安全地释放内存?

传统上,这总是以糟糕的结果告终,Microsoft CRT使用HeapCreate()创建了自己的堆,为C或C++程序中的malloc/new调用提供服务。无法在C#中释放这样的内存,您没有堆句柄。

然而,从VS2012中包含的CRT(msvcr120.dll及以上版本)开始,情况发生了变化。它现在使用默认的进程堆,即GetProcessHeap()返回的进程堆。也是Marshal.Aloc/FreeHGlobal()使用的。所以,如果本机代码不使用调试分配器(crtdbg.h),您现在可以尝试一下了。小心放弃调试选项。

pinvokemarshaller没有改变,也不能。如果它必须释放内存,比如作为函数返回值返回的数组或字符串,那么它将调用CoTaskMemFree()。从你的问题中还不清楚哪一个适用。如果有疑问,并且您在本机代码中可以选择,那么CoTaskMemAlloc()与C#代码中的Marshal.FreeCoTaskMem()配对不会出错。

来自文档:

AllocHGlobal是Marshal中的两种内存分配方法之一班(Marshal.AllocCoTaskMem是另一个。)此方法公开Kernel32.dll.中的Win32 LocalAlloc函数

当AllocHGlobal调用LocalAlloc时,它传递一个LMEM_FIXED标志导致所分配的存储器被锁定到位。此外,分配的内存不是零填充的。

因此,您可以从非托管代码调用LocalAlloc来分配内存,从托管代码中调用Marshal.FreeHGlobal来取消分配内存。同样,LocalFree也可以在非托管代码中用于取消分配使用Marshal.AllocHGlobal分配的内存。

正如文档所暗示的,您可以对CoTaskMemAlloc/CoTaskMemFreeMarshal.AllocCoTaskMem/FreeCoTaskMem执行同样的操作。

话虽如此,你这样做是在为摔倒做准备。将分配和解除分配保持在相同的模块中要干净得多。以这种方式混合匹配很可能会导致对谁负责释放内存产生很大的困惑。