Marshal.StructureToPtr无法解释的内存泄漏

时间:2012-12-03 14:37:49

标签: c# c++ memory interop marshalling

我正在通过C ++ / CLR包装器开发一个涉及本机C ++到C#interop的应用程序。

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

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. 将管理结构复制回ptr。

  3. 引用的同一内存

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

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

    编辑:根据要求,以下是“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;
    }
    

1 个答案:

答案 0 :(得分:4)

你有指向结构中字符串的指针。那些指针是在你的非托管代码中分配的(使用new wchar_t[<a number>]),对吧?在将这些指针编组到托管代码时,封送程序会创建托管字符串并使用非托管字符数组的内容填充它们。当将它们编组回非托管代码时,封送程序会复制整个结构内容,包括字符指针,为它们分配新值(使用CoTaskMemAlloc()为每个字符串分配内存)。这就是Marshal.StructureToPtr的第三个参数。如果设置为true,则封送器会尝试释放字符指针指向的内存(使用CoTaskMemFree())。如果您使用new运算符为字符指针分配了内存,则无法将该参数设置为true,并且通过编组返回到非托管,您将丢失指向已分配内存的指针(marshaller将使用新值覆盖它们)。而且由于你没有释放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);