【问题标题】:PInvoke: Allocate memory in C++ and free it in C#PInvoke:在 C++ 中分配内存并在 C# 中释放它
【发布时间】:2016-02-02 14:07:17
【问题描述】:

我们正在使用 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# 端安全地释放它?

【问题讨论】:

  • 使用 Windows API GlobalAllocLocalAlloc 函数和堆函数:msdn.microsoft.com/en-us/library/windows/desktop/…。此外,我不建议让两种不同的语言尝试弄清楚每种语言如何分配或取消分配内存。让每种语言负责自己的内存,唯一可能的例外是使用我之前提到的 Windows API 堆分配函数。

标签: c# c++ interop pinvoke marshalling


【解决方案1】:

来自documentation

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

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

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

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

话虽如此,你这样做是为了让自己跌倒。将分配和释放保持在同一个模块中要干净得多。以这种方式混合匹配很可能会导致严重混淆谁负责释放内存。

【讨论】:

  • 我明白你的意思,但对我来说,总是让互操作结构的 Dispose 负责清理而不是让一些结构使用 C# 清理,其他结构使用 C++ 清理似乎更干净。
  • @Wilbert,那为什么不让 C# 代码知道哪一侧分配了内存,然后调用 C# free 或 C++ free(当然是通过 pinvoke)?
  • LocalFree 的文档说,释放GlobalAlloc 分配的内存是不安全的。那么,在托管代码中调用以安全释放使用Marshal.AllocHGlobal 分配的内存的函数可能是umanaged 中的GlobalFree
  • @ceztko 您可能会这么认为,但 AllocHGlobal 的命名会产生误导。从我在答案中复制的文档中可以清楚地看到,AllocHGlobal 调用LocalFree
  • 你的意思肯定是LocalAlloc,我也在参考源中验证过。谢谢,我没有仔细阅读您的留言。
【解决方案2】:

这在传统上总是以失败告终,Microsoft CRT 使用 HeapCreate() 创建了自己的堆,以服务于 C 或 C++ 程序中的 malloc/new 调用。无法在 C# 中释放此类内存,您没有堆句柄。

但是,从 VS2012 中包含的 CRT(msvcr120.dll 及更高版本)开始,情况发生了变化。它现在使用默认进程堆,即 GetProcessHeap() 返回的堆。也是 Marshal.Alloc/FreeHGlobal() 使用的那个。因此,您现在可以尝试一下,前提是本机代码不使用调试分配器 (crtdbg.h)。小心丢弃该调试选项。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-19
    • 1970-01-01
    • 2016-02-19
    • 1970-01-01
    相关资源
    最近更新 更多