C#Interop - 释放在非托管代码中分配的内存

use*_*732 5 c# interop

我正在调用以下VC++方法

__declspec(dllexport) unsigned char* Get_Version_String()
Run Code Online (Sandbox Code Playgroud)

来自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();
}
Run Code Online (Sandbox Code Playgroud)

上面的代码位于一个面向.NET 3.5的库中.当我从3.5组装中调用它时,它工作正常; 但是,当从4.5程序集调用它时,它会导致

0xC0000374:堆已损坏

阅读完这个问题之后,我改变了我的方法调用,如下所示:

[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;
}
Run Code Online (Sandbox Code Playgroud)

这可以按预期工作,但Hans Passant的回答是警告:

您找到的解决方法是正确的,marshaller不会尝试释放内存IntPtr.请注意,如果C代码返回const char*不需要释放的代码,这实际上只会达到一个好的结果.如果不是这种情况,则会发生永久性内存泄漏.

由于C++方法没有返回const,我假设我的特定函数的解决方法将导致内存泄漏.

我无法改变原始方法,所以我发现了另一个讨论如何从manged代码中释放内存的问题.然而,无论是打电话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;
}
Run Code Online (Sandbox Code Playgroud)

在此先感谢,如果这是微不足道的话,我很抱歉; 我既不是C++也不是Interop专家.

Han*_*ant 7

任何人都可以确认这样的方法确实会遭受内存泄漏

只有你可以这样做,缺少的const关键字不能保证本机代码实际上不返回文字.C代码中的普遍错误.写一个小的测试程序,调用该函数一亿次.如果您没有看到内存使用情况与任务管理器爆炸,那么您没有问题.

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

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


更具建设性的方法是大步消除内存泄漏.只需调用该函数一次,程序运行时版本号就不会改变.所以将它存储在静态变量中.丢失~80字节的地址空间不是您可能注意到的问题,操作系统会在您的程序终止时自动清理.