从具有指针的托管C#中释放非托管内存
Release unmanaged memory from managed C# with pointer of it
简短的问题是:如何在托管代码中释放从本机DLL返回的内存作为ItrPtr?
详细信息:假设我们有一个简单的函数,它将两个参数作为OUTPUT,第一个是指向字节数组的引用指针,第二个是引用Int。该函数将根据一些规则分配字节数,并返回内存指针、字节大小和返回值(1表示成功,0表示失败)。
下面的代码运行良好,我可以正确地获得字节数组、字节计数和返回值,但当我尝试使用指针(IntPtr)释放内存时,我会得到异常:
Windows在TestCppDllCall.exe中触发了断点。
这可能是由于堆损坏,这表明TestCppDllCall.exe或其加载的任何DLL中存在错误。
这也可能是由于用户在TestCppDllCall.exe具有焦点时按下F12所致。
输出窗口可能具有更多诊断信息。
让事情变得清楚:
-
下一个C#代码与其他DLL函数一起正常工作,具有相同的签名,释放内存不会有任何问题。
-
如果您需要更改分配内存方法或添加任何其他代码,则接受(C)代码中的任何修改。
-
我需要的所有功能都是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[]
(与delete
或free
相反)释放。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++代码。
- 如何在c++中释放内存
- 为什么这个 std::queue/指向结构的指针列表直到 List.Size() == 0 才释放内存?
- std::unordered_map析构函数不释放内存?
- 在C++中释放内存期间,迭代器与指针有何不同
- 使用共享指针时,从共享指针本身释放内存的机制是什么
- 释放内存(主题模板)时出现问题
- 使用后自动释放内存
- C++ 如何释放内存
- 从函数内对象的向量中释放内存
- C++ 在不释放内存的情况下调用析构函数
- 多个线程之间的获取-释放内存顺序
- C++ - 析构函数只是释放内存还是实际删除对象
- 使用 RAII 替换最终块以释放内存
- 如何通过带有指向基类的指针的删除运算符释放内存
- 代码中的"sprintf"用法是否需要释放内存?
- C++何时使用 delete[] 并正确释放内存?
- 为什么此获取和释放内存围栏不能给出一致的值?
- 抛出新表达式的参数子表达式时释放内存
- 在这种情况下,如何释放内存?
- 在 c++ 中应按什么顺序释放内存?