C# 调 C++ DLL 托管代码中释放非托管函数分配的内存

  【说明不当之处,望各位大神多多指正】

    在实际情况中,很多平台调用的非托管函数都会用C/C++的方式分配一块内存并指向它的指针返回给调用方。这就需要调用方在使用完获得的指针后将内存释放掉,否则会引起内存泄漏。

要想在托管代码中释放掉由非托管函数分配的内存,最重要的就是:确定托管内存是由哪种方法分配的。只有确定了非托管内存的分配方法,才能在与非托管函数进行互操作时选择正确的方法释放非托管内存。

  非托管代码分配内存的3中方法:

  • 在C语言中:malloc分配内存,free释放内存;
  • 在C++中:new分配内存,delete释放内存;
  • 在COM中:CoTaskMemAlloc分配内存,CoTaskMemFree释放内存。

    【注意事项】

  ① 若非托管内存由前两种方法来分配(malloc和new),那么在托管代码中不能直接对其进行释放。

         原因:托管代码无法确切获悉非托管代码是采取哪种方法来分配内存的。并且,若是用C语言编写的代码,则更无法知道采用的是哪个版本的C运行库。

     解决措施:要想释放掉malloc和new分配的内存,就必须在非托管代码中实现一个能够释放此非托管内存的方法(采用free和delete方法来实现),然后在托管代码中调用该方法

                      对非托管内存进行释放。

  ② 若非托管代码采用COM中分配内存的方法CoTaskMemAlloc进行分配的,那么封送拆收器是能够将其释放掉的。

     原因:封送拆收器在对非托管内存进行处理时,会将COM的内存分配方法CoTaskMemAlloc作为非托管内存的默认分配方法。所以,当封送拆收器将一个非托管内存指针封送成.NET

      数据类型时,封送拆收器会使用非托管数据的一个复制创建一个.NET对象。由于非托管数据已经将封送拆收器获取,因此封送拆收器就会使用相应的COM释放内存的方法CoTaskMemFree

      来释放掉这块已经被封送过的非托管内存。

   释放由malloc方法分配的非托管内存

   平台:VS2015(分别创建一个C++ DLL项目 和 C#控制台项目)

    【错误示例】

  【C++】 

  extern "C" __declspec(dllexport) wchar_t* __cdecl GetStringMalloc()
  {
    int iBufferSize = 128;
    wchar_t* pBuffer = (wchar_t*)malloc(iBufferSize);
    if (NULL != pBuffer)
    {
      wcscpy_s(pBuffer,iBufferSize/sizeof(wchar_t),L"String from MALLOC.");
    }
    return pBuffer;
  }

   【C#】

  public class LoadDLLMethod
  {
    [DllImport(@"D:\ExerciseForTest\CsharpAndCPP\MyDllTest\Debug\MyDllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    public static extern string GetStringMalloc();
  }
  class Program
  {
    static void Main(string[] args)
    {
      string stringFromMalloc = LoadDLLMethod.GetStringMalloc();
    }
  }

   【错误提示】

  

  注:单步调试你会发现,由非托管函数分配的非托管内存(如:pBuffer - 0x00e83210),当完成对非托管函数的平台调用后,改地址的内存没有被释放掉。

  【解决方法】

   方法一:将非托管函数中分配内存的方法修改成CoTaskMemAlloc.

  【C++】 

  extern "C" __declspec(dllexport) wchar_t* __cdecl GetStringMalloc()
  {
    int iBufferSize = 128;
    wchar_t* pBuffer = (wchar_t*)CoTaskMemAlloc(iBufferSize);
    if (NULL != pBuffer)
    {
      wcscpy_s(pBuffer,iBufferSize/sizeof(wchar_t),L"String from MALLOC.");
    }
    return pBuffer;
  }

   【C#】

  public class LoadDLLMethod
  {
    [DllImport(@"D:\ExerciseForTest\CsharpAndCPP\MyDllTest\Debug\MyDllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    public static extern string GetStringMalloc();
  }
  class Program
  {
    static void Main(string[] args)
    {
      string stringFromMalloc = LoadDLLMethod.GetStringMalloc();
    }
  }

   方法二:在非托管代码中添加一个能够释放掉非托管内存的非托管方法。(非托管堆上分配的内存必须从非托管堆上释放掉)

  【C++】  

  extern "C" __declspec(dllexport) wchar_t* __cdecl GetStringMalloc()
  {
    int iBufferSize = 128;
    wchar_t* pBuffer = (wchar_t*)malloc(iBufferSize);
    if (NULL != pBuffer)
    {
      wcscpy_s(pBuffer,iBufferSize/sizeof(wchar_t),L"String from MALLOC.");
    }
    return pBuffer;
  }
  extern "C" __declspec(dllexport) void FreeMallocMemory(void* pBuffer)
  {
    if (NULL != pBuffer)
    {
      free(pBuffer);
      pBuffer = NULL;
    }
  }

   【C#】

  public class LoadDLLMethod
  {
    [DllImport(@"D:\ExerciseForTest\CsharpAndCPP\MyDllTest\Debug\MyDllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    public static extern IntPtr GetStringMalloc();

    [DllImport(@"D:\ExerciseForTest\CsharpAndCPP\MyDllTest\Debug\MyDllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    public static extern void FreeMallocMemory(IntPtr pBuffer);
  }
  class Program
  {
    static void Main(string[] args)
    {
      IntPtr stringPtr = LoadDLLMethod.GetStringMalloc();
      //获得string
      string stringFromMalloc = Marshal.PtrToStringUni(stringPtr);
      //调用非托管函数以释放掉非托管内存
      LoadDLLMethod.FreeMallocMemory(stringPtr);
    }
  }

【new...delete代码同上操作】

 

posted @ 2016-11-12 10:30  Death、MrZ  阅读(2434)  评论(0编辑  收藏  举报