C# Interop - 释放在非托管代码中分配的内存
Posted
技术标签:
【中文标题】C# Interop - 释放在非托管代码中分配的内存【英文标题】:C# Interop - Releasing memory allocated in unmanaged code 【发布时间】:2016-03-02 12:25:47 【问题描述】:我正在调用下面的 VC++ 方法
__declspec(dllexport) unsigned char* Get_Version_String()
来自C#如下:
internal static class NativeMethods
[DllImport("my.dll"),
CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true,
CallingConvention = CallingConvention.Cdecl)]
internal static extern string Get_Version_String();
以上代码位于一个面向 .NET 3.5 的库中。当我从 3.5 程序集调用它时,它工作正常;但是,当从 4.5 程序集调用它时,会导致
0xC0000374:堆已损坏
阅读this question后,我改变了我的方法调用如下:
[DllImport("my.dll",
EntryPoint = "Get_Version_String",
CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true,
CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr Get_Version_String_PInvoke();
internal static string Get_Version_String()
IntPtr ptr = Get_Version_String_PInvoke();
string versionString = Marshal.PtrToStringAnsi(ptr);
return versionString;
这按预期工作,但 Hans Passant 的回答带有警告:
您找到的解决方法是正确的,编组器不会尝试为
IntPtr
释放内存。请注意,只有在 C 代码返回不需要发布的const char*
时,这才会真正结束。如果不是这种情况,就会出现永久性内存泄漏。
由于 C++ 方法不返回 const
,我假设我的特定函数的解决方法会导致内存泄漏。
我无法更改原始方法,所以我找到了this other question,它讨论了如何从管理代码中释放内存。但是,调用Marshal.FreeHGlobal(ptr)
或Marshal.FreeCoTaskMem(ptr)
也会抛出0xC0000374: A heap has been corrupted.
任何人都可以 a) 确认这样的方法确实会出现内存泄漏,并且 b) 如果是,建议如何从托管代码中的指针中释放内存?
C++ 方法体简化如下:
unsigned char versionString[50];
__declspec(dllexport) unsigned char* Get_Version_String()
strcpy((char *) versionString, "Key1:[xx],Key2:[xx],Key3:[xx],Key4:[xx]");
// string manipulation
return versionString;
在此先感谢,如果这是微不足道的,请见谅;我既不是 C++ 也不是互操作专家。
【问题讨论】:
如果可能的话,我可能会尝试引发内存泄漏。如果该非托管代码可以多次执行而不会产生不必要的影响,您可以将其打包到无限循环中,偶尔调用GC.Collect()
并观察内存消耗。
好的,我可以测试一下。我应该如何“偶尔”致电GC.Collect()
?每一分钟?更长?
GC.Collect() 不一定需要。如果您正在运行 32 位应用程序,内存将在大约 2Gb 处耗尽。但这是查看内存是否更早释放的好方法,而不仅仅是等待 OutOfMemoryException。调用它的频率取决于分配内存的速率。如果这很慢,那么每分钟就足够了。但是,如果它加注非常快,您可以每隔一秒左右跟注它。另请注意,每次调用 GC.Collect() 不一定会导致垃圾收集。并且不要在生产代码中调用 GC.Collect() - 在大多数情况下应该没有必要。
【参考方案1】:
任何人都可以确认这种方法确实会遭受内存泄漏
只有您可以这样做,缺少的 const 关键字并不能保证本机代码实际上不会返回文字。顺便说一句,C 代码中普遍存在错误。编写一个小测试程序,调用该函数一亿次。如果您没有看到任务管理器的内存使用量激增,那么您没有问题。
如果是,建议如何从托管代码中的指针中释放内存?
你不能,它必须是调用free()
的本机代码本身。以便它使用正确的堆,即由该代码使用的 C 运行时库创建的堆。底层的 winapi 调用是 HeapCreate(),你没有堆句柄。从技术上讲,它可以通过 GetProcessHeaps() 发现,但您只是不知道哪一个是“正确的”。从 VS2012 开始,CRT 使用 GetProcessHeap() 而不是 HeapCreate(),现在 Marshal.FreeHGlobal() 可以工作了。但是您知道此代码没有,您必须向作者或供应商询问更新。只要你这样做,就向他询问这个函数的更有用的风格,它应该接受一个 char* 作为参数。
更有建设性的方法是从容应对内存泄漏。只需调用函数一次,程序运行时版本号不会改变。所以将它存储在 static 变量中。丢失约 80 字节的地址空间并不是您能注意到的问题,当您的程序终止时,操作系统会自动清理。
【讨论】:
感谢您将其纳入视野并提供实用的解决方案。以上是关于C# Interop - 释放在非托管代码中分配的内存的主要内容,如果未能解决你的问题,请参考以下文章