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

Unexplained memory leak with Marshal.StructureToPtr

本文关键字:内存 泄漏 无法解释 StructureToPtr Marshal      更新时间:2023-10-16

我正在开发一个应用程序,该应用程序通过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. 管理结构被复制回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;
}

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

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

将字符串封送为指针,并使用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);