You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
    }
}

内存泄漏排查与修复建议
  1. 未用互斥锁保护共享内存读取
    接收端GetMappedMemoryData读取共享内存时未获取互斥锁,可能导致C#端写入数据时,C++端读取到不完整/脏数据,引发内存分配异常或数组越界,间接导致泄漏。
    修复:读取前加锁,读取完成后解锁:

    // 在读取共享内存前添加
    if (WaitForSingleObject(MutexHandle, INFINITE) != WAIT_OBJECT_0) {
        UE_LOG(LogTemp, Error, TEXT("Failed to acquire mutex!"));
        return Points;
    }
    // 读取完成后(try块结束后)添加
    ReleaseMutex(MutexHandle);
    
  2. 冗余的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;
    
  3. 缺失共享内存边界校验
    未校验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;
    }
    
  4. TArray缓冲区复用导致的内存增长
    静态变量Points调用Empty()时,默认会保留内存缓冲区以便复用,若点云数量持续递增,缓冲区会持续扩容,导致内存占用上升。
    修复:强制释放缓冲区:

    Points.Empty(true);
    
  5. C#端流位置未重置
    C#端每次写入时,stream位置可能未重置到开头,导致后续数据追加到旧数据后,C++端读取到错误的numPoints,进而读取过多数据。
    修复:写入前重置流位置:

    stream.Position = 0;
    

内容的提问来源于stack exchange,提问作者anti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 08:27:32