从具有指针的托管C#中释放非托管内存

Release unmanaged memory from managed C# with pointer of it

本文关键字:释放 内存 指针      更新时间:2023-10-16

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

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

下面的代码运行良好,我可以正确地获得字节数组、字节计数和返回值,但当我尝试使用指针(IntPtr)释放内存时,我会得到异常:

Windows在TestCppDllCall.exe中触发了断点。

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

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

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

让事情变得清楚:

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

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

  3. 我需要的所有功能都是Native DLL函数通过引用接受两个参数(Byte array和int,In c#[IntPtr of Byte array和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;
}

C#代码:

using System;
using System.Text;
using System.Runtime.InteropServices;
namespace TestCppDllCall
{
    class Program
    {
        const string KERNEL32 = @"kernel32.dll";
        const string _dllLocation = @"D:CppDllBinCppDll.dll";
        const string funEntryPoint = @"writeToBuffer";
        [DllImport(KERNEL32, SetLastError = true)]
        public static extern IntPtr GetProcessHeap();
        [DllImport(KERNEL32, SetLastError = true)]
        public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);
        [DllImport(_dllLocation, EntryPoint = funEntryPoint, CallingConvention = CallingConvention.Cdecl)]
        public static extern int writeToBuffer(out IntPtr myBuffer, out int mySize);
        static void Main(string[] args)
        {
            IntPtr byteArrayPointer = IntPtr.Zero;
            int arraySize;
            try
            {
                int retValue = writeToBuffer(out byteArrayPointer, out arraySize);
                if (retValue == 1 && byteArrayPointer != IntPtr.Zero)
                {
                    byte[] byteArrayBuffer = new byte[arraySize];
                    Marshal.Copy(byteArrayPointer, byteArrayBuffer, 0, byteArrayBuffer.Length);
                    string strMyBuffer = Encoding.Default.GetString(byteArrayBuffer);
                    Console.WriteLine("Return Value : {0}rnArray Size : {1}rnReturn String : {2}",
                        retValue, arraySize, strMyBuffer);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error calling DLL rn {0}", ex.Message);
            }
            finally
            {
                if (byteArrayPointer != IntPtr.Zero)
                    HeapFree(GetProcessHeap(), 0, byteArrayPointer);
            }
            Console.ReadKey();
        }
    }
}

当我调试这个代码时,我在行中设置了断点(返回1),缓冲区的值是:

myBuffer = 0x031b4fc0 "ABCDEFGHIJKLMNOPQRSTUVWXYZ‎‎‎‎««««««««î‏"

当函数调用返回时,我在C#代码中得到了相同的值,值为:

52121536

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

如果有什么不清楚的地方或者有错别字,请告诉我,我不是以英语为母语的人。

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

长话短说:在DLL实现中有不同的内存分配方式。释放内存的方式必须与分配内存的方式相匹配。例如,用new[](带方括号)分配的内存需要用delete[](与deletefree相反)释放。C#并没有为您提供一种机制来实现它;您需要将指针发送回C++。

extern "C" CPPDLL_API void freeBuffer(unsigned char* myBuffer) {
    delete[] myBuffer;
}

如果在本机代码中分配自己的内存,请使用CoTaskMemAlloc,并且可以使用Marshal.FreeCoTaskMem释放托管代码中的指针。CoTaskMemAlloc被描述为"在基于COM的应用程序中共享内存的唯一方法"(请参阅http://msdn.microsoft.com/en-us/library/windows/desktop/aa366533(v=vs.85).aspx)

如果需要将CoTaskMemAlloc分配的内存与本机C++对象一起使用,则可以使用placement new初始化内存,就像使用了new运算符一样。例如:

void * p = CoTaskMemAlloc(sizeof(MyType));
MyType * pMyType = new (p) MyType;

这并不分配内存,new只是在预先分配的内存上调用构造函数。

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

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

  HeapFree(GetProcessHeap(), 0, byteArrayPointer);

不,那不行。堆句柄错误,CRT使用HeapCreate()创建自己的堆。它被隐藏在CRT数据中,你无法访问它。从技术上讲,你可以从GetProcessHeaps()中找到句柄,只是你不知道它是哪一个。

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

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