首页 > 编程语言 >WPF通过WM_COPYDATA实现与Qt的进程间通信

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

来源:互联网 2026-04-21 10:27:31

WPF与Qt进程间通信:WM_COPYDATA消息实战 在Windows桌面开发中,实现WPF(C#)与Qt(C++)程序间的实时数据交互是常见需求。由于两者均运行于Windows平台,利用Win32 API中的WM_COPYDATA消息进行进程间通信,是一种简单、高效且低延迟的解决方案。本文将详细

WPF与Qt进程间通信:WM_COPYDATA消息实战

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

在Windows桌面开发中,实现WPF(C#)与Qt(C++)程序间的实时数据交互是常见需求。由于两者均运行于Windows平台,利用Win32 API中的WM_COPYDATA消息进行进程间通信,是一种简单、高效且低延迟的解决方案。本文将详细介绍如何通过WM_COPYDATA实现WPF与Qt之间的双向消息互传。

长期稳定更新的攒劲资源: >>>点此立即查看<<<

WM_COPYDATA消息的核心原理

WM_COPYDATA是Windows系统专为进程间传递只读数据设计的消息。其核心是COPYDATASTRUCT数据结构,该结构定义了传递数据的格式。

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

WPF端实现:接收与发送消息

WPF框架封装了底层消息循环,需要通过HwndSource挂载钩子来监听原生Windows消息。

WPF接收消息:挂载WndProc钩子

在窗口初始化完成后,获取窗口句柄并添加消息监听钩子。

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;
}

WPF发送消息:查找Qt窗口并发送

WPF端需先定位目标Qt窗口句柄,然后组装数据并发送WM_COPYDATA消息。

[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) {
    IntPtr targetHwnd = FindWindow(null, "Qt子程序");
    if (targetHwnd == IntPtr.Zero) return;

    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);

    SendMessage(targetHwnd, 0x004A, IntPtr.Zero, ref cds);
    Marshal.FreeHGlobal(cds.lpData);
}

Qt端实现:接收与发送消息

Qt框架通过重写nativeEvent函数可以方便地拦截和处理Windows原生消息。

Qt接收消息:重写nativeEvent函数

在QMainWindow或QWidget的子类中实现nativeEvent以处理WM_COPYDATA。

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);
        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);
}

Qt发送消息:枚举窗口与模糊匹配

Qt端可以使用EnumWindows函数进行窗口遍历,实现更灵活的窗口标题模糊匹配。

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);
    }
}

WM_COPYDATA通信关键点与注意事项

编码一致性:

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

WPF与Qt进程间通信方案拓展

在跨技术栈的桌面应用开发中,常需WPF(.NET)与Qt(C++)进程协同,例如WPF负责主界面与业务逻辑,Qt负责高性能图形渲染。此时需要高效稳定的进程间通信机制。

以下对比几种常见的IPC方式及其在WPF与Qt间的实现。

主流IPC方式对比

IPC 方式优点缺点适用场景
命名管道 (Named Pipe)Windows原生高性能,支持双向流式通信主要限于Windows平台Windows平台首选,WPF与Qt支持良好
TCP Socket (本地回环)完全跨平台,代码通用需处理粘包、连接管理,性能稍逊需要跨平台或已有网络编程经验
共享内存 (Shared Memory)吞吐量最大,延迟最低需手动同步,数据格式复杂实时视频流、大数据块传输
消息队列 (MSMQ等)解耦、持久化、支持广播重量级,引入中间件,延迟高异步任务、高可靠性场景
COM/DCOMWindows深度集成学习曲线陡峭,配置复杂遗留系统集成,新项目不推荐

方案组合推荐:

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

命名管道实现(Windows平台推荐)

WPF端(C#)服务端示例:

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);
        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);
        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++)客户端示例:

#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() << "收到:" << recvData;
    socket.disconnectFromServer();
    return 0;
}

注意:在Windows上,Qt的QLocalSocket底层即命名管道,因此管道名直接使用“MyPipe”即可,无需“\\.\pipe\”前缀。

TCP Socket实现(跨平台方案)

WPF端(C#)TCP服务端示例:

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);
        string reply = "Hello from WPF";
        byte[] replyData = Encoding.UTF8.GetBytes(reply);
        await stream.WriteAsync(replyData, 0, replyData.Length);
    }
}

Qt端(C++)TCP客户端示例:

#include 
#include 
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;
}

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。