【问题标题】:Unexplained memory leak with Marshal.StructureToPtrMarshal.StructureToPtr 无法解释的内存泄漏
【发布时间】:2012-11-21 01:11:30
【问题描述】:

我正在开发一个应用程序,该应用程序通过 C++/CLR 包装器涉及本机 C++ 到 C# 互操作。

我在执行以下操作时遇到问题,导致内存泄漏:

MyObject data = (MyObject)Marshal.PtrToStructure(ptr, typeof(MyObject));
Marshal.StructureToPtr(data, ptr, false);

(注意:我意识到此时我实际上并没有对“数据”做任何事情,所以这是多余的。)

内存使用量持续上升,直到应用程序因系统内存不足而崩溃。当我删除此代码时,这不会发生。它不是垃圾收集器,因为 a)它应该在系统内存不足时收集,b)我尝试使用 GC.Collect() 强制它。

事实上,我已将泄漏范围缩小到 StructureToPtr 命令。

我无法将第三个参数设置为“true”,因为内存是由本机 C++ 分配的,而 C# 认为这个“受保护”的内存无法释放。

我检查了填充的 Data 结构是否完好无损,具有有效数据,并且与等效的原生结构大小相同。

在我看来,这就是应该发生的事情:

  1. 我的 ptr 引用的本机结构被编组并复制到“数据”托管结构中

  2. manage 结构被复制回 ptr 引用的同一内存中。

我看不出这是如何导致内存泄漏的,因为它的结构大小完全相同,被复制回相同的内存空间。但显然确实如此,删除代码会堵塞泄漏。

这里有一些我没有正确理解的机制吗?

编辑:根据要求,这里是“MyObject”的声明。

C#:

[StructLayout(LayoutKind.Sequential)]
public struct MyObject
{
    [MarshalAs(UnmanagedType.I1)]
    public bool ParamOne;
    [MarshalAs(UnmanagedType.I1)]
    public bool ParamTwo;
    [MarshalAs(UnmanagedType.I1)]
    public bool ParamThree;
    [MarshalAs(UnmanagedType.I1)]
    public bool ParamFour;
    [MarshalAs(UnmanagedType.I1)]
    public bool ParamFive;
    [MarshalAs(UnmanagedType.I1)]
    public bool ParamSix;
    [MarshalAs(UnmanagedType.R4)]
    public float ParamSeven;
    [MarshalAs(UnmanagedType.R4)]
    public float ParamEight;
    [MarshalAs(UnmanagedType.R4)]
    public float ParamNine;
    public Vector2f ParamTen;
    public Vector2f ParamEleven;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string ParamTwelve;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string ParamThirteen;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string ParamFourteen;
    public IntPtr ParamFifteen;
    public IntPtr ParamSixteen;
}

C++:

struct MyObject
{
    public:
    bool ParamOne;
    bool ParamTwo;      
    bool ParamThree;
    bool ParamFour;
    bool ParamFive;
    bool ParamSix;
    float ParamSeven;
    float ParamEight;
    float ParamNine;
    Vector2f ParamTen;
    Vector2f ParamEleven;
    wchar_t * ParamTwelve;
    wchar_t * ParamThirteen;
    wchar_t * ParamFourteen;
    void * ParamFifteen; 
    void * ParamSixteen;
};

Vector2f的定义如下:

[StructLayout(LayoutKind.Sequential)]
public struct Vector2f
{
    [MarshalAs(UnmanagedType.R4)]
    float x;
    [MarshalAs(UnmanagedType.R4)]
    float y;
}

【问题讨论】:

  • 可以发一下MyObject的定义吗?
  • "MyObject" 听起来不像是一个结构。您确实需要记录下来才能得到一个好的答案。
  • 您的内存泄漏情况如何?您是如何发现泄漏的?
  • 内存使用量持续上升,直到应用程序因系统内存不足而崩溃。当我删除此代码时,这不会发生。它不是垃圾收集器,因为 a)它应该在系统内存不足时收集,b)我尝试使用 GC.Collect() 强制它。
  • 结构中的 wchar_t* 和 void* 指针是不可避免的泄漏。它们需要被销毁,除非本机代码使用 CoTaskMemAlloc(),否则不能在托管代码中这样做。当然不是。这种代码很难在纯原生代码中可靠地进行互操作,在 C# 中尝试它并没有变得更好。

标签: c# c++ memory interop marshalling


【解决方案1】:

你的结构中有指向字符串的指针。这些指针是在您的非托管代码中分配的(使用new wchar_t[<a number>]),对吗?在封送这些指向托管代码的指针时,封送拆收器创建托管字符串并用非托管字符数组的内容填充它们。当将它们封送回非托管代码时,封送器复制整个结构内容,包括字符指针,为它们分配新值(使用CoTaskMemAlloc() 为每个字符串分配内存)。这就是Marshal.StructureToPtr 的第三个参数的用途。如果设置为 true,则封送拆收器尝试释放字符指针指向的内存(使用 CoTaskMemFree())。如果您已使用 new 运算符为字符指针分配内存,则不能将该参数设置为 true,并且通过封送回非托管,您将丢失指向已分配内存的指针(封送器用新值覆盖它们)。而且由于您没有释放 marshaler 分配的内存,因此最终会导致内存泄漏。

处理这种情况的最佳选择是:

将字符串编组为指针并使用Marshal.PtrToStringUni() 将它们转换为字符串:

[StructLayout(LayoutKind.Sequential)]
public struct MyObject
{
    [MarshalAs(UnmanagedType.I1)]
    public bool ParamOne;
    [MarshalAs(UnmanagedType.I1)]
    public bool ParamTwo;
    [MarshalAs(UnmanagedType.I1)]
    public bool ParamThree;
    [MarshalAs(UnmanagedType.I1)]
    public bool ParamFour;
    [MarshalAs(UnmanagedType.I1)]
    public bool ParamFive;
    [MarshalAs(UnmanagedType.I1)]
    public bool ParamSix;
    [MarshalAs(UnmanagedType.R4)]
    public float ParamSeven;
    [MarshalAs(UnmanagedType.R4)]
    public float ParamEight;
    [MarshalAs(UnmanagedType.R4)]
    public float ParamNine;
    public Vector2f ParamTen;
    public Vector2f ParamEleven;
    public IntPtr ParamTwelve;    // <-- These are your strings
    public IntPtr ParamThirteen;  // <--
    public IntPtr ParamFourteen;  // <--
    public IntPtr ParamFifteen;
    public IntPtr ParamSixteen;
}

和编组:

    MyObject data = (MyObject)Marshal.PtrToStructure(ptr, typeof(MyObject));
    var str1 = Marshal.PtrToStringUni(data.ParamTwelve);
    var str2 = Marshal.PtrToStringUni(data.ParamThirteen);
    var str3 = Marshal.PtrToStringUni(data.ParamFourteen);
    Marshal.StructureToPtr(data, ptr, false);

【讨论】:

  • 你太棒了。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-04-15
  • 2019-02-27
  • 1970-01-01
  • 2011-12-08
  • 1970-01-01
  • 1970-01-01
  • 2018-06-04
相关资源
最近更新 更多