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

 更新时间:2026年04月21日 09:08:07   作者:碎碎念的安静  
在开发中,我们有时需要让不同的框架程序进行实时数据交互,本文将详细介绍如何通过 WM_COPYDATA 实现 WPF 与 Qt 的双向消息互传,感兴趣的小伙伴可以了解下

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

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

1. 核心原理:WM_COPYDATA

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

[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)获取窗口句柄并添加监听。

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 窗口的句柄。

[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 子类中实现:

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 更灵活,支持模糊匹配。

// 查找包含特定标题的窗口
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 的信号槽)进行后续处理。

5.知识拓展

在跨技术栈的桌面应用开发中,常常需要让 WPF(.NET)与 Qt(C++)两个独立进程协同工作,比如 WPF 负责主界面和业务逻辑,Qt 负责高性能图形渲染或视频处理。此时就需要一种高效、稳定的进程间通信(IPC)机制。

下面我会分析几种常见的 IPC 方式,对比它们的优缺点,并给出 WPF 和 Qt 两端的具体实现示例。

主流 IPC 方式对比

IPC 方式优点缺点适用场景
命名管道 (Named Pipe)Windows 原生,高性能,支持双向通信,流式传输仅限 Windows(Qt 的 QLocalSocket 在 Windows 上底层就是命名管道),跨平台需额外适配Windows 平台首选,WPF 和 Qt 支持均好
TCP Socket (loopback)完全跨平台,代码通用,支持网络远程需处理粘包、连接状态等,性能略低于命名管道需要跨平台(如 Qt 运行在 Linux/macOS)或已有网络编程经验
共享内存 (Shared Memory)吞吐量最大,延迟最低,适合传输大量数据(如图像帧)需要手动同步(互斥锁、信号量),数据格式复杂实时视频流、大数据块传输
消息队列 (MSMQ / RabbitMQ)解耦、持久化、支持广播重量级,引入中间件,延迟较高异步任务、高可靠场景
COM/DCOMWindows 深度集成,可远程调用对象学习曲线陡峭,配置复杂,易出问题老旧系统集成,不推荐新项目

推荐组合

  • 一般控制指令、小数据量 → 命名管道(简单可靠)
  • 大量数据(如实时视频) → 共享内存 + 命名管道(同步信号)
  • 需要跨平台 → TCP Socket

1.命名管道实现(Windows 推荐)

WPF 端(C#, .NET 8)

使用 System.IO.Pipes.NamedPipeServerStream

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($"收到: {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 <QLocalSocket>
#include <QCoreApplication>
#include <QDebug>
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() << "收到:" << recvData;
    socket.disconnectFromServer();
    return 0;
}

注意QLocalSocket 在 Windows 上底层使用的就是命名管道,因此管道名不需要前缀(不要用 \\.\pipe\,直接写 MyPipe 即可)。

2.TCP Socket 实现(跨平台)

WPF 端(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($"收到: {msg}");
        // 发送
        string reply = "Hello from WPF";
        byte[] replyData = Encoding.UTF8.GetBytes(reply);
        await stream.WriteAsync(replyData, 0, replyData.Length);
    }
}

Qt 端(C++,使用 QTcpSocket)

#include <QTcpSocket>
#include <QCoreApplication>
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QTcpSocket socket;
    socket.connectToHost(QHostAddress::LocalHost, 12345);
    if (!socket.waitForConnected(3000)) {
        qDebug() << "连接失败";
        return -1;
    }
    // 发送
    socket.write("Hello from Qt");
    socket.waitForBytesWritten();
    // 接收
    socket.waitForReadyRead();
    QByteArray data = socket.readAll();
    qDebug() << "收到:" << data;
    socket.disconnectFromHost();
    return 0;
}

到此这篇关于WPF通过 WM_COPYDATA 实现与Qt的进程间通信的文章就介绍到这了,更多相关WPF与Qt进程间通信内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 手动编译C#代码的方法

    手动编译C#代码的方法

    在本文里小编给大家分享的是关于手动编译C#代码的方法和步骤,对此有需要的朋友们可以学习下。
    2018-12-12
  • C#中常见警告类型及处理方法详解

    C#中常见警告类型及处理方法详解

    在C#开发过程中,常常会遇到各种各样的警告信息,本文将结合多种常见情况,详细介绍如何处理C#中的一些典型警告,希望对大家有所帮助
    2024-11-11
  • C# WINFORM自定义异常处理方法

    C# WINFORM自定义异常处理方法

    这篇文章主要介绍了一个简单的统一异常处理方法。系统底层出现异常,写入记录文件,系统顶层捕获底层异常,显示提示信息。需要的可以参考一下
    2021-12-12
  • C#简单聊天室雏形

    C#简单聊天室雏形

    这篇文章主要为大家详细介绍了C#简单聊天室雏形,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • C#使用队列(Queue)解决简单的并发问题

    C#使用队列(Queue)解决简单的并发问题

    这篇文章主要介绍了使用队列(Queue)解决简单的并发问题,讲解的很细致,喜欢的朋友们可以了解一下
    2015-07-07
  • PC蓝牙通信C#代码实现

    PC蓝牙通信C#代码实现

    这篇文章主要为大家详细介绍了PC蓝牙通信C#代码实现,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • 算法证明每一位都相同十进制数不是完全平方数

    算法证明每一位都相同十进制数不是完全平方数

    这篇文章主要为大家介绍了算法证明每一位都相同十进制数不是完全平方数的过程论述,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • C#实现自定义FTP操作封装类实例

    C#实现自定义FTP操作封装类实例

    这篇文章主要介绍了C#实现自定义FTP操作封装类,涉及C#操作FTP的连接、传输、下载等操作的实现技巧,非常具有实用价值,需要的朋友可以参考下
    2015-03-03
  • 深入分析C#中WinForm控件之Dock顺序调整的详解

    深入分析C#中WinForm控件之Dock顺序调整的详解

    本篇文章是对C#中WinForm控件之Dock顺序调整进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C# Socket数据接收的三种实现方式

    C# Socket数据接收的三种实现方式

    本文主要介绍了C# Socket数据接收的三种实现方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07

最新评论