Unity中调用C++ DLL时Marshal.FreeHGlobal()失败,如何保证内存安全?
解决Unity C#调用自定义C++ DLL的内存安全问题
问题根源
你遇到的free(): invalid pointer错误,核心原因是内存分配和释放的分配器不匹配:
- C++函数里用
new[]分配内存,对应的释放必须用delete[]; Marshal.FreeHGlobal()是用来释放由Marshal.AllocHGlobal()分配的内存,依赖Windows系统堆分配器,和C++标准库的堆不是同一个,直接调用必然触发内存错误。
方案一:在DLL中提供配套的释放函数(推荐)
最稳妥的方式是让分配内存的C++代码负责提供释放逻辑,在DLL中新增一个释放函数,C#调用该函数来释放内存。
修改后的C++代码
// 原解码函数保持不变 extern "C" __declspec(dllexport) unsigned char* MyCppFunction(const unsigned char* messagePtr, int messageSize, int* decodedFrameLength) { std::vector<uint8_t> decoded_img(33554432); int decoded_len = 0; decoder_->DecodeFrame( reinterpret_cast<const unsigned char*>(messagePtr), messageSize, reinterpret_cast<unsigned char*>(decoded_img.data()), &decoded_len, 33554432); *decodedFrameLength = decoded_len; unsigned char* outputData = new unsigned char[decoded_len]; std::memcpy(outputData, decoded_img.data(), decoded_len); return outputData; } // 新增的内存释放函数 extern "C" __declspec(dllexport) void FreeDecodedData(unsigned char* data) { delete[] data; }
修改后的C#代码
using System.Runtime.InteropServices; using UnityEngine; public class YourScript : MonoBehaviour { [DllImport("你的DLL文件名.dll")] private static extern IntPtr MyCppFunction(byte[] messagePtr, int messageSize, out int decodedFrameLength); [DllImport("你的DLL文件名.dll")] private static extern void FreeDecodedData(IntPtr data); void OnMessage(Message message) { byte[] byteArray = message.payload.ToByteArray(); int decodedFrameLength; IntPtr output = MyCppFunction(byteArray, byteArray.Length, out decodedFrameLength); byte[] resultByteArray = new byte[decodedFrameLength]; Marshal.Copy(output, resultByteArray, 0, decodedFrameLength); // 调用DLL提供的释放函数 FreeDecodedData(output); Debug.Log($"decoded frame length was: {decodedFrameLength} and resultByteArray length was: {resultByteArray.Length}"); if (resultByteArray.Length > 0) { // 处理解码后的数据 } } }
方案二:使用跨语言兼容的分配器
如果不想新增释放函数,可以让C++使用和.NET互操作兼容的分配器(比如CoTaskMemAlloc),这样C#可以用Marshal.FreeCoTaskMem()直接释放。
修改后的C++代码
#include <objbase.h> // 引入CoTaskMemAlloc的头文件 extern "C" __declspec(dllexport) unsigned char* MyCppFunction(const unsigned char* messagePtr, int messageSize, int* decodedFrameLength) { std::vector<uint8_t> decoded_img(33554432); int decoded_len = 0; decoder_->DecodeFrame( reinterpret_cast<const unsigned char*>(messagePtr), messageSize, reinterpret_cast<unsigned char*>(decoded_img.data()), &decoded_len, 33554432); *decodedFrameLength = decoded_len; // 用CoTaskMemAlloc分配内存,兼容.NET的Marshal释放 unsigned char* outputData = static_cast<unsigned char*>(CoTaskMemAlloc(decoded_len)); std::memcpy(outputData, decoded_img.data(), decoded_len); return outputData; }
修改后的C#代码
void OnMessage(Message message) { byte[] byteArray = message.payload.ToByteArray(); int decodedFrameLength; IntPtr output = MyCppFunction(byteArray, byteArray.Length, out decodedFrameLength); byte[] resultByteArray = new byte[decodedFrameLength]; Marshal.Copy(output, resultByteArray, 0, decodedFrameLength); // 用Marshal.FreeCoTaskMem释放CoTaskMemAlloc分配的内存 Marshal.FreeCoTaskMem(output); Debug.Log($"decoded frame length was: {decodedFrameLength} and resultByteArray length was: {resultByteArray.Length}"); if (resultByteArray.Length > 0) { // 处理解码后的数据 } }
注意事项
- 必须严格遵循谁分配谁释放的原则,或者使用双方都兼容的分配器;
- 不要混用不同分配器的分配和释放函数,否则必然导致内存泄漏或崩溃;
- 若使用C++/CLI(混合模式),还可以用
gcnew直接分配托管数组,但需要修改DLL编译模式,适合更复杂的交互场景。
内容的提问来源于stack exchange,提问作者Lucas B
相关产品推荐
相关产品推荐

