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

非托管Visual C++ DLL主动向C# WinForm GUI高效传输数据的实现方法

非托管C++ DLL向C# WinForm主动推流的最优实现方案(回调机制)

该方案完全满足仅触发一次、后续C++主动推送的要求,性能远高于Socket,无网络栈开销,适配100ms级的高频像素数据传输场景。

核心实现逻辑

  • C#侧提前定义回调函数委托,向C++ DLL注册回调实例
  • C++ DLL完成每次计算后,直接调用注册的回调函数,将字节数组和长度作为参数传递给C#
  • C#侧接收到数据后,跨线程更新UI生成位图

实现步骤

1. C#侧代码实现

首先定义匹配调用约定的回调委托,导入DLL的注册方法,注意要将委托实例存为类成员避免被GC回收,同时处理跨线程UI更新:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public partial class MainForm : Form
{
    // 定义回调委托,和C++侧的函数指针签名完全匹配
    // CallingConvention.Cdecl对应C++默认调用约定
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void DataReceivedDelegate(IntPtr pixelData, int dataLength);

    // 导入C++ DLL的注册回调方法
    [DllImport("YourCppDll.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void RegisterDataCallback(DataReceivedDelegate callback);

    // 导入触发传输的启动方法,仅需调用一次
    [DllImport("YourCppDll.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void StartDataTransmission();

    // 将回调实例存为类成员,避免GC回收导致C++调用崩溃
    private DataReceivedDelegate _dataCallback;

    public MainForm()
    {
        InitializeComponent();
        // 初始化回调
        _dataCallback = OnDataReceived;
        RegisterDataCallback(_dataCallback);
        // 仅触发一次启动流程
        StartDataTransmission();
    }

    // 回调执行逻辑,运行在C++的工作线程,不可直接操作UI
    private void OnDataReceived(IntPtr pixelData, int dataLength)
    {
        // 拷贝非托管内存到C#字节数组
        byte[] pixelBuffer = new byte[dataLength];
        Marshal.Copy(pixelData, pixelBuffer, 0, dataLength);

        // 跨线程更新UI必须用Invoke
        if (this.InvokeRequired)
        {
            this.Invoke(new Action(() => UpdateBitmap(pixelBuffer)));
            return;
        }
        UpdateBitmap(pixelBuffer);
    }

    // 拼接像素列生成位图的业务逻辑
    private void UpdateBitmap(byte[] pixelColumn)
    {
        // 你的位图拼接逻辑
    }
}

2. C++ DLL侧代码实现

定义函数指针类型,导出注册方法,计算完成后主动调用回调:

// 头文件定义
extern "C" __declspec(dllexport) void RegisterDataCallback(void(__cdecl *callback)(const unsigned char* data, int length));
extern "C" __declspec(dllexport) void StartDataTransmission();

// 全局存储回调指针
static void(__cdecl *g_dataCallback)(const unsigned char* data, int length) = nullptr;

// 注册回调的实现
void RegisterDataCallback(void(__cdecl *callback)(const unsigned char* data, int length))
{
    g_dataCallback = callback;
}

// 启动传输的实现,内部会启动工作线程处理传感器数据
void StartDataTransmission()
{
    // 你的逻辑:启动工作线程,循环接收传感器数据、计算
    // 示例伪代码:
    while(计算任务未停止)
    {
        // 等待传感器数据,完成计算得到像素列字节数组
        unsigned char* pixelData = 计算得到的像素列缓冲区;
        int dataLength = 像素列长度;

        // 回调已注册则主动推送数据
        if(g_dataCallback != nullptr)
        {
            g_dataCallback(pixelData, dataLength);
        }
        // 等待100ms下一次数据
        Sleep(100);
    }
}

性能优化建议

  • 可预分配固定长度的共享缓冲区,避免每次调用Marshal.Copy产生的内存拷贝开销,直接在C#侧固定缓冲区内存供C++写入
  • 若数据量较大,可使用GCHandle.Alloc固定C#字节数组,将指针传递给C++直接写入,完全消除内存拷贝
  • 回调内不要执行耗时逻辑,像素拼接和UI更新逻辑尽量异步处理,避免阻塞C++的工作线程

常见问题规避

  • 禁止将C#回调委托定义为局部变量,必须作为类成员持有,否则GC回收后C++调用会触发内存访问错误
  • C++侧传递的缓冲区不要在回调返回前释放,等C#完成拷贝后再回收或复用
  • WinForm UI更新必须通过Invoke/BeginInvoke封送到UI线程,否则会抛出跨线程操作异常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 04:12:01