您的当前位置:首页>全部文章>文章详情

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

CrazyPanda发表于:2024-02-03 22:30:47浏览:295次TAG:


效果

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

01174706_63da351a96fff24847.gif

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

01174706_63da351a96fff24847.gif

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

01174706_63da351a96fff24847.gif



一、概述

参考:C# Winfrom程序之间通讯_c# sendmessege copydatastruct 返回多个值_熊思宇的博客-CSDN博客

在之前我写过 winform 程序与程序之间的通信,但是这个版本有个问题,那就是只能由两个程序进行通信,同时打开多个程序的话,接收方收到的数据就会一模一样,这次发表这个教程,也就是要解决这个问题。

归根结底,还是 FindWindow 这个函数的用法没用对,下面是对应的解释:

函数获得一个顶层窗体的句柄,该窗体的类名和窗体名与给定的字符串相匹配。这个函数不查找子窗体。在查找时不区分大写和小写。
函数原型

int FindWindow(string lpClassName, string lpWindowName);

在测试中,我发现,如果用 FindWindow 这个函数去寻找对应的窗体,如果哪个窗体打开了多个,那么每个窗体的句柄就是一样的,解决这个问题也很简单,不用就行了。

有人可能会问,这种通信方式有什么用呢?主要用途当然是通信啦,因为使用 Scoket 通信有一定的难度,TCP 协议写起来也比较复杂,网上的资料也少,并且都很基础,程序运行一段时间就会自动退出,或者自动掉线,这个是很常见的事。


二、实现需求

新建一个 .Net Framework 的 Winform 项目,这次实现一个主程序和多个子程序通信的案例。

主程序的界面

1.png

后面源码我会提供,先可以不用管这些控件的具体参数

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
    }
}

子程序的界面

1.png

对应的代码

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 多个程序之间的通信(非Scoket)_winform程序通信-CSDN博客

猜你喜欢

【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用的,如果用不着可以去掉,但必须要更改源码,如果不想改源码,直接将启
发表于:2024-02-09 浏览:372 TAG:
【C#】C#超急速举例入门-适用有C/C++语言基础
前提编程环境:vs2022电脑系统:win10学习目的:能看懂c#,不纠结各种细节,快速适应开发。入门篇程序结构变量类型类似c语言,不掌握细节,int,float,double都有。输入输出Console.WriteLine(&quot;变量0:{0}&quot;,&nbsp;para0); var&nbsp;a=Console.ReadLine();&nbsp;运算符几乎相同。sizeof();typeof();取地址,取值:&amp;,*;三元运算符: ? :判断类型:is强制转换:as。注
发表于:2024-01-30 浏览:340 TAG:
【C#】从零开始用C#写一个桌面应用程序(一)基础操作
准备winform应用程序编写桌面应用客户端的技术。xaml一种标记语言。winform程序组成。&nbsp;程序入口:&nbsp;form.cs和它的设计文件:&nbsp;&nbsp;启动的过程以及涉及的文件:main函数:&nbsp;form1的构造函数和它的设计文件:&nbsp;&nbsp;&nbsp; main-》构造form-》initializeComponent-》&nbsp;拖入一个 button控件可以看到:&nbsp;这时我们已经梳理启动过程。使用组件的方法&nbsp;可以在
发表于:2024-01-30 浏览:486 TAG:
【C#】C#md5加密
using&nbsp;System; using&nbsp;System.Collections.Generic; using&nbsp;System.Linq; using&nbsp;System.Security.Cryptography; using&nbsp;System.Text; using&nbsp;System.Threading.Tasks; &nbsp; namespace&nbsp;mdstr { &nbsp;&nbsp;&nbsp;&nbsp;internal&nbsp;
发表于:2024-01-30 浏览:267 TAG:
【C#】C# Winform DataGridView 数据刷新问题
目录一、问题二、创建项目三、绑定空的数据源四、绑定有数据的数据源五、修改绑定的数据源六、解决数据源刷新问题七、解决刷新数据界面闪烁一、问题DataGridView 是比较常用的表格控件,在 DataGridView 中显示数据,&nbsp;一般使用 dataGridView1.DataSource = 数据源,来绑定数据,数据源可以是 DataTable、List、Dictionary 等,那么如何做到及时刷新数据呢,这里我提出几个问题:1.绑定一个空的数据源,后面向数据源添加数据。2.Data
发表于:2024-02-05 浏览:345 TAG:
【C#】C# Winform 文本面板带滚动条
在PC软件开发中经常有这样的需求,需要在一个固定大小的面板中显示一些内容,并且面板能上下拖动,将所有的内容完整的展示,有点类似网页上看新闻,如果要在 winfrom 中要如何实现的呢,下面就演示如何实现的吧效果:1.新建一个winform 项目,在界面中拖入一个Panel&nbsp;将 panel1 的&nbsp;AutoScroll 设置为 True2.再次拖入一个&nbsp;Panel ,将高度拉长,这时就自动出现了滚动条,只是此时里面还没有任何内容,下面就在 panel2 中加入一点内容。
发表于:2024-02-03 浏览:323 TAG:
【C#】C# Winform 相册功能,图片缩放,拖拽,预览图分页
一、前言在一些项目中也会用到预览图片的功能,至于为什么有一个添加图片的按钮,是因为有些项目,比如视觉相关的项目,摄像头拍摄图片,然后显示在界面上,拍一次显示一张。另一个,就是分页功能,当预览图位置不够用时就会用到。当前软件的功能1.添加图片如果8个预览图都满了,会自动分页,就可以点击上一页,或者下一页了。2.点击预览图显示大图点击预览图,之前的拖拽和放大会自动复位3.大图可以拖拽,放大,缩小如果图片比较小,有这个功能就看到图片的更多细节了。4.图片倒序排列最后拍摄的图片,始终显示在前面,方便用户
发表于:2024-02-02 浏览:313 TAG:
【C#】从零开始用C#写一个桌面应用程序(二)一个表白程序
恶搞表白小程序如何添加按钮,如何添加属性和事件请移步前文。基础操作第一步:新建窗口&nbsp;&nbsp;在工具箱内找到label和button,拖到form上,设置它们的size属性,autosize属性,text属性。第二步:添加事件为了实现我们的效果,需要给三个按钮分别设计点击事件,鼠标移入事件,鼠标点下事件。分别为click,mouseenter,mousedown事件。第三步:实现移动逻辑this获取form对象,clientsize获取实时大小,使用随机数值,设置对应按钮位置。将对应
发表于:2024-01-30 浏览:294 TAG:
【C#】Winform NanUI 0.77版本 读取本地资源(扩展功能)
一、前言在NanUI官方的文档中,原本是有一个NanUI.FileResourceHandler的扩展包的,但现在官方已经无法下载了,现在只有0.88版本中有一个NanUI.LocalFileResource程序包,而0.77版本只剩下了一个读取嵌入式资源的程序包。关于NanUI:NanUI | .Net/.Net Core界面组件NanUI 0.7版正式发布 - 林选臣 - 博客园在扩展功能之前,请参考[资源处理器]-04 自定义资源处理器 - 知乎&nbsp;,我参考这个帖子进行扩展的,也不
发表于:2024-02-08 浏览:327 TAG:
【C#】C# Winform 三层架构
一、介绍三层架构是 C# 桌面开发中比较常用的框架,是由&nbsp;表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)三层架构组成,目的是为了 “高内聚,低耦合”。开发人员分工更明确,将精力更专注于应用系统核心业务逻辑的分析、设计和开发,加快项目的进度,提高了开发效率,有利于项目的更新和维护工作。从三层架构可以看到,很类似于&nbsp;Web 前端开发的 MVC 框架(视图View,模型Model,控制Contorller),但本质上也有不同的地方,比如都有视图(三层中叫 UI),Mod
发表于:2024-02-03 浏览:287 TAG: