在开发中,我们有时需要让不同的框架程序(如 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/dcom | windows 深度集成,可远程调用对象 | 学习曲线陡峭,配置复杂,易出问题 | 老旧系统集成,不推荐新项目 |
推荐组合:
- 一般控制指令、小数据量 → 命名管道(简单可靠)
- 大量数据(如实时视频) → 共享内存 + 命名管道(同步信号)
- 需要跨平台 → 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进程间通信内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论