• 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:ExerciseForTestCsharpAndCPPMyDllTestDebugMyDllTest.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:ExerciseForTestCsharpAndCPPMyDllTestDebugMyDllTest.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:ExerciseForTestCsharpAndCPPMyDllTestDebugMyDllTest.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
        public static extern IntPtr GetStringMalloc();

        [DllImport(@"D:ExerciseForTestCsharpAndCPPMyDllTestDebugMyDllTest.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代码同上操作】

    ——Death、Mr
  • 相关阅读:
    JavaBean命名不规范导致数据导出丢失
    Vue应用部署
    vue-cli的lib库模式下调试和不输出map文件
    Vue CLI的理解
    MongoDB副本集原理
    MongDB副本集成员状态
    Appium Android环境搭建
    如何自己实现一个HTMLRunner
    使用Flask搭建基于unittest的简单用例挑选及执行平台
    Django Admin中增加导出Excel功能
  • 原文地址:https://www.cnblogs.com/deathmr/p/6055561.html
Copyright © 2020-2023  润新知