用指针从托管C#释放非托管内存

用指针从托管C#释放非托管内存,c#,c++,memory-leaks,buffer,pinvoke,C#,C++,Memory Leaks,Buffer,Pinvoke,简而言之,问题是: 如何在托管代码中释放作为ItrPtr从本机DLL返回的内存 详情: 假设我们有一个简单的函数,它有两个参数作为输出,第一个是指向字节数组的引用指针,第二个是引用Int。 该函数将根据一些规则分配字节数,并返回内存指针、字节大小和返回值(1表示成功,0表示失败) 下面的代码工作正常,我可以正确获取字节数组、字节计数和返回值,但当我尝试使用指针(IntPtr)释放内存时,我遇到异常: Windows已在TestCppDllCall.exe中触发断点 这可能是由于堆损坏,这表明Te

简而言之,问题是: 如何在托管代码中释放作为ItrPtr从本机DLL返回的内存

详情: 假设我们有一个简单的函数,它有两个参数作为输出,第一个是指向字节数组的引用指针,第二个是引用Int。 该函数将根据一些规则分配字节数,并返回内存指针、字节大小和返回值(1表示成功,0表示失败)

下面的代码工作正常,我可以正确获取字节数组、字节计数和返回值,但当我尝试使用指针(IntPtr)释放内存时,我遇到异常:

Windows已在TestCppDllCall.exe中触发断点

这可能是由于堆损坏,这表明TestCppDllCall.exe或其加载的任何DLL中存在错误

这也可能是由于用户在TestCppDllCall.exe具有焦点时按下F12

输出窗口可能有更多诊断信息

要说清楚:

  • 下一个C#代码与其他具有相同签名的DLL函数一起正确工作,释放内存不会出现任何问题

  • 如果您需要更改分配内存方法或添加任何其他代码,则接受(C)代码中的任何修改

  • 我需要的所有功能是本机DLL函数通过引用接受两个参数(字节数组和int,在c#[IntPtr of Byte array and int]),根据一些规则用一些值填充它们并返回函数结果(成功或失败)


  • CppDll.h

    #ifdef CPPDLL_EXPORTS
    #define CPPDLL_API __declspec(dllexport)
    #else
    #define CPPDLL_API __declspec(dllimport)
    #endif
    
    extern "C" CPPDLL_API int writeToBuffer(unsigned char *&myBuffer, int& mySize);
    
    CppDll.cpp

    #include "stdafx.h"
    #include "CppDll.h"
    
    extern "C" CPPDLL_API int writeToBuffer(unsigned char*& myBuffer, int& mySize)
    {
        mySize = 26;
    
        unsigned char* pTemp = new unsigned char[26];
        for(int i = 0; i < 26; i++)
        {
            pTemp[i] = 65 + i;
        }
        myBuffer = pTemp; 
        return 1;
    }
    
    调试此代码时,我在行中设置了断点(返回1),缓冲区的值为:

    myBuffer = 0x031b4fc0 "ABCDEFGHIJKLMNOPQRSTUVWXYZ‎‎‎‎««««««««î‏"
    
    当函数调用返回时,我在C代码中得到了相同的值,值为:

    52121536
    
    结果我得到了正确的内存指针,我能够得到字节数组值,如何用C#中的这个指针释放这些内存块


    如果有任何不清楚的地方或输入错误,请告诉我,我不是英语母语。

    简短回答:您应该在DLL中添加一个单独的方法,为您释放内存

    详细回答:在DLL实现中有不同的内存分配方式。释放内存的方式必须与分配内存的方式相匹配。例如,使用
    new[]
    (带方括号)分配的内存需要通过
    delete[]
    释放(与
    delete
    free
    相反)。C#并没有为你提供一种机制;您需要将指针发送回C++。
    extern "C" CPPDLL_API void freeBuffer(unsigned char* myBuffer) {
        delete[] myBuffer;
    }
    
    不,那不行。堆句柄错误,CRT使用HeapCreate()创建自己的堆。它隐藏在CRT数据中,你无法找到它。从技术上讲,您可以从GetProcessHeaps()中找到句柄,但您不知道它是哪一个

    指针也可能是错误的,CRT可能从HeapAlloc()返回的指针中添加了一些额外的信息来存储调试数据


    您需要导出一个调用delete[]的函数来释放缓冲区。或者编写一个C++/CLI包装,以便在包装中使用delete[]。具有额外的要求,即C++代码和包装器使用CRT DLL(MD需要的)完全相同的版本。这几乎总是要求你可以重新编译C++代码。

    如果你在本地代码中分配你自己的内存,使用,你可以用.
    CoTaskMemAlloc
    被描述为“在基于COM的应用程序中共享内存的唯一方法”(请参阅)

    如果需要使用带有原生C++对象的 COTASKMeMeloLC/分配的内存,可以使用Survivnew初始化内存,就像使用<代码> new < /Cuth>运算符。例如:

    void * p = CoTaskMemAlloc(sizeof(MyType));
    MyType * pMyType = new (p) MyType;
    
    这不使用
    new
    分配内存,只调用预先分配内存上的构造函数

    调用
    Marshal.FreeCoTaskMem
    不会调用该类型的析构函数(如果只需要释放内存,则不需要析构函数);如果您需要通过调用析构函数来释放内存,那么您必须提供一个本机方法来实现这一点,并P/Invoke它。无论如何都不支持将本机类实例传递给托管代码


    如果需要使用其他API分配内存,则需要通过p/Invoke在托管代码中公开内存,以便在托管代码中释放内存。

    感谢您的快速响应。我希望有一种方法可以直接释放C#中的非托管内存。我同意dasblinkenlight:最干净的方法可以说是在.dll中添加一个“free”方法。但是Peter Ritchie也是正确的:C当然可以(通过互操作)调用相应的“free”(例如
    FreeCoTaskMem()
    )来分配.dll(例如
    CoTaskMemAlloc()
    )。如果C是“C++”“新建”,那么从C中不能做的一件事就是“删除”。“malloc/free”:好的。CoTaskMemAlloc/FreeCoTaskMem:也可以。“New/free”:绝对不是。@paulsm4请解释如何在C#malloc/free中调用?@user1142979:Peter Ritchie解释了如何使用CoTaskMemAlloc()和Marshal.FreeCoTaskMem(),在下面的回复中(回到2012年!),您可以在这里阅读更多内容:。注意注释“必须使用CoTaskMemAlloc而不是新运算符,因为托管端的代码将调用Marshal.FreeCoTaskMem来释放此内存”。@paulsm4我理解,但当dll使用malloc来释放内存时,则不应使用Marshal.FreeCoTaskMem来释放内存。我的印象是,您已经找到了在这种情况下调用相应“free”的方法。+1-CoTaskMemAlloc是在Windows中匹配托管/非托管(或任何其他跨语言)分配的官方方法。
      HeapFree(GetProcessHeap(), 0, byteArrayPointer);
    
    void * p = CoTaskMemAlloc(sizeof(MyType));
    MyType * pMyType = new (p) MyType;