您的位置:首页 >WPF通过 WM_COPYDATA 实现与Qt的进程间通信
发布于2026-04-21 阅读(0)
扫一扫,手机访问

接下来,我们将详细介绍如何通过 WM_COPYDATA 实现 WPF 与 Qt 之间的双向消息互传。
WM_COPYDATA 是 Windows 提供的一个用于进程间传递只读数据的消息。其核心数据结构如下:
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT {
public IntPtr dwData; // 自定义标识符
public int cbData; // 数据大小(字节)
public IntPtr lpData; // 指向数据的指针
}
WPF 默认封装了底层的 Win32 消息循环,因此我们需要通过 HwndSource 来挂载钩子(Hook)监听原始消息。
在 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;
}
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);
}
Qt 通过重写 nativeEvent 函数可以非常方便地截获 Windows 原生消息。
在 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);
}
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);
}
}
编码统一性:
这里有个细节需要特别注意:WPF 接收端代码中使用了 Marshal.PtrToStringAnsi,如果 Qt 发送的是中文字符,建议将 WPF 接收端也改为 UTF8 转换,否则很容易出现乱码问题。
窗口标题: WM_COPYDATA 依赖句柄。如果窗口标题在运行时会改变,建议使用更稳定的查找方式(如类名查找)。
内存安全:
WM_COPYDATA 在 SendMessage 返回前,发送端的数据内存必须保持有效。
在 WPF 端,使用 Marshal.AllocHGlobal 申请的内存必须在使用完后通过 Marshal.FreeHGlobal 手动释放,防止内存泄漏。
同步性: SendMessage 是阻塞的。如果接收端处理逻辑非常耗时,会导致发送端 UI 界面卡死。建议接收端收到消息后通过异步方式(如 WPF 的 Dispatcher.BeginInvoke 或 Qt 的信号槽)进行后续处理。
在跨技术栈的桌面应用开发中,常常需要让 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 深度集成,可远程调用对象 | 学习曲线陡峭,配置复杂,易出问题 | 老旧系统集成,不推荐新项目 |
推荐组合:
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#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; }
注意: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#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; }
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
9