2012-09-05 54 views
10

问题简言之: 如何将托管代码中从Native DLL返回的内存释放为ItrPtr?从托管C#发布非托管内存,并指向它的位置

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

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

Windows已经在TestCppDllCall.exe中触发了一个断点。

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

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

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

为了把事情说清楚:

  1. 与其他DLL函数接下来的C#代码正常工作具有相同的签名和释放内存没有任何问题的作品。

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

  3. 我需要的所有功能是Native DLL函数通过引用接受两个参数(byte数组和int,在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:\CppDll\Bin\CppDll.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}\r\nArray Size : {1}\r\nReturn String : {2}", 
         retValue, arraySize, strMyBuffer); 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("Error calling DLL \r\n {0}", ex.Message); 
      } 
      finally 
      { 
       if (byteArrayPointer != IntPtr.Zero) 
        HeapFree(GetProcessHeap(), 0, byteArrayPointer); 
      } 
      Console.ReadKey(); 
     } 
    } 
} 

当调试这个代码我设置在线路断裂点(返回1)和缓冲区的值为:

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

当函数调用返回并且值为:

我在C#代码中获得了相同的值
52121536 

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

请让我知道是否有什么不清楚或者是否有任何错字,我不是英语母语的人。

回答

13

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

长答案:有不同的方式可以在您的DLL实现中分配内存。释放内存的方式必须与您分配内存的方式相匹配。例如,分配有new[](带方括号)的内存需要用delete[](与deletefree相对)释放。 C#不提供一个机制让你去做;您需要将指针返回给C++。

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

感谢您的快速回复。 我希望有一种方法可以直接在C#中释放非托管内存。 – khaled

+0

我同意dasblinkenlight:* cleananest *方式可以说是为您的.dll添加一个“免费”方法。但Peter Ritchie也是正确的:C#肯定有可能通过interop调用相应的“free”(例如'FreeCoTaskMem()')来分配.dll(例如'CoTaskMemAlloc()')。你不能从C#做的一件事是“删除”,如果.dll做了一个C++“新”的话。 “malloc/free”:OK。 CoTaskMemAlloc/FreeCoTaskMem:也行。 “新/免费”:绝对*不*。 – paulsm4

+0

@ paulsm4请解释如何在C#malloc/free中调用? – awattar

3
HeapFree(GetProcessHeap(), 0, byteArrayPointer); 

不,那是行不通的。堆句柄是错误的,CRT用HeapCreate()创建自己的堆。它被埋在CRT数据中,你无法得到它。你可以在技术上从GetProcessHeaps()中找回句柄,除非你不知道它是哪一个。

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

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

7

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

如果你需要使用分配与CoTaskMemAlloc与本地C++对象的内存,你可以使用放置新到初始化内存,就好像使用了new运算符一样。例如:

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

这与new不分配内存只是呼吁预分配内存的构造。

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

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

+0

+1 - CoTaskMemAlloc是在Windows中匹配托管/非托管(或任何其他跨语言)分配的官方方式。 –