当前位置: 首页
编程语言
WPF通过 WM_COPYDATA 实现与Qt的进程间通信

WPF通过 WM_COPYDATA 实现与Qt的进程间通信

热心网友 时间:2026-04-21
转载

在跨技术栈桌面应用开发中,实现 WPF(C#)与 Qt(C++)的实时数据交互

WPF通过 WM_COPYDATA 实现与Qt的进程间通信

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈

本文将深入解析如何利用 Windows 原生消息机制,实现 WPF 与 Qt 应用程序之间的高效、低延迟双向通信,并提供完整的代码实现与最佳实践指南。

1. 核心原理:WM_COPYDATA 消息机制

WM_COPYDATA 是 Windows 操作系统提供的一种轻量级进程间通信(IPC)方式,特别适合在不同技术栈的应用程序之间传递只读数据。其核心数据结构定义如下:

[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT {
    public IntPtr dwData; // 用户自定义标识符,可用于区分消息类型
    public int cbData;    // 待传递数据的字节长度
    public IntPtr lpData; // 指向实际数据缓冲区的指针
}

2. WPF 端完整实现:消息接收与发送

WPF 框架对底层 Win32 消息循环进行了封装,因此需要通过 HwndSource 挂载消息钩子来拦截和处理原生 Windows 消息。

2.1 接收消息:挂载 WndProc 消息处理钩子

在 WPF 窗口初始化完成后,获取窗口句柄并添加自定义消息处理函数。

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) {
    const int WM_COPYDATA = 0x004A;
    if (msg == WM_COPYDATA) {
        // 将指针转换为 COPYDATASTRUCT 结构体
        COPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(COPYDATASTRUCT));
        // 将指针内容解析为字符串(注意编码问题)
        string message = Marshal.PtrToStringAnsi(cds.lpData); 
        ProcessReceivedMessage(message); // 处理接收到的业务逻辑
        handled = true;
    }
    return IntPtr.Zero;
}

2.2 发送消息:定位目标窗口并发送数据

WPF 端需要调用 Win32 API 查找 Qt 应用程序窗口句柄,并构造消息进行发送。

[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 SendMessageToQt(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. 发送 WM_COPYDATA 消息
    SendMessage(targetHwnd, 0x004A, IntPtr.Zero, ref cds);
    
    // 4. 释放已分配的非托管内存,防止内存泄漏
    Marshal.FreeHGlobal(cds.lpData);
}

3. Qt 端完整实现:原生事件处理与消息发送

Qt 框架提供了 nativeEvent 虚函数,可以方便地拦截和处理 Windows 原生消息,实现跨框架通信。

3.1 接收消息:重写 nativeEvent 函数

在 QMainWindow 或 QWidget 的子类中实现原生消息处理逻辑。

bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result) {
    MSG* msg = static_cast(message);
    if (msg->message == WM_COPYDATA) {
        COPYDATASTRUCT* cds = reinterpret_cast(msg->lParam);
        // 解析 UTF-8 编码的字节流数据
        QString receivedMsg = QString::fromUtf8(static_cast(cds->lpData), cds->cbData);
        m_receivedMsgEdit->append("[From WPF]: " + receivedMsg);
        *result = 1; // 标记消息已处理
        return true;
    }
    return QMainWindow::nativeEvent(eventType, message, result);
}

3.2 发送消息:使用 EnumWindows 进行窗口模糊查找

Qt 端可以通过遍历系统窗口的方式,灵活地查找包含特定标题的 WPF 窗口,实现更稳健的句柄获取。

// 回调函数,用于遍历所有顶层窗口
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 = (void*)data.constData();
        SendMessage(sd.resultHandle, WM_COPYDATA, (WPARAM)this->winId(), (LPARAM)&cds);
    }
}

4. 关键注意事项与优化建议

编码一致性:

  • WPF 发送端使用 UTF8.GetBytes 进行编码。
  • Qt 接收端使用 QString::fromUtf8 进行解码。

重要细节: 示例中 WPF 接收端使用了 Marshal.PtrToStringAnsi,这仅适用于 ASCII 字符。如果 Qt 发送的是包含中文等非 ASCII 字符的数据,WPF 接收端应改为使用 UTF-8 解码(如 Marshal.PtrToStringUTF8 或相应编码转换),否则会出现乱码。

窗口句柄稳定性: WM_COPYDATA 通信依赖于窗口句柄。如果目标窗口的标题在运行时可能改变,建议使用窗口类名(FindWindow 的第一个参数)或更稳定的查找方式。

内存管理安全:

WM_COPYDATA 消息在 SendMessage 函数返回前,发送端的数据内存必须保持有效且不被修改。

在 WPF 端,使用 Marshal.AllocHGlobal 申请的非托管内存,务必在使用后通过 Marshal.FreeHGlobal 手动释放,以避免内存泄漏。

避免 UI 阻塞: SendMessage 是同步阻塞调用。如果接收端的消息处理逻辑非常耗时,会导致发送端 UI 线程卡顿。建议在接收端将耗时的业务逻辑通过异步方式处理(例如在 WPF 中使用 Dispatcher.BeginInvoke,在 Qt 中使用信号槽机制投递到其他线程)。

5. 知识拓展:WPF 与 Qt 进程间通信方案全面对比

在复杂的桌面应用架构中,常常需要整合不同技术栈的优势,例如使用 WPF(.NET)构建主业务界面,而使用 Qt(C++)进行高性能图形渲染或音视频处理。选择一种高效、稳定的进程间通信(IPC)方案至关重要。

以下对几种常见的 IPC 技术进行详细对比,并附上 WPF 与 Qt 的实现示例,帮助开发者根据实际场景做出最佳选择。

主流 IPC 方式优缺点与应用场景分析

IPC 方式核心优势潜在缺点典型适用场景
命名管道 (Named Pipe)Windows 原生支持,性能优异,支持全双工通信和流式数据传输主要限于 Windows 平台(Qt 的 QLocalSocket 在 Windows 底层即为命名管道)Windows 平台首选方案,WPF 与 Qt 均有良好支持
TCP Socket (本地回环)完全跨平台,代码通用性强,支持网络远程通信需要处理粘包、连接状态维护等问题,性能略低于命名管道需要支持跨平台(如 Qt 运行于 Linux/macOS)或已有网络编程基础
共享内存 (Shared Memory)吞吐量最大,延迟最低,适合传输大块数据(如图像帧、音频流)需要手动实现同步机制(如互斥锁、信号量),数据格式设计较复杂实时视频流处理、大规模数据块传输
消息队列 (MSMQ / RabbitMQ)彻底解耦、支持消息持久化、具备广播能力系统重量级,需引入中间件,通信延迟相对较高异步任务调度、高可靠性业务场景
COM/DCOM与 Windows 系统深度集成,支持远程对象调用学习曲线陡峭,配置复杂,调试困难遗留系统集成,新项目不推荐使用

方案选型推荐

  • 常规控制指令、小数据量传输 → 优先选择 命名管道,简单可靠。
  • 大量数据(如实时视频帧)传输 → 采用 共享内存 + 命名管道(用于传递同步信号和控制命令)的组合方案。
  • 需要支持跨平台部署 → 使用 TCP Socket(本地回环地址 127.0.0.1)

1. 命名管道实现示例(Windows 平台推荐)

WPF 服务端实现(C#, .NET)

using System.IO.Pipes;
using System.Text;
using System.Threading.Tasks;
public class PipeServer
{
    private NamedPipeServerStream _server;
    public async Task StartAsync()
    {
        _server = new NamedPipeServerStream("MyPipe", PipeDirection.InOut, 1, PipeTransmissionMode.Message);
        Console.WriteLine("等待 Qt 客户端连接...");
        await _server.WaitForConnectionAsync();
        // 异步接收消息
        byte[] buffer = new byte[4096];
        int bytesRead = await _server.ReadAsync(buffer, 0, buffer.Length);
        string msg = Encoding.UTF8.GetString(buffer, 0, bytesRead);
        Console.WriteLine($"收到来自 Qt 的消息: {msg}");
        // 发送回复
        string reply = "Hello from WPF";
        byte[] replyData = Encoding.UTF8.GetBytes(reply);
        await _server.WriteAsync(replyData, 0, replyData.Length);
        await _server.FlushAsync();
        _server.Disconnect();
    }
}

Qt 客户端实现(C++,使用 QLocalSocket)

#include 
#include 
#include 
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QLocalSocket socket;
    socket.connectToServer("MyPipe");
    if (!socket.waitForConnected(3000)) {
        qDebug() << "连接失败:" << socket.errorString();
        return -1;
    }
    // 发送数据
    QByteArray sendData = "Hello from Qt";
    socket.write(sendData);
    socket.waitForBytesWritten();
    // 等待并接收回复
    socket.waitForReadyRead();
    QByteArray recvData = socket.readAll();
    qDebug() << "收到 WPF 回复:" << recvData;
    socket.disconnectFromServer();
    return 0;
}

重要提示:在 Qt 中使用 QLocalSocket 连接 Windows 命名管道时,管道名直接使用自定义名称(如 "MyPipe")即可,无需添加 Windows 命名管道的标准前缀 \\.\pipe\

2. TCP Socket 实现示例(跨平台方案)

WPF TCP 服务端实现(C#)

using System.Net;
using System.Net.Sockets;
using System.Text;
public class TcpServer
{
    private TcpListener _listener;
    public async Task StartAsync()
    {
        _listener = new TcpListener(IPAddress.Loopback, 12345);
        _listener.Start();
        using var client = await _listener.AcceptTcpClientAsync();
        using var stream = client.GetStream();
        // 接收数据
        byte[] buffer = new byte[4096];
        int len = await stream.ReadAsync(buffer, 0, buffer.Length);
        string msg = Encoding.UTF8.GetString(buffer, 0, len);
        Console.WriteLine($"收到 TCP 消息: {msg}");
        // 发送回复
        string reply = "Hello from WPF";
        byte[] replyData = Encoding.UTF8.GetBytes(reply);
        await stream.WriteAsync(replyData, 0, replyData.Length);
    }
}

Qt TCP 客户端实现(C++,使用 QTcpSocket)

#include 
#include 
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QTcpSocket socket;
    socket.connectToHost(QHostAddress::LocalHost, 12345);
    if (!socket.waitForConnected(3000)) {
        qDebug() << "TCP 连接失败";
        return -1;
    }
    // 发送数据
    socket.write("Hello from Qt");
    socket.waitForBytesWritten();
    // 接收回复
    socket.waitForReadyRead();
    QByteArray data = socket.readAll();
    qDebug() << "收到 TCP 回复:" << data;
    socket.disconnectFromHost();
    return 0;
}
来源:https://www.jb51.net/program/36253635g.htm

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
CPUInfo对系统性能有何影响

CPUInfo对系统性能有何影响

CPUInfo对系统性能的影响 核心结论 先说一个核心判断:Linux 系统中的 CPUInfo(典型代表是 proc cpuinfo 文件和 lscpu 命令)本身并不直接提升或降低性能。它的角色,更像是一位“硬件情报官”,只负责读取和展示 CPU 的详细信息与拓扑结构。那么它的价值何在?答案是

时间:2026-04-23 22:29
idea新窗口打开工程不生效问题及解决

idea新窗口打开工程不生效问题及解决

一、确保设置了 首先,你得确认这个选项已经勾选上。具体路径是:打开 IntelliJ IDEA 的设置,找到 Settings Preferences -> Appearance & Beha vior -> System Settings,然后确保 Open project in new wind

时间:2026-04-23 22:29
CentOS环境下Golang日志的最佳实践

CentOS环境下Golang日志的最佳实践

在CentOS环境下使用Golang进行日志记录的最佳实践 在CentOS服务器上部署Golang应用时,高效的日志管理是提升后期运维效率与系统可观测性的核心。一套设计良好的日志策略,能将问题排查从“大海捞针”转变为“精准定位”。本文将深入探讨在CentOS系统中,如何构建一套既高效又易于维护的Go

时间:2026-04-23 22:29
如何优化CentOS Java日志记录效率

如何优化CentOS Java日志记录效率

优化CentOS上Ja va应用程序的日志记录效率 在CentOS服务器上跑Ja va应用,日志记录效率上不去,性能瓶颈往往就藏在这里。别担心,这事儿有章可循。下面这几个关键策略和具体步骤,能帮你系统性地解决问题。 1 选择高效的日志框架 工欲善其事,必先利其器。选对日志框架,是提升效率的第一步。

时间:2026-04-23 22:28
Ubuntu安装PySide6开发桌面应用实践

Ubuntu安装PySide6开发桌面应用实践

一、引言 最近在对接大模型测试任务时,需要开发一个Python桌面应用。于是,就有了这篇在WSL2的Ubuntu环境下配置PySide6开发环境的实战记录。 二、Ubuntu非桌面端安装PySide6 理想情况下,在Ubuntu桌面系统里直接安装PySide6,再配上VSCode就能开干。但手头只有

时间:2026-04-23 22:28
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜
热门教程
更多
  • 游戏攻略
  • 安卓教程
  • 苹果教程
  • 电脑教程