C# Winform 多个程序之间的通信(非Scoket)

news/2025/2/19 16:53:27/

效果

功能:打开窗体自动连接主程序,并自动添加到列表,可以向子程序群发消息

可以向单个程序单独发送消息

在退出程序后,添加的程序列表会自动移除

一、概述

参考: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


http://www.ppmy.cn/news/312831.html

相关文章

数据治理|数据资产中心

01 前言 我们来聊聊数据治理最最核心的部分——数据资产治理&#xff0c;本文主要阐述数据资产治理的策略和工具建设思路。 02 基本概念 广义的数据资产涵盖一切非结构化、半结构化和结构化数据&#xff0c;狭义的数据资产主要包括业务侧的业务日志、流数据的topic、批数据…

USB转TTL模块连接ESP8266ex m1n1

m1n1引脚说明 USB转TTL模块 连接ESP16个针脚中的4个就可以了&#xff0c;分别是G、3V3、TX、RX&#xff0c;将针脚分别连接到USB转TTL模块&#xff0c;TX接RXD&#xff0c;RX接TXD&#xff0c;3v3与3v3&#xff0c;G接GND。 注意&#xff1a;模块使用的电源是3.3V的&#xff0…

N1烧USB供电跳线修复方法

转自恩山&#xff1a;https://www.right.com.cn/forum/thread-480569-2-1.html 转载于:https://www.cnblogs.com/wxfy/p/10775646.html

诺基亚N1 WIFI感叹号消除

Android Captive Portal Server 安卓系统wifi连接后&#xff0c;与CPS服务器通信&#xff0c;检测是否连接到互联网&#xff0c;当不能默认的CPS时就会有感叹号。 通过ADB设置可以消除这个感叹号。 下好ADB之后&#xff0c;打开ADB文件夹&#xff0c;按住shift鼠标右键&…

PHICOMM(斐讯)N1盒子 - Armbian5.77(Debian 9)配置自动连接WIFI无线网络

PHICOMM(斐讯)N1盒子 - Armbian5.77(Debian 9)配置自动连接WIFI无线网络 如需转载请标明出处&#xff1a;http://blog.csdn.net/itas109 技术交流&#xff1a;129518033 文章目录 PHICOMM(斐讯)N1盒子 - Armbian5.77(Debian 9)配置自动连接WIFI无线网络前言1. 配置WIFI无线网络…

高恪或者Padavan等品牌路由用N1作为旁路由

经常逛论坛发现很多朋友对于旁路由的设置还不是很清楚&#xff0c;折腾了几天都无功而返&#xff0c;所以就整理了一下设置的步骤&#xff0c;给需要的人。 一、设备如下 K2P主路由(高恪、Padavan) IP地址&#xff1a;192.168.0.1 N1旁路由&#xff08;Openwrt&#xff09; I…

armbian 斐讯n1_记录一下斐讯N1盒子刷Armbian的各种坑

最近搞了一个斐讯N1盒子,准备拿来刷Armbian,也就是linux。 armbian使用了AArch64架构,也就是说,是armv8,安装nodejs时注意下载armv8的版本。 斐讯N1盒子自带的系统是安卓系统,版本是V2.2, 刷机主要参考的教程在这里:https://www.uselys.com/archives/85.html 第一步,需…

N1 armbian打造家庭NAS、下载机、aliyun-webdav

1. 自动挂载硬盘 使用lsblk命令查看磁盘设备 使用blkid命令查询硬盘的uuid 创建挂载目录mkdir -p /data 修改配置开机自动挂载vim /etc/fstab 新增一行UUIDf6e23bcb-983f-1d4e-bea1-36bae2a72c33 /data ext4 defaults 0 0 立即挂载mount -a 2. 安装samba文件共享服务 使…