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

C#与C++通过共享内存传递图像指针时出现读访问违例问题排查

C#传图像指针到C++出现Read Access Violation的错误排查

1. 托管内存未固定引发GC移动

C#的托管对象(如Bitmap)会被GC自动回收或移动内存位置,如果直接传递Scan0指针而不固定内存,C++处理过程中指针可能指向无效地址。

  • 错误示例(Server.cs):
    Bitmap bmp = new Bitmap(640, 480, PixelFormat.Format24bppRgb);
    IntPtr imgPtr = bmp.Scan0;
    SaveImage(imgPtr, bmp.Width, bmp.Height); // GC可能移动bmp内存,指针失效
    
  • 修复方式:用fixed语句固定内存指针:
    Bitmap bmp = new Bitmap(640, 480, PixelFormat.Format24bppRgb);
    var bitmapData = bmp.LockBits(new Rectangle(0,0,bmp.Width,bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
    fixed (byte* p = (byte*)bitmapData.Scan0)
    {
        SaveImage((IntPtr)p, bmp.Width, bmp.Height, bitmapData.Stride);
    }
    bmp.UnlockBits(bitmapData);
    
    或用GCHandle固定整个对象:
    GCHandle handle = GCHandle.Alloc(bmp, GCHandleType.Pinned);
    IntPtr imgPtr = handle.AddrOfPinnedObject();
    SaveImage(imgPtr, bmp.Width, bmp.Height, bmp.Stride);
    handle.Free();
    

2. cv::Mat构造参数与C#图像不匹配

C++构造cv::Mat时,必须严格匹配C#图像的宽、高、通道数、步长(stride),否则会出现内存越界访问。

  • 错误示例(Client.cpp):忽略C#的stride,直接用width*3作为步长:
    extern "C" __declspec(dllexport) void SaveImage(void* ptr, int width, int height)
    {
        // 错误:未考虑C# Bitmap的stride对齐(可能不等于width*3)
        cv::Mat img(height, width, CV_8UC3, (uchar*)ptr);
        cv::imwrite("test.jpg", img);
    }
    
  • 修复方式:传递C#的Bitmap.Stride到C++,构造Mat时指定步长:
    extern "C" __declspec(dllexport) void SaveImage(void* ptr, int width, int height, int stride)
    {
        // 正确:使用传入的stride,匹配C#内存布局
        cv::Mat img(height, width, CV_8UC3, (uchar*)ptr, stride);
        // 注意:C# Format24bppRgb是BGR顺序,无需转换即可直接保存
        cv::imwrite("test.jpg", img);
    }
    

3. 像素格式与数据类型不匹配

C#图像的像素格式必须和C++的cv::Mat数据类型严格对应,否则会因字节数不匹配导致越界:

  • C# Format24bppRgb → C++ CV_8UC3(每个像素3字节,BGR顺序)
  • C# Format32bppArgb → C++ CV_8UC4(每个像素4字节,BGRA顺序)
  • C# Format8bppIndexed → C++ CV_8UC1(单通道灰度)
    如果混用格式(比如C#传32位图像,C++用CV_8UC3),必然会引发内存访问错误。

4. 指针生命周期失效

C#传递的指针指向的内存如果在C++处理前就被释放,会导致无效地址访问。比如临时Bitmap对象在函数返回后被GC回收:

  • 错误示例(Server.cs):
    IntPtr GetImagePtr()
    {
        Bitmap tempBmp = new Bitmap(640,480);
        return tempBmp.Scan0; // tempBmp被回收,指针无效
    }
    
  • 修复方式:确保C++处理完图像前,C#的图像对象始终处于有效状态(比如将对象声明在函数外部,或固定内存直到处理完成)。

5. 调用约定不匹配

C#调用C++ DLL函数时,必须保证调用约定一致,否则会导致参数传递混乱,传递错误的指针地址:

  • C++导出函数如果用__stdcall(默认DLL调用约定),C#需在DllImport中指定:
    [DllImport("Client.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern void SaveImage(IntPtr ptr, int width, int height, int stride);
    
  • 如果C++用__cdecl,则C#对应设置CallingConvention = CallingConvention.Cdecl。

6. 错误释放外部内存

C++中不能手动释放C#托管内存,cv::Mat构造时只是引用外部指针,无需手动释放:

  • 错误示例(Client.cpp):
    void SaveImage(void* ptr, int width, int height, int stride)
    {
        cv::Mat img(height, width, CV_8UC3, (uchar*)ptr, stride);
        cv::imwrite("test.jpg", img);
        delete[] (uchar*)ptr; // 错误:C#内存由GC管理,不能手动释放
    }
    
  • 修复方式:删除手动释放内存的代码,仅保留cv::imwrite逻辑即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 05:54:09