【C#】C# Winform 多个程序之间的通信(非Scoket)
效果
功能:打开窗体自动连接主程序,并自动添加到列表,可以向子程序群发消息
可以向单个程序单独发送消息
在退出程序后,添加的程序列表会自动移除
一、概述
参考:C# Winfrom程序之间通讯_c# sendmessege copydatastruct 返回多个值_熊思宇的博客-CSDN博客
在之前我写过 winform 程序与程序之间的通信,但是这个版本有个问题,那就是只能由两个程序进行通信,同时打开多个程序的话,接收方收到的数据就会一模一样,这次发表这个教程,也就是要解决这个问题。
归根结底,还是 FindWindow 这个函数的用法没用对,下面是对应的解释:
函数获得一个顶层窗体的句柄,该窗体的类名和窗体名与给定的字符串相匹配。这个函数不查找子窗体。在查找时不区分大写和小写。
函数原型
int FindWindow(string lpClassName, string lpWindowName);
在测试中,我发现,如果用 FindWindow 这个函数去寻找对应的窗体,如果哪个窗体打开了多个,那么每个窗体的句柄就是一样的,解决这个问题也很简单,不用就行了。
有人可能会问,这种通信方式有什么用呢?主要用途当然是通信啦,因为使用 Scoket 通信有一定的难度,TCP 协议写起来也比较复杂,网上的资料也少,并且都很基础,程序运行一段时间就会自动退出,或者自动掉线,这个是很常见的事。
二、实现需求
新建一个 .Net Framework 的 Winform 项目,这次实现一个主程序和多个子程序通信的案例。
主程序的界面
后面源码我会提供,先可以不用管这些控件的具体参数
Form1 代码
using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Windows.Forms; namespace 程序之间的通信 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } #region 字段 public struct CopyDataStruct { public IntPtr dwData; public int cbData; [MarshalAs(UnmanagedType.LPStr)] public string lpData; } //当一个应用程序传递数据给另一个应用程序时发送此消息指令 public const int WM_COPYDATA = 0x004A; //在DLL库中的发送消息函数 [DllImport("User32.dll", EntryPoint = "SendMessage")] private static extern int SendMessage(int hWnd, int Msg, int wParam, ref CopyDataStruct lParam); /// <summary> /// 句柄列表 /// </summary> List<int> IntPtrList = new List<int>(); #endregion #region 窗体相关 private void Form1_Load(object sender, EventArgs e) { TextBox_IntPtr.Text = this.Handle.ToString(); } #endregion #region 按钮相关 /// <summary> /// 发送 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Button_Send_Click(object sender, EventArgs e) { if (IntPtrList.Count == 0) { Console.WriteLine("句柄列表为空"); return; } //将文本框中的值, 发送给接收端 string message = TextBox_Message.Text; if (string.IsNullOrEmpty(message)) { Console.WriteLine("消息输入框不能为空"); return; } CopyDataStruct cds; cds.dwData = (IntPtr)1; //这里可以传入一些自定义的数据,但只能是4字节整数 cds.lpData = message; //消息字符串 cds.cbData = System.Text.Encoding.Default.GetBytes(message).Length + 1; //注意,这里的长度是按字节来算的 //这里要修改成接收窗口的标题 “DownloadClient” //SendMessage(FindWindow(null, "DownloadClient"), WM_COPYDATA, 0, ref cds); if (radioButton1.Checked) { for (int i = 0; i < IntPtrList.Count; i++) { SendMessage(IntPtrList[i], WM_COPYDATA, 0, ref cds); } return; } if (radioButton2.Checked) { string sIntptr = TextBox_SingleIntptr.Text; if (string.IsNullOrEmpty(sIntptr)) { Console.WriteLine("单个窗体的句柄不能为空"); return; } int ptr = 0; if (!int.TryParse(sIntptr, out ptr)) { Console.WriteLine("你输入的不是一个句柄:" + sIntptr); return; } SendMessage(ptr, WM_COPYDATA, 0, ref cds); } } /// <summary> /// 拷贝 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Button_Copy_Click(object sender, EventArgs e) { string content = TextBox_IntPtr.Text; Clipboard.SetText(content); } /// <summary> /// 粘贴 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Button_Paste_Click(object sender, EventArgs e) { TextBox_IntPtrCon.Text = (string)Clipboard.GetDataObject().GetData(DataFormats.Text); } /// <summary> /// 添加句柄 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Button_Add_Click(object sender, EventArgs e) { string ptr = TextBox_IntPtrCon.Text; if (string.IsNullOrEmpty(ptr)) { Console.WriteLine("TextBox_IntPtrCon 输入框不能为空"); return; } int intPtr = 0; if (!int.TryParse(ptr, out intPtr)) { Console.WriteLine("你输入的不是一个句柄:" + ptr); return; } AddIntPtr(intPtr); } /// <summary> /// 拷贝按钮2 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Button_Paste2_Click(object sender, EventArgs e) { TextBox_SingleIntptr.Text = (string)Clipboard.GetDataObject().GetData(DataFormats.Text); } #endregion #region 消息相关 protected override void WndProc(ref System.Windows.Forms.Message e) { if (e.Msg == WM_COPYDATA) { CopyDataStruct cds = (CopyDataStruct)e.GetLParam(typeof(CopyDataStruct)); string message = cds.lpData.ToString(); MessageHandle(message); } base.WndProc(ref e); } private void MessageHandle(string message) { if (string.IsNullOrEmpty(message)) return; string[] arr = message.Split(','); if (arr == null || arr.Length == 0) { Console.WriteLine("分割数组为空"); return; } if (arr.Length != 3) { Console.WriteLine("分割的数组长度必须为3"); return; } int cmd = 0; if (!int.TryParse(arr[0], out cmd)) { Console.WriteLine("消息头无法转化为int类型"); return; } //句柄的转换 string sptr = arr[1]; int ptr = 0; if (!int.TryParse(sptr, out ptr)) { Console.WriteLine("句柄转换int类型错误"); return; } switch (cmd) { case CMD.通知主程序当前句柄: AddIntPtr(ptr); break; case CMD.退出程序: QuitHandle(ptr); break; default: break; } } #endregion #region 其他 /// <summary> /// 添加日志 /// </summary> /// <param name="content"></param> private void AddLog(string content) { //读取当前ListBox列表长度 int len = listBox1.Items.Count; //插入新的一行 listBox1.Items.Insert(len, content); //列表长度大于30,那么就删除第1行的数据 //if (len > 30) // listBox1.Items.RemoveAt(0); //插入新的数据后,将滚动条移动到最下面 //int visibleItems = listBox1.ClientSize.Height / listBox1.ItemHeight; //listBox1.TopIndex = Math.Max(listBox1.Items.Count - visibleItems + 1, 0); } /// <summary> /// 添加句柄 /// </summary> /// <param name="ptr"></param> private void AddIntPtr(int ptr) { if (IntPtrList.Contains(ptr)) { Console.WriteLine("当前句柄已经添加完成"); return; } IntPtrList.Add(ptr); AddLog(ptr.ToString()); } /// <summary> /// 客户端退出处理 /// </summary> /// <param name="ptr"></param> private void QuitHandle(int ptr) { if (IntPtrList.Contains(ptr)) { IntPtrList.Remove(ptr); listBox1.Items.Clear(); for (int i = 0; i < IntPtrList.Count; i++) { int len = listBox1.Items.Count; listBox1.Items.Insert(len, IntPtrList[i]); } } } #endregion } }
子程序的界面
对应的代码
using System; using System.Runtime.InteropServices; using System.Windows.Forms; namespace 程序通信接收端 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } #region 字段 //WM_COPYDATA消息所要求的数据结构 public struct CopyDataStruct { public IntPtr dwData; public int cbData; [MarshalAs(UnmanagedType.LPStr)] public string lpData; } //当一个应用程序传递数据给另一个应用程序时发送此消息指令 private int WM_COPYDATA = 0x004A; //通过窗口的标题来查找窗口的句柄 [DllImport("User32.dll", EntryPoint = "FindWindow")] private static extern int FindWindow(string lpClassName, string lpWindowName); //在DLL库中的发送消息函数 [DllImport("User32.dll", EntryPoint = "SendMessage")] private static extern int SendMessage(int hWnd, int Msg, int wParam, ref CopyDataStruct lParam); //主程序的句柄 private int MainPtr = 0; #endregion #region 界面相关 private void Form1_Load(object sender, EventArgs e) { TextBox_IntPtr.Text = this.Handle.ToString(); MainPtr = FindWindow(null, "CNCMain"); //给主程序发送当前的句柄 SendToMainMessage(CMD.通知主程序当前句柄); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { SendToMainMessage(CMD.退出程序); } #endregion #region 按钮点击事件 private void Button_Copy_Click(object sender, EventArgs e) { string content = TextBox_IntPtr.Text; Clipboard.SetText(content); } #endregion #region 消息相关 protected override void WndProc(ref System.Windows.Forms.Message e) { if (e.Msg == WM_COPYDATA) { CopyDataStruct cds = (CopyDataStruct)e.GetLParam(typeof(CopyDataStruct)); string message = cds.lpData.ToString(); TextBox_Message.Text = message; } base.WndProc(ref e); } /// <summary> /// 给主程序发送消息 /// </summary> /// <param name="cmd"></param> /// <param name="content"></param> private void SendToMainMessage(int cmd, string content = null) { //MainPtr = FindWindow(null, "CNCMain"); if (MainPtr <= 0) { Console.WriteLine("CNCMain程序为找到"); return; } if (content == null) content = string.Empty; string message = string.Format("{0},{1},{2}", cmd, this.Handle, content); CopyDataStruct cds; cds.dwData = (IntPtr)1; //这里可以传入一些自定义的数据,但只能是4字节整数 cds.lpData = message; //消息字符串 cds.cbData = System.Text.Encoding.Default.GetBytes(message).Length + 1; SendMessage(MainPtr, WM_COPYDATA, 0, ref cds); } #endregion } }
由于主程序目前只有一个,所以这里用的是 FindWindow 方法,这样大致的功能就完成了。
通信的命令:
internal class CMD { public const int 通知主程序当前句柄 = 1001; public const int 退出程序 = 1002; }
运行后,效果如文章开头的 gif 图片
源码:点击下载
如果当前的文章对你有所帮助,欢迎点赞 + 留言,有疑问也可以私信,谢谢。
end
猜你喜欢
- 【C#】Winform NanUI 0.77版本 读取嵌入式资源
- 引入NanUI框架这三个组件都要引入了,NetDimension.NanUI.AssemblyResourceHandler 是属于嵌入式资源部分,下载地址:由于作者已经废弃了这个版本,在VS2019中的 NuGet 程序包 中已经下载不了,我这里上传了,有需要的可以点击下面链接下载NanUI.AssemblyResourceHandler.0.7.4 下载 另外,NanUI.AssemblyResourceHandler 源码github地址:GitHub - maxjov
- 【C#】Winform NanUI 0.88版本 用官方源码搭建原生态开发环境
- 目录一、需求二、搭建原生开发环境1.导入源码2.解决源码报错错误1错误23.导入其他项目4.官方Demo运行效果三、创建自己的NanUI项目1.新建项目2.导入NanUI.Runtime扩展包3.添加NanUI程序集的引用4.MainIndex主界面相关代码5.Program程序入口相关代码6.读取本地前端文件的处理四、测试项目效果五、结束一、需求NanUI 插件确实很方便,但想改其中的需求怎么办,下面就来自己搭建NanUI 原生开发环境,在此很感谢作者免费的开源。官方源码地址:GitHub -
- 【C#】Winform NanUI 0.77版本 读取本地资源(扩展功能)
- 一、前言在NanUI官方的文档中,原本是有一个NanUI.FileResourceHandler的扩展包的,但现在官方已经无法下载了,现在只有0.88版本中有一个NanUI.LocalFileResource程序包,而0.77版本只剩下了一个读取嵌入式资源的程序包。关于NanUI:NanUI | .Net/.Net Core界面组件NanUI 0.7版正式发布 - 林选臣 - 博客园在扩展功能之前,请参考[资源处理器]-04 自定义资源处理器 - 知乎 ,我参考这个帖子进行扩展的,也不
- 【C#】C# Winform Label 控件
- 目录一、概述二、基本用法1.控件内容显示2.控件的外观3.自定义控件的大小4.控件的内边距 5.设置文本的固定位置6.控件的事件结束一、概述Label 控件是 winform 开发中最常用的一个控件,一般用做显示文本,也有时用做打开链接等操作。二、基本用法新建一个 winform 项目,点击 form1 界面,找到工具箱,在工具箱里找到 Label ,拖入到界面即可。1.控件内容显示label 拖入界面中,如下,单击在属性界面就能看到具体的控件属性在这里有两个重要的属性:1.Name在
- 【C#】Winform NanUI 相关功能整合
- 目录NanUI 0.88版本 去掉启动界面(遮罩)NanUI 0.88版本 读取本地资源和嵌入式资源NanUI 0.77版本 打开控制台NanUI 0.77版本 C#调用多个参数的JS方法NanUI 0.77版本 传递数组参数NanUI 0.77版本 设置窗体全屏显示(类似Kiosk模式)NanUI 0.77版本 Bootstrap类 APINanUI 0.88版本 去掉启动界面(遮罩)启动界面是作者给我们显示公司产品的Logo用的,如果用不着可以去掉,但必须要更改源码,如果不想改源码,直接将启
- 【C#】C# Winform DataGridView 数据刷新问题
- 目录一、问题二、创建项目三、绑定空的数据源四、绑定有数据的数据源五、修改绑定的数据源六、解决数据源刷新问题七、解决刷新数据界面闪烁一、问题DataGridView 是比较常用的表格控件,在 DataGridView 中显示数据, 一般使用 dataGridView1.DataSource = 数据源,来绑定数据,数据源可以是 DataTable、List、Dictionary 等,那么如何做到及时刷新数据呢,这里我提出几个问题:1.绑定一个空的数据源,后面向数据源添加数据。2.Data
- 【C#】C#md5加密
- using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace mdstr { internal
- 【C#】C# System.Windows.Forms.DataVisualization Demo案例
- 简介DataVisualization 其实就是Winform 中自带的 Chart 控件,整个图形控件主要由以下几个部份组成:1.Annotations --图形注解集合2.ChartAreas --图表区域集合3.Legends --图例集合4.Series --图表序列集合(即图表数据对象集合)5.Titles --图标的标题集合每个集合具体介绍,可以参考下面的帖子,看完了介绍,一定对你理解这个插件