开源 C# 快速开发(十五)进程--windows消息
文章的目的为了记录使用C# 开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C# 快速开发(一)基础知识
开源 C# 快速开发(二)基础控件
开源 C# 快速开发(三)复杂控件
开源 C# 快速开发(四)自定义控件--波形图
开源 C# 快速开发(五)自定义控件--仪表盘
开源 C# 快速开发(六)自定义控件--圆环
开源 C# 快速开发(七)通讯--串口
开源 C# 快速开发(八)通讯--Tcp服务器端
开源 C# 快速开发(九)通讯--Tcp客户端
开源 C# 快速开发(十)通讯--http客户端
推荐链接:
开源 C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客
开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客
开源 C# .net mvc 开发(三)WEB内外网访问-CSDN博客
开源 C# .net mvc 开发(四)工程结构、页面提交以及显示-CSDN博客
开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客
开源 C# .net mvc 开发(六)发送邮件、定时以及CMD编程-CSDN博客
开源 C# .net mvc 开发(七)动态图片、动态表格和json数据生成-CSDN博客
开源 C# .net mvc 开发(八)IIS Express轻量化Web服务器的配置和使用-CSDN博客
开源 C# .net mvc 开发(九)websocket--服务器与客户端的实时通信-CSDN博客
本章节主要内容是:进程间 Windows 消息通信的例子。
目录:
1.源码分析
2.所有源码
3.效果演示
一、源码分析
1. MessageHelper.cs 分析
Windows API 声明
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
功能:根据类名或窗口标题查找窗口句柄
参数:
lpClassName:窗口类名,为null时按窗口标题查找
lpWindowName:窗口标题文本
返回值:找到的窗口句柄,未找到返回IntPtr.Zero
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
功能:向指定窗口发送消息
参数:
hWnd:目标窗口句柄
Msg:消息类型(如WM_COPYDATA)
wParam:附加消息参数
lParam:附加消息参数(通常指向数据结构的指针)
数据结构定义
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{public IntPtr dwData; // 自定义数据,可用于标识消息类型public int cbData; // 数据长度(字节数)public IntPtr lpData; // 指向实际数据的指针
}
作用:用于WM_COPYDATA消息的数据包装结构
LayoutKind.Sequential:确保字段在内存中按声明顺序排列
核心函数分析
SendMessageToProcess 函数
public static bool SendMessageToProcess(string windowName, string message)
{// 1. 查找目标窗口IntPtr targetWindow = FindWindow(null, windowName);// 2. 字符串编码和内存分配byte[] data = Encoding.Unicode.GetBytes(message);IntPtr pData = Marshal.AllocCoTaskMem(data.Length);Marshal.Copy(data, 0, pData, data.Length);// 3. 构建COPYDATASTRUCTCOPYDATASTRUCT cds = new COPYDATASTRUCT();cds.dwData = IntPtr.Zero; // 不使用自定义标识cds.cbData = data.Length; // 数据长度cds.lpData = pData; // 数据指针// 4. 结构体序列化到指针IntPtr pCds = Marshal.AllocCoTaskMem(Marshal.SizeOf(cds));Marshal.StructureToPtr(cds, pCds, false);// 5. 发送消息IntPtr result = SendMessage(targetWindow, WM_COPYDATA, IntPtr.Zero, pCds);// 6. 资源清理Marshal.FreeCoTaskMem(pData);Marshal.FreeCoTaskMem(pCds);return result != IntPtr.Zero;
}
执行流程:
窗口查找 → 2. 数据编码 → 3. 内存分配 → 4. 结构构建 → 5. 消息发送 → 6. 资源清理
SendCustomMessage 函数
public static bool SendCustomMessage(string windowName, string message)
{// 1. 注册系统唯一消息IDuint customMsg = (uint)RegisterWindowMessage("MyCustomMessage");// 2. 查找目标窗口IntPtr targetWindow = FindWindow(null, windowName);// 3. 字符串内存分配IntPtr pData = Marshal.StringToHGlobalUni(message);// 4. 发送自定义消息IntPtr result = SendMessage(targetWindow, customMsg, IntPtr.Zero, pData);// 5. 资源清理Marshal.FreeHGlobal(pData);return result != IntPtr.Zero;
}
特点:使用RegisterWindowMessage确保消息ID在系统范围内唯一
2. MessageReceiverForm.cs 分析
构造函数
public MessageReceiverForm()
{InitializeComponent();customMessage = (uint)RegisterWindowMessage("MyCustomMessage");this.Text = $"消息接收器 - PID: {Process.GetCurrentProcess().Id}";UpdateStatus("就绪 - 等待接收消息");
}
注册与发送方相同的自定义消息ID
在窗口标题显示进程ID便于调试
消息处理核心 - WndProc
protected override void WndProc(ref Message m)
{switch (m.Msg){case WM_COPYDATA:HandleCopyData(ref m); // 处理WM_COPYDATAbreak;case int msg when customMessage != 0 && msg == customMessage:HandleCustomMessage(ref m); // 处理自定义消息break;default:base.WndProc(ref m); // 其他消息交给基类处理break;}
}
消息路由机制:
所有Windows消息都经过此方法
根据消息类型分发给对应的处理器
未处理的消息传递给基类
WM_COPYDATA 消息处理
private void HandleCopyData(ref Message m)
{// 1. 反序列化COPYDATASTRUCTCOPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT));// 2. 从指针复制数据到字节数组byte[] data = new byte[cds.cbData];Marshal.Copy(cds.lpData, data, 0, cds.cbData);// 3. 字节数组解码为字符串string message = Encoding.Unicode.GetString(data);// 4. UI线程安全更新this.Invoke(new Action(() =>{listBoxMessages.Items.Add($"[WM_COPYDATA] {DateTime.Now:HH:mm:ss}: {message}");listBoxMessages.SelectedIndex = listBoxMessages.Items.Count - 1;UpdateStatus($"收到 WM_COPYDATA 消息: {message}");}));m.Result = (IntPtr)1; // 告知发送方处理成功
}
关键点:
Marshal.PtrToStructure:将指针转换为结构体
Marshal.Copy:从非托管内存复制到托管数组
this.Invoke:确保UI操作在正确的线程执行
自定义消息处理
private void HandleCustomMessage(ref Message m)
{// 直接从指针读取字符串string message = Marshal.PtrToStringUni(m.LParam);this.Invoke(new Action(() =>{listBoxMessages.Items.Add($"[自定义消息] {DateTime.Now:HH:mm:ss}: {message}");listBoxMessages.SelectedIndex = listBoxMessages.Items.Count - 1;UpdateStatus($"收到自定义消息: {message}");}));m.Result = (IntPtr)1;
}
简化处理:直接使用Marshal.PtrToStringUni转换字符串
UI交互函数
private void btnSendToSender_Click(object sender, EventArgs e)
{string message = txtSendMessage.Text.Trim();if (!string.IsNullOrEmpty(message)){// 双向通信:接收方也可以发送消息bool success = MessageHelper.SendMessageToProcess("消息发送器", message);if (success){listBoxMessages.Items.Add($"[发送] {DateTime.Now:HH:mm:ss}: {message}");listBoxMessages.SelectedIndex = listBoxMessages.Items.Count - 1;UpdateStatus($"已发送消息到发送器: {message}");}}
}
线程安全的状态更新
private void UpdateStatus(string status)
{if (lblStatus.InvokeRequired){lblStatus.Invoke(new Action<string>(UpdateStatus), status);}else{lblStatus.Text = $"{DateTime.Now:HH:mm:ss} - {status}";}
}
InvokeRequired机制:
检查调用线程是否是创建控件的线程
如果不是,通过Invoke跨线程安全更新UI
3. MessageSenderForm.cs 分析
消息发送函数
private void btnSendCopyData_Click(object sender, EventArgs e)
{string message = txtMessage.Text.Trim();string receiverWindow = txtReceiverWindow.Text.Trim();if (!string.IsNullOrEmpty(message) && !string.IsNullOrEmpty(receiverWindow)){bool success = MessageHelper.SendMessageToProcess(receiverWindow, message);if (success){// 本地记录发送历史listBoxSent.Items.Add($"[WM_COPYDATA] {DateTime.Now:HH:mm:ss}: {message}");listBoxSent.SelectedIndex = listBoxSent.Items.Count - 1;UpdateStatus($"WM_COPYDATA 消息发送成功: {message}");}}
}
进程启动功能
private void btnStartReceiver_Click(object sender, EventArgs e)
{// 构建接收器程序路径string receiverPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MessageReceiver.exe");if (System.IO.File.Exists(receiverPath)){Process.Start(receiverPath); // 启动外部进程UpdateStatus("接收器进程已启动");}
}
4. 通信流程总结
WM_COPYDATA 通信流程:
发送方:
1. FindWindow("消息接收器") → 获取窗口句柄
2. Encoding.Unicode.GetBytes() → 字符串转字节数组
3. Marshal.AllocCoTaskMem() → 分配非托管内存
4. Marshal.StructureToPtr() → 结构体序列化
5. SendMessage(WM_COPYDATA) → 发送消息
接收方:
1. WndProc(WM_COPYDATA) → 接收消息
2. Marshal.PtrToStructure() → 反序列化结构体
3. Marshal.Copy() → 复制数据到托管内存
4. Encoding.Unicode.GetString() → 字节数组转字符串
5. UI更新 → 显示接收到的消息
二、所有源码
项目源码由2部分组成,windowsmessageRecv和windowsmessageSend
windowsmessageRecv中包括MessageHelper.cs和MessageReceiverForm。
windowsmessageSend中包括MessageHelper.cs和MessageSenderForm。
MessageHelper.cs文件源码
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;namespace windowsmessageRecv
{public static class MessageHelper{// Windows API[DllImport("user32.dll", SetLastError = true)]public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);[DllImport("user32.dll", SetLastError = true)]public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);[DllImport("user32.dll")]public static extern int RegisterWindowMessage(string lpString);[DllImport("user32.dll")]public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, string lParam);// 常量public const int WM_COPYDATA = 0x004A;public const int WM_SETTEXT = 0x000C;// COPYDATASTRUCT 结构[StructLayout(LayoutKind.Sequential)]public struct COPYDATASTRUCT{public IntPtr dwData;public int cbData;public IntPtr lpData;}public static bool SendMessageToProcess(string windowName, string message){try{// 查找接收窗口IntPtr targetWindow = FindWindow(null, windowName);if (targetWindow == IntPtr.Zero){MessageBox.Show($"未找到目标窗口: {windowName}");return false;}// 准备数据byte[] data = Encoding.Unicode.GetBytes(message);IntPtr pData = Marshal.AllocCoTaskMem(data.Length);Marshal.Copy(data, 0, pData, data.Length);// 创建 COPYDATASTRUCTCOPYDATASTRUCT cds = new COPYDATASTRUCT();cds.dwData = IntPtr.Zero;cds.cbData = data.Length;cds.lpData = pData;IntPtr pCds = Marshal.AllocCoTaskMem(Marshal.SizeOf(cds));Marshal.StructureToPtr(cds, pCds, false);// 发送消息IntPtr result = SendMessage(targetWindow, WM_COPYDATA, IntPtr.Zero, pCds);// 清理资源Marshal.FreeCoTaskMem(pData);Marshal.FreeCoTaskMem(pCds);return result != IntPtr.Zero;}catch (Exception ex){MessageBox.Show($"发送消息时出错: {ex.Message}");return false;}}public static bool SendCustomMessage(string windowName, string message){try{// 注册自定义消息uint customMsg = (uint)RegisterWindowMessage("MyCustomMessage");if (customMsg == 0){MessageBox.Show("注册自定义消息失败");return false;}IntPtr targetWindow = FindWindow(null, windowName);if (targetWindow == IntPtr.Zero){MessageBox.Show($"未找到目标窗口: {windowName}");return false;}// 发送字符串消息IntPtr pData = Marshal.StringToHGlobalUni(message);IntPtr result = SendMessage(targetWindow, customMsg, IntPtr.Zero, pData);Marshal.FreeHGlobal(pData);return result != IntPtr.Zero;}catch (Exception ex){MessageBox.Show($"发送自定义消息时出错: {ex.Message}");return false;}}public static bool SendTextMessage(string windowName, string message){try{IntPtr targetWindow = FindWindow(null, windowName);if (targetWindow == IntPtr.Zero){MessageBox.Show($"未找到目标窗口: {windowName}");return false;}IntPtr result = SendMessage(targetWindow, WM_SETTEXT, IntPtr.Zero, message);return result != IntPtr.Zero;}catch (Exception ex){MessageBox.Show($"发送文本消息时出错: {ex.Message}");return false;}}}
}
MessageReceiverForm.cs文件源码
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;namespace windowsmessageRecv
{public partial class MessageReceiverForm : Form{// Windows API 常量private const int WM_COPYDATA = 0x004A;// 注册自定义消息[DllImport("user32.dll")]private static extern int RegisterWindowMessage(string lpString);private uint customMessage;private ListBox listBoxMessages;private Button btnClear;private Button btnStartSender;private Label lblStatus;private TextBox txtSendMessage;private Button btnSendToSender;private Process senderProcess;public MessageReceiverForm(){InitializeComponent();// 注册自定义消息customMessage = (uint)RegisterWindowMessage("MyCustomMessage");// 显示当前进程ID,方便调试//this.Text = $"消息接收器 - PID: {Process.GetCurrentProcess().Id}";this.Text = "消息接收器";UpdateStatus("就绪 - 等待接收消息");}private void InitializeComponent(){this.listBoxMessages = new ListBox();this.btnClear = new Button();this.btnStartSender = new Button();this.lblStatus = new Label();this.txtSendMessage = new TextBox();this.btnSendToSender = new Button();this.SuspendLayout();// listBoxMessagesthis.listBoxMessages.Dock = DockStyle.Top;this.listBoxMessages.FormattingEnabled = true;this.listBoxMessages.Location = new Point(0, 0);this.listBoxMessages.Size = new Size(500, 200);this.listBoxMessages.BackColor = Color.LightYellow;// lblStatusthis.lblStatus.Dock = DockStyle.Top;this.lblStatus.Location = new Point(0, 200);this.lblStatus.Size = new Size(500, 25);this.lblStatus.BackColor = Color.LightBlue;this.lblStatus.TextAlign = ContentAlignment.MiddleCenter;// txtSendMessagethis.txtSendMessage.Location = new Point(10, 235);this.txtSendMessage.Size = new Size(300, 20);this.txtSendMessage.Text = "这是来自接收器的回复消息";// btnSendToSenderthis.btnSendToSender.Location = new Point(320, 235);this.btnSendToSender.Size = new Size(80, 23);this.btnSendToSender.Text = "发送回复";this.btnSendToSender.Click += new EventHandler(this.btnSendToSender_Click);// btnStartSenderthis.btnStartSender.Location = new Point(410, 235);this.btnStartSender.Size = new Size(80, 23);this.btnStartSender.Text = "启动发送器";this.btnStartSender.Click += new EventHandler(this.btnStartSender_Click);// btnClearthis.btnClear.Location = new Point(210, 265);this.btnClear.Size = new Size(80, 23);this.btnClear.Text = "清空消息";this.btnClear.Click += new EventHandler(this.btnClear_Click);// MessageReceiverFormthis.ClientSize = new Size(500, 300);this.Controls.Add(this.btnClear);this.Controls.Add(this.btnStartSender);this.Controls.Add(this.btnSendToSender);this.Controls.Add(this.txtSendMessage);this.Controls.Add(this.lblStatus);this.Controls.Add(this.listBoxMessages);this.Text = "消息接收器";this.StartPosition = FormStartPosition.CenterScreen;this.ResumeLayout(false);this.PerformLayout();}// 重写 WndProc 方法来处理 Windows 消息protected override void WndProc(ref Message m){switch (m.Msg){case WM_COPYDATA:HandleCopyData(ref m);break;case int msg when customMessage != 0 && msg == customMessage:HandleCustomMessage(ref m);break;default:base.WndProc(ref m);break;}}// 处理 WM_COPYDATA 消息private void HandleCopyData(ref Message m){try{// 解析 COPYDATASTRUCTCOPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT));// 读取数据byte[] data = new byte[cds.cbData];Marshal.Copy(cds.lpData, data, 0, cds.cbData);string message = Encoding.Unicode.GetString(data);// 在 UI 线程中更新界面this.Invoke(new Action(() =>{listBoxMessages.Items.Add($"[WM_COPYDATA] {DateTime.Now:HH:mm:ss}: {message}");listBoxMessages.SelectedIndex = listBoxMessages.Items.Count - 1;UpdateStatus($"收到 WM_COPYDATA 消息: {message}");}));m.Result = (IntPtr)1; // 表示消息处理成功}catch (Exception ex){MessageBox.Show("处理 WM_COPYDATA 消息时出错: " + ex.Message);m.Result = IntPtr.Zero;}}// 处理自定义消息private void HandleCustomMessage(ref Message m){try{// 读取字符串数据string message = Marshal.PtrToStringUni(m.LParam);this.Invoke(new Action(() =>{listBoxMessages.Items.Add($"[自定义消息] {DateTime.Now:HH:mm:ss}: {message}");listBoxMessages.SelectedIndex = listBoxMessages.Items.Count - 1;UpdateStatus($"收到自定义消息: {message}");}));m.Result = (IntPtr)1;}catch (Exception ex){MessageBox.Show("处理自定义消息时出错: " + ex.Message);m.Result = IntPtr.Zero;}}private void btnClear_Click(object sender, EventArgs e){listBoxMessages.Items.Clear();UpdateStatus("消息列表已清空");}private void btnStartSender_Click(object sender, EventArgs e){try{// 启动发送器进程string senderPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"windowsmessageSend.exe");if (System.IO.File.Exists(senderPath)){senderProcess = Process.Start(senderPath);UpdateStatus("发送器进程已启动");}else{MessageBox.Show($"找不到发送器程序: {senderPath}\n请先编译 MessageSender 项目。");}}catch (Exception ex){MessageBox.Show("启动发送器失败: " + ex.Message);}}private void btnSendToSender_Click(object sender, EventArgs e){string message = txtSendMessage.Text.Trim();if (!string.IsNullOrEmpty(message)){// 发送消息到发送器窗口bool success = MessageHelper.SendMessageToProcess("消息发送器", message);if (success){listBoxMessages.Items.Add($"[发送] {DateTime.Now:HH:mm:ss}: {message}");listBoxMessages.SelectedIndex = listBoxMessages.Items.Count - 1;UpdateStatus($"已发送消息到发送器: {message}");}else{UpdateStatus($"发送失败,请确保发送器正在运行");}}}private void UpdateStatus(string status){if (lblStatus.InvokeRequired){lblStatus.Invoke(new Action<string>(UpdateStatus), status);}else{lblStatus.Text = $"{DateTime.Now:HH:mm:ss} - {status}";}}// COPYDATASTRUCT 结构定义[StructLayout(LayoutKind.Sequential)]public struct COPYDATASTRUCT{public IntPtr dwData;public int cbData;public IntPtr lpData;}}// 程序入口}
MessageSenderForm.cs文件源码
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Diagnostics;namespace windowsmessageSend
{public partial class MessageSenderForm : Form{private TextBox txtMessage;private Button btnSendCopyData;private Button btnSendCustom;private ListBox listBoxSent;private Button btnClear;private TextBox txtReceiverWindow;private Label lblStatus;private Button btnStartReceiver;public MessageSenderForm(){InitializeComponent();//this.Text = $"消息发送器 - PID: {Process.GetCurrentProcess().Id}";this.Text = "消息发送器";UpdateStatus("就绪 - 可以发送消息");}private void InitializeComponent(){this.txtMessage = new TextBox();this.btnSendCopyData = new Button();this.btnSendCustom = new Button();this.listBoxSent = new ListBox();this.btnClear = new Button();this.txtReceiverWindow = new TextBox();this.lblStatus = new Label();this.btnStartReceiver = new Button();this.SuspendLayout();// txtReceiverWindowthis.txtReceiverWindow.Location = new Point(10, 10);this.txtReceiverWindow.Size = new Size(300, 20);this.txtReceiverWindow.Text = "消息接收器";// txtMessagethis.txtMessage.Location = new Point(10, 40);this.txtMessage.Size = new Size(300, 20);this.txtMessage.Text = "这是一条测试消息";// btnSendCopyDatathis.btnSendCopyData.Location = new Point(320, 40);this.btnSendCopyData.Size = new Size(100, 23);this.btnSendCopyData.Text = "发送 WM_COPYDATA";this.btnSendCopyData.Click += new EventHandler(this.btnSendCopyData_Click);// btnSendCustomthis.btnSendCustom.Location = new Point(430, 40);this.btnSendCustom.Size = new Size(100, 23);this.btnSendCustom.Text = "发送自定义消息";this.btnSendCustom.Click += new EventHandler(this.btnSendCustom_Click);// btnStartReceiverthis.btnStartReceiver.Location = new Point(320, 10);this.btnStartReceiver.Size = new Size(100, 23);this.btnStartReceiver.Text = "启动接收器";this.btnStartReceiver.Click += new EventHandler(this.btnStartReceiver_Click);// listBoxSentthis.listBoxSent.Dock = DockStyle.Bottom;this.listBoxSent.FormattingEnabled = true;this.listBoxSent.Location = new Point(0, 120);this.listBoxSent.Size = new Size(550, 150);this.listBoxSent.BackColor = Color.LightGreen;// lblStatusthis.lblStatus.Dock = DockStyle.Bottom;this.lblStatus.Location = new Point(0, 100);this.lblStatus.Size = new Size(550, 20);this.lblStatus.BackColor = Color.LightBlue;this.lblStatus.TextAlign = ContentAlignment.MiddleCenter;// btnClearthis.btnClear.Location = new Point(230, 70);this.btnClear.Size = new Size(80, 23);this.btnClear.Text = "清空列表";this.btnClear.Click += new EventHandler(this.btnClear_Click);// MessageSenderFormthis.ClientSize = new Size(550, 270);this.Controls.Add(this.btnClear);this.Controls.Add(this.listBoxSent);this.Controls.Add(this.lblStatus);this.Controls.Add(this.btnStartReceiver);this.Controls.Add(this.btnSendCustom);this.Controls.Add(this.btnSendCopyData);this.Controls.Add(this.txtMessage);this.Controls.Add(this.txtReceiverWindow);this.Text = "消息发送器";this.StartPosition = FormStartPosition.CenterScreen;this.ResumeLayout(false);this.PerformLayout();}private void btnSendCopyData_Click(object sender, EventArgs e){string message = txtMessage.Text.Trim();string receiverWindow = txtReceiverWindow.Text.Trim();if (!string.IsNullOrEmpty(message) && !string.IsNullOrEmpty(receiverWindow)){try{bool success = MessageHelper.SendMessageToProcess(receiverWindow, message);if (success){listBoxSent.Items.Add($"[WM_COPYDATA] {DateTime.Now:HH:mm:ss}: {message}");listBoxSent.SelectedIndex = listBoxSent.Items.Count - 1;UpdateStatus($"WM_COPYDATA 消息发送成功: {message}");}else{UpdateStatus($"发送失败,请确保接收器正在运行");}}catch (Exception ex){MessageBox.Show($"发送消息失败: {ex.Message}");UpdateStatus($"发送失败: {ex.Message}");}}else{MessageBox.Show("请输入消息内容和接收窗口标题");}}private void btnSendCustom_Click(object sender, EventArgs e){string message = txtMessage.Text.Trim();string receiverWindow = txtReceiverWindow.Text.Trim();if (!string.IsNullOrEmpty(message) && !string.IsNullOrEmpty(receiverWindow)){try{bool success = MessageHelper.SendCustomMessage(receiverWindow, message);if (success){listBoxSent.Items.Add($"[自定义消息] {DateTime.Now:HH:mm:ss}: {message}");listBoxSent.SelectedIndex = listBoxSent.Items.Count - 1;UpdateStatus($"自定义消息发送成功: {message}");}else{UpdateStatus($"发送失败,请确保接收器正在运行");}}catch (Exception ex){MessageBox.Show($"发送自定义消息失败: {ex.Message}");UpdateStatus($"发送失败: {ex.Message}");}}else{MessageBox.Show("请输入消息内容和接收窗口标题");}}private void btnStartReceiver_Click(object sender, EventArgs e){try{// 启动接收器进程string receiverPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"MessageReceiver.exe");if (System.IO.File.Exists(receiverPath)){Process.Start(receiverPath);UpdateStatus("接收器进程已启动");}else{MessageBox.Show($"找不到接收器程序: {receiverPath}\n请先编译 MessageReceiver 项目。");}}catch (Exception ex){MessageBox.Show("启动接收器失败: " + ex.Message);}}private void btnClear_Click(object sender, EventArgs e){listBoxSent.Items.Clear();UpdateStatus("发送列表已清空");}private void UpdateStatus(string status){if (lblStatus.InvokeRequired){lblStatus.Invoke(new Action<string>(UpdateStatus), status);}else{lblStatus.Text = $"{DateTime.Now:HH:mm:ss} - {status}";}}}}
三、效果演示
启动接收器和发送器的exe,发送器中点击发送,接收器收到消息。