【说明不当之处,望各位大神多多指正】
在实际情况中,很多平台调用的非托管函数都会用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代码同上操作】