2016-03-02 62 views
4

我是从C#调用以下VC++方法C#互操作释放存储器在非托管代码

__declspec(dllexport) unsigned char* Get_Version_String() 

如下分配:

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.

谁能
一)证实这种方法确实会从内存泄漏受苦,
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++也不是Interop专家。

+0

我可能会尽可能激发内存泄漏。如果这个非托管代码可以被多次执行而没有不必要的影响,那么可以偶尔使用'GC.Collect()'调用将其打包到无限循环中,并观察内存消耗。 – Edin

+0

好的,我可以测试一下。我应该如何“偶尔”调用GC.Collect()?每一分钟?更长? – user5877732

+0

GC.Collect()不一定需要。如果您正在运行32位应用程序,则内存将大约为2Gb。但是,这是一种很好的方式来查看内存是否被更早释放,而不仅仅是等待OutOfMemoryException。多久调用一次取决于记忆分配的速度。如果这是缓慢的,那么每分钟就足够了。但是,如果速度非常快,您可以每隔一秒左右调用一次。另请注意,每次调用GC.Collect()都不一定会导致垃圾回收。并且不要在生产性代码中调用GC.Collect() - 在大多数情况下不需要。 – Edin

回答

5

任何人都可以证实这种方法确实会从记忆遭受泄漏

只有你能做到这一点,缺少常量关键字是不能保证本机代码实际上不返回一个文字。 C代码中的普遍错误btw。写一个测试程序,调用函数一亿次。如果你没有看到使用任务管理器的内存使用爆炸,那么你没有问题。

如果是这样,建议如何从托管代码中的指针释放内存?

你只是不能,它必须是调用free()的本机代码本身。以便它使用正确的堆,即由该代码使用的C运行时库创建的堆。底层的winapi调用是HeapCreate(),你没有堆句柄。从技术上讲,GetProcessHeaps()是可以发现的,但你不知道哪一个是“正确”的。从VS2012开始,CRT使用GetProcessHeap()而不是HeapCreate(),现在Marshal.FreeHGlobal()可以工作。但是你知道这段代码没有,你必须要求作者或供应商进行更新。只要你这样做,问他一个更有用的味道这个函数,它应该采取char *作为参数。


一个更具建设性的方法是只采取跨步内存泄漏。只需调用函数一次,在程序运行时版本号不会改变。因此将其存储在静态变量中。丢失大约80字节的地址空间并不是你可能注意到的问题,当你的程序终止时,操作系统会自动清理。

+0

感谢您将它纳入视角并提供实用的解决方案。 – user5877732