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

6

我们正在使用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;
        }
    }
}

现在我想添加第二个构造函数。
    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#端安全释放它?


使用Windows API的GlobalAllocLocalAlloc函数和Heap函数:https://msdn.microsoft.com/en-us/library/windows/desktop/aa366711%28v=vs.85%29.aspx。此外,我不建议让两种不同的语言尝试弄清楚彼此如何分配或释放内存。让每种语言负责自己的内存,唯一可能的例外是使用我之前提到的Windows API堆分配函数。 - PaulMcKenzie
2个回答

5
根据文档,AllocHGlobal是Marshal类中的两种内存分配方法之一(另一个是Marshal.AllocCoTaskMem)。此方法公开了Kernel32.dll中的Win32 LocalAlloc函数。当AllocHGlobal调用LocalAlloc时,它传递了LMEM_FIXED标志,这会导致分配的内存固定在位置上。此外,分配的内存未被填充为零。
因此,您可以从非托管代码调用LocalAlloc来分配内存,并从托管代码中使用Marshal.FreeHGlobal来释放它。同样,可以在非托管代码中使用LocalFree来释放使用Marshal.AllocHGlobal分配的内存。
正如文档所暗示的那样,您也可以使用CoTaskMemAlloc/CoTaskMemFree和Marshal.AllocCoTaskMem/FreeCoTaskMem来执行相同的操作。
尽管如此,使用这种方式设置自己可能会失败。最好保持分配和释放内存的模块相同。混合使用很可能会导致对谁负责释放内存的极大困惑。

我理解你的观点,但是对我来说,让交互结构体的Dispose始终负责清理工作似乎更加简洁,而不是有一些结构体使用C#清理,另一些则使用C++清理。 - Wilbert
@Wilbert,那为什么不让C#代码知道哪一侧分配了内存,然后通过PInvoke调用C# free或C++ free呢? - GreatAndPowerfulOz
LocalFree 的文档说明,使用 GlobalAlloc 分配的内存不安全。那么,在托管代码中安全释放使用 Marshal.AllocHGlobal 分配的内存的函数可能是非托管的 GlobalFree - ceztko
1
@ceztko 你可能会这样认为,但是AllocHGlobal的命名是误导性的。正如你可以从我在答案中复制的文档中清楚地看到的那样,AllocHGlobal调用了LocalFree - David Heffernan
你肯定是指LocalAlloc,我也在参考源码中验证过了。谢谢,我没有仔细读你的信息。 - ceztko
嗯,是的,我打字太快了! - David Heffernan

5

传统上,Microsoft CRT使用HeapCreate()创建自己的堆来服务于C或C++程序中的malloc/new调用,但这种方式通常会导致问题。在C#中无法释放此类内存,因为您没有堆句柄。

然而,从VS2012(msvcr120.dll及以上版本)开始,情况发生了变化。CRT现在使用默认进程堆(由GetProcessHeap()返回)。Marshal.Alloc/FreeHGlobal()也使用该堆。因此,只要本地代码不使用调试分配器(crtdbg.h),您就有机会进行操作。但请小心放弃调试选项。

pinvoke marshaller未被更改,也无法更改。如果它必须释放内存,例如作为函数返回值返回的数组或字符串,则会调用CoTaskMemFree()。从您的问题中无法确定哪种情况适用。如果存在疑问并且您在本地代码中有选择,则使用CoTaskMemAlloc()和Marshal.FreeCoTaskMem()相配合是一个不错的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接