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 的信号槽)进行后续处理。

相关推荐
boonya2 小时前
Spring AI 深度实践教程:从“能用”到“用好”
开发语言·python
(Charon)2 小时前
【Qt/C++】Qt/C++ 中 :: 和 . 到底有什么区别?
开发语言·c++·qt
REDcker2 小时前
C++跨平台与跨语言绑定工具:SWIG、Djinni 等选型
开发语言·c++
傻啦嘿哟2 小时前
Python 操作 Word 文档属性与字数统计方法详解
开发语言·c#
郝学胜-神的一滴2 小时前
[ 力扣 1124 ] 解锁最长良好时段问题:前缀和+哈希表的优雅解法
java·开发语言·数据结构·python·算法·leetcode·散列表
戴西软件2 小时前
戴西CAxWorks.VPG车辆工程仿真软件|假人+座椅双调整 汽车仿真效率直接拉满
java·开发语言·人工智能·python·算法·ui·汽车
北漂Zachary2 小时前
PHP vs C++ vs 易语言:三大语言对比解析
开发语言·c++·php
FeBaby2 小时前
使用mat 分析java OOM问题
java·开发语言
无限进步_2 小时前
二叉树的中序遍历(非递归实现)
开发语言·数据结构·c++·windows·算法·visual studio