WPF 与 Qt 进程间通信(IPC)

在开发中,我们有时需要让不同的框架程序(如 C# 的 WPF 和 C++ 的 Qt)进行实时数据交互。由于两者底层都运行在 Windows 系统上,利用 Win32 API 中的 WM_COPYDATA 消息是一种简单、高效且低延迟的解决方案。

本文将详细介绍如何通过 WM_COPYDATA 实现 WPF 与 Qt 的双向消息互传。

1. 核心原理:WM_COPYDATA

WM_COPYDATA 是 Windows 提供的一个用于进程间传递只读数据的消息。其核心数据结构如下:

csharp 复制代码
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT {
    public IntPtr dwData; // 自定义标识符
    public int cbData;    // 数据大小(字节)
    public IntPtr lpData; // 指向数据的指针
}

2. WPF 端实现:接收与发送

WPF 默认封装了底层的 Win32 消息循环,因此我们需要通过 HwndSource 来挂载钩子(Hook)监听原始消息。

2.1 接收消息:挂载 WndProc 钩子

在 WPF 中,我们需要在窗口初始化完成后(OnSourceInitialized)获取窗口句柄并添加监听。

csharp 复制代码
protected override void OnSourceInitialized(EventArgs e) {
    base.OnSourceInitialized(e);
    // 获取窗口句柄源并挂载钩子
    HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
    if (source != null) {
        source.AddHook(WndProc);
    }
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
    if (msg == 0x004A) { // WM_COPYDATA
        COPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(COPYDATASTRUCT));
        // 将指针内容解析为字符串
        string message = Marshal.PtrToStringAnsi(cds.lpData); 
        UpdateUI(message); // 处理业务逻辑
        handled = true;
    }
    return IntPtr.Zero;
}
2.2 发送消息:查找窗口并发送

WPF 需要利用 FindWindow 定位 Qt 窗口的句柄。

csharp 复制代码
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, ref COPYDATASTRUCT lParam);

public void SendToQt(string message) {
    // 1. 根据窗口标题寻找 Qt 进程句柄
    IntPtr targetHwnd = FindWindow(null, "Qt子程序");
    if (targetHwnd == IntPtr.Zero) return;

    // 2. 准备数据并分配内存
    byte[] sBuffer = System.Text.Encoding.UTF8.GetBytes(message);
    COPYDATASTRUCT cds;
    cds.dwData = (IntPtr)1024;
    cds.cbData = sBuffer.Length;
    cds.lpData = Marshal.AllocHGlobal(sBuffer.Length);
    Marshal.Copy(sBuffer, 0, cds.lpData, sBuffer.Length);

    // 3. 发送消息
    SendMessage(targetHwnd, 0x004A, IntPtr.Zero, ref cds);
    
    // 4. 释放内存
    Marshal.FreeHGlobal(cds.lpData);
}

3. Qt 端实现:接收与发送

Qt 通过重写 nativeEvent 函数可以非常方便地截获 Windows 原生消息。

3.1 接收消息:重写 nativeEvent

在 QMainWindow 或 QWidget 子类中实现:

cpp 复制代码
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result) {
    MSG* msg = static_cast<MSG*>(message);
    if (msg->message == WM_COPYDATA) {
        COPYDATASTRUCT* cds = reinterpret_cast<COPYDATASTRUCT*>(msg->lParam);
        
        // 解析 UTF-8 编码的字节流
        QString receivedMsg = QString::fromUtf8(static_cast<const char*>(cds->lpData), cds->cbData);
        
        m_receivedMsgEdit->append("[From WPF]: " + receivedMsg);
        *result = 1; // 标记已处理
        return true;
    }
    return QMainWindow::nativeEvent(eventType, message, result);
}
3.2 发送消息:模糊匹配窗口标题

Qt 示例中使用 EnumWindows 回调函数来搜索 WPF 窗口,这种方式比 FindWindow 更灵活,支持模糊匹配。

cpp 复制代码
// 查找包含特定标题的窗口
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
    SearchData* data = (SearchData*)lParam;
    wchar_t buffer[256];
    GetWindowTextW(hwnd, buffer, 256);
    QString title = QString::fromWCharArray(buffer);

    if (title.contains(data->partTitle)) {
        data->resultHandle = hwnd;
        return FALSE; // 找到即停止遍历
    }
    return TRUE;
}

void MainWindow::sendMessageToWPF(const QString& message) {
    SearchData sd;
    sd.partTitle = "Qt进程通信"; 
    EnumWindows(EnumWindowsProc, (LPARAM)&sd);

    if (sd.resultHandle) {
        QByteArray data = message.toUtf8();
        COPYDATASTRUCT cds;
        cds.dwData = 100;
        cds.cbData = data.size() + 1;
        cds.lpData = data.data();

        SendMessage(sd.resultHandle, WM_COPYDATA, (WPARAM)this->winId(), (LPARAM)&cds);
    }
}

4. 关键点总结与注意事项

编码统一性:

WPF 发送时使用了 UTF8.GetBytes。

Qt 接收时使用了 QString::fromUtf8。

警告:WPF 接收端代码中使用了 Marshal.PtrToStringAnsi,如果 Qt 发送的是中文字符,建议将 WPF 接收端也改为 UTF8 转换,避免乱码。

窗口标题: WM_COPYDATA 依赖句柄。如果窗口标题在运行时会改变,建议使用更稳定的查找方式(如类名查找)。

内存安全:

WM_COPYDATA 在 SendMessage 返回前,发送端的数据内存必须保持有效。

在 WPF 端,使用 Marshal.AllocHGlobal 申请的内存必须在使用完后通过 Marshal.FreeHGlobal 手动释放,防止内存泄漏。

同步性: SendMessage 是阻塞的。如果接收端处理逻辑非常耗时,会导致发送端 UI 界面卡死。建议接收端收到消息后通过异步方式(如 WPF 的 Dispatcher.BeginInvoke 或 Qt 的信号槽)进行后续处理。

相关推荐
灯澜忆梦16 分钟前
GO_文件处理---字符串操作
开发语言·golang
-银雾鸢尾-21 分钟前
里氏替换原则
开发语言·c#·里氏替换原则
এ慕ོ冬℘゜22 分钟前
JavaScript 数组核心方法实战|新增元素 + 数组转字符串 零基础详解
开发语言·javascript·ecmascript
一个初入编程的小白24 分钟前
C语言:函数栈帧与销毁
c语言·开发语言
czhc114007566326 分钟前
718:public internl class;partial []
开发语言
临床数据科学和人工智能兴趣组37 分钟前
R语言因其强大的统计功能、灵活的编程环境、活跃的社区支持和强大的R扩展包,迅速成为统计学和数据科学领域的首选工具之一
开发语言·数据分析·r语言·r语言-4.2.1
__log1 小时前
弱网环境下的“生命线“:从AI流式响应到大文件上传的稳定性
开发语言·人工智能·php
巧克力男孩dd2 小时前
Python超典型练习题(第一次作业)
开发语言·python·算法
TlSfoward2 小时前
TLSFOWARD TLS指纹
开发语言·数据库·爬虫·搜索引擎·https·php
闲猫2 小时前
LangChain / Core components / Models
开发语言·python·langchain