我正在通过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结构是否完整,是否有有效数据,并且与等效的本机结构大小相同。
在我看来,这应该是应该发生的事情:
我的ptr引用的本机结构被编组并复制到“数据”托管结构
将管理结构复制回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;
}
答案 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);