C#向Unreal Engine C++共享内存传点云数据存在内存泄漏
问题描述
在Unreal Engine中通过共享内存从C#向C传输double类型的点云数据(每次传输大小不固定),代码运行正常,但C端启动后内存占用持续轻微上升,存在内存泄漏。
已尝试的解决方式:
- 读取内存后用
std::memset(SharedMemoryData, 0, (numPoints*3)*sizeof(double));重置内存 - 通过
MEMORY_BASIC_INFORMATION计算已使用内存并尝试重置 - 检查所有变量的重置和释放操作
但问题仍未解决,求排查遗漏点。
C# 发送端代码
MemoryMappedFile mmf = null; Mutex mutex = null; public void ProcessingEventHandler(object sender, DLLFunctions.ProcessingEventArgsPoints args) { if (mmf == null) { long maxSize = 1100000; // doubles * 8 bytes per double mmf = MemoryMappedFile.CreateNew("SharedMemoryTest", maxSize); mutex = new Mutex(true, "SharedMemoryTestMutex", out mutexCreated); } if (mmf != null) { try { using (var stream = mmf.CreateViewStream()) { // Acquire the mutex mutex.WaitOne(); try { using (BinaryWriter bw = new BinaryWriter(stream)) { // Write the count of valid doubles first bw.Write(Convert.ToDouble(args.num)); foreach (double value in args.pointData) { bw.Write(value); } } } finally { // Ensure the mutex is always released mutex.ReleaseMutex(); } } } catch (Exception e) { // Handle exceptions appropriately Console.WriteLine("An error occurred: " + e.Message); } } }
Unreal Engine C++ 接收端代码
说明:
OpenMappedMemory函数仅调用一次,GetMappedMemoryData函数每帧(Tick)调用一次。
头文件
static bool OpenMappedMemory(const FString& sharedMemoryName); static TArray<FLidarPointCloudPoint> GetMappedMemoryData(); static void CloseSharedMemory(); //to lidar points static TArray<FLidarPointCloudPoint> Points; static void* SharedMemoryHandle; // Mapped memory handle. static double* SharedMemoryData; // Pointer to memory data.
.cpp文件
#include "SharedMemory.h" #include <windows.h> constexpr size_t MAX_EXPECTED_SIZE = 1100000; size_t memorySize = 0; void* SharedMemory::SharedMemoryHandle; // Mapped memory handle. double* SharedMemory::SharedMemoryData; // Pointer to memory data. TArray<FLidarPointCloudPoint> SharedMemory::Points; HANDLE MutexHandle = nullptr; SharedMemory::SharedMemory() { } SharedMemory::~SharedMemory() { CloseSharedMemory(); } bool SharedMemory::OpenMappedMemory(const FString& sharedMemoryName) { MutexHandle = OpenMutex(MUTEX_ALL_ACCESS, false, TEXT("SharedMemoryTestMutex")); if (MutexHandle == nullptr) { UE_LOG(LogClass, Log, TEXT("Could not open mutex.")); return false; } SharedMemoryHandle = OpenFileMapping( FILE_MAP_ALL_ACCESS, // File Mapping Security and Access Rights true, // Do not inherit the handle *sharedMemoryName); if (!SharedMemoryHandle) { UE_LOG(LogClass, Log, TEXT("Memory-mapped file does not exist.")); return false; } // Map view of the file SharedMemoryData = static_cast<double*>(MapViewOfFile(SharedMemoryHandle, FILE_MAP_ALL_ACCESS, 0, 0, MAX_EXPECTED_SIZE)); // 0 to map the entire file // Calculate the memory size MEMORY_BASIC_INFORMATION memInfo; if (VirtualQuery(SharedMemoryData, &memInfo, sizeof(memInfo)) == 0) { UE_LOG(LogClass, Log, TEXT("Failed to query memory information.")); UnmapViewOfFile(SharedMemoryData); SharedMemoryData = nullptr; CloseHandle(SharedMemoryHandle); CloseHandle(MutexHandle); SharedMemoryHandle = nullptr; MutexHandle = nullptr; return false; } // The size of the memory region is stored in the RegionSize member of MEMORY_BASIC_INFORMATION memorySize = static_cast<size_t>(memInfo.RegionSize); // UE_LOG(LogClass, Log, TEXT("memory information. %d"), memorySize); return true; } TArray<FLidarPointCloudPoint> SharedMemory::GetMappedMemoryData() { Points.Empty(); if (!SharedMemoryData) { UE_LOG(LogTemp, Log, TEXT("Memory-mapped data does not exist.")); return Points; } size_t numPoints = static_cast<size_t>(SharedMemoryData[0]); try { // Read data from shared memory for (size_t i = 0; i < numPoints; i += 3) { size_t baseIndex = 1 + i; double x = SharedMemoryData[baseIndex] * 100; double y = SharedMemoryData[baseIndex + 1] * 100; double z = SharedMemoryData[baseIndex + 2] * 100; auto Point = MakeUnique<FLidarPointCloudPoint>(FVector3f(x, y, z)); Point->Color = FColor::Red; Points.Add(MoveTemp(*Point)); } } catch (...) { UE_LOG(LogTemp, Log, TEXT("Exception occurred while reading shared memory data.")); } // std::memset(SharedMemoryData, 0, (numPoints*3)*sizeof(double)); return Points; } void SharedMemory::CloseSharedMemory() { if (SharedMemoryData != nullptr) { UnmapViewOfFile(SharedMemoryData); SharedMemoryData = nullptr; } if (SharedMemoryHandle != nullptr) { CloseHandle(SharedMemoryHandle); SharedMemoryHandle = nullptr; } if (MutexHandle != nullptr) { CloseHandle(MutexHandle); MutexHandle = nullptr; } }
内存泄漏排查与修复建议
未用互斥锁保护共享内存读取
接收端GetMappedMemoryData读取共享内存时未获取互斥锁,可能导致C#端写入数据时,C++端读取到不完整/脏数据,引发内存分配异常或数组越界,间接导致泄漏。
修复:读取前加锁,读取完成后解锁:// 在读取共享内存前添加 if (WaitForSingleObject(MutexHandle, INFINITE) != WAIT_OBJECT_0) { UE_LOG(LogTemp, Error, TEXT("Failed to acquire mutex!")); return Points; } // 读取完成后(try块结束后)添加 ReleaseMutex(MutexHandle);冗余的
MakeUnique操作
用MakeUnique创建对象再通过MoveTemp添加到TArray,会触发不必要的对象拷贝或UniquePtr管理开销,可能引发隐性泄漏。
修复:直接构造对象添加:FLidarPointCloudPoint Point(FVector3f(x, y, z)); Point.Color = FColor::Red; Points.Add(Point); // 或更高效的Emplace方式 Points.Emplace(FVector3f(x, y, z)); Points.Last().Color = FColor::Red;缺失共享内存边界校验
未校验numPoints*3 +1是否超过共享内存最大容量,若C#端写入数据超出预期,会导致越界读取,破坏内存结构。
修复:添加边界检查:size_t totalDoubles = 1 + numPoints * 3; // 1个计数 + 每个点3个double if (totalDoubles > MAX_EXPECTED_SIZE / sizeof(double)) { UE_LOG(LogTemp, Error, TEXT("Received data exceeds shared memory capacity!")); return Points; }TArray缓冲区复用导致的内存增长
静态变量Points调用Empty()时,默认会保留内存缓冲区以便复用,若点云数量持续递增,缓冲区会持续扩容,导致内存占用上升。
修复:强制释放缓冲区:Points.Empty(true);C#端流位置未重置
C#端每次写入时,stream位置可能未重置到开头,导致后续数据追加到旧数据后,C++端读取到错误的numPoints,进而读取过多数据。
修复:写入前重置流位置:stream.Position = 0;
内容的提问来源于stack exchange,提问作者anti
相关产品推荐
相关产品推荐

