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

news2024/12/23 18:09:01

效果

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

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

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

一、概述

参考: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.coloradmin.cn/o/630965.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

x宝评论抓取

#某宝评论接口sign参数逆向 1.接口速览 多次请求发现&#xff0c;t为时间戳&#xff0c;sign为加密参数&#xff0c;盲猜和data、t有关&#xff0c;sign为32位&#xff0c;盲猜是字符串的32位的MD5 2.搜索js代码 这里为搜索的是appKey&#xff0c;就找到了sign&#xff0c;然…

如何实现APP自动化测试?

APP测试&#xff0c;尤其是APP的自动化测试&#xff0c;在软件测试工程师的面试中越来越会被问到了。为了更好的回答这个问题&#xff0c;我今天就给大家分享一下&#xff0c;如何进行APP的自动化测试。 一、为了实现JavaAppiumJunit技术用于APP自动化测试&#xff0c;所以需要…

使用AIGC工具提升论文阅读效率

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

接口测试实战工具如何选择?这6个工具首选(建议收藏)

常见接口类型 • HTTP/HTTPS 类型接口 基于HTTP协议开发的接口现在应用是最为广泛的&#xff0c;这类API使用起来简单明了&#xff0c;因为它是轻量级的、跨平台、跨语言的&#xff0c; 但凡是第三方提供的API都会有HTTP版本的接口。 RESTful API也是基于HTTP协议的&#xff0c…

Android滴滴路由框架DRouter原理解析

作者&#xff1a;linversion 前言 最近的一个新项目使用了Clean Architecture模块化MVVM架构&#xff0c;将首页每个tab对应的功能都放到单独的模块且不相互依赖&#xff0c;这时就有了模块间页面跳转的问题&#xff0c;经过一番研究选择了滴滴的DRouter&#xff0c;因为其出色…

Image captioning中自定义文本数据整理为类似Flickr8k.token.txt的格式--->助力后期生成JSON格式用于训练

手把手实现Image captioning,将自定义文本数据整理为类似Flickr8k.token.txt的格式,助力后期生成JSON格式用于训练。如果感觉有用,不妨给博主来个一键三连,白天科研,晚上肝文,实属不易~ ~ ](https://imgse.com/i/p9FmMDK) 这里写目录标题 1. 任务需求2. 程序实现2.1 读取…

vistual studio 2017中导入pthread.h的配置方法

1.下载pthread.h的相关库文件 下载路径 https://www.mirrorservice.org/sites/sourceware.org/pub/pthreads-win32/pthreads-w32-2-9-1-release.zip 加压后得到两种系统版本的三个文件夹 pthreads.2 : 包含了pthread的源文件 Pre-built2 :包含了pthreads for win32的头文件…

软件测试04:软件测试流程和软件测试过程

软件测试04&#xff1a;软件测试流程和软件测试过程 软件测试流程 软件测试流程&#xff1a;获取测试需求->编写测试计划->制造测试方案->开发与设计测试用例->执行测试->提交缺陷报告->测试分析与评审->提交测试总结->准备下一版本测试 软件测试过…

网页JS自动化脚本(九)创建一键导出数据库到桌面的功能按钮

我们获取到了数据库,当然我们希望能把这个数据库给保存到本地电脑上进行一些数据的处理,我们这一节就添加一个按钮把数据一次性导出 保存到桌面为json格式 我们直接用TXT打开它如下图所示 然后再使用json转EXCEL的小工具规整之后如下图 好了我们下面上代码 // UserScript // n…

算法提高-图论-单源最短路的综合应用

单源最短路的综合应用 单源最短路的综合应用AcWing 1135. 新年好AcWing 340. 通信线路AcWing 342. 道路与航线AcWing 341. 最优贸易 单源最短路的综合应用 AcWing 1135. 新年好 多次dijkstra求每个点到其它点的最短距离&#xff0c; 此时相当于建好了一张图&#xff0c;每个点…

http客户端Fegin

1.RestTemplate方式调用存在的问题 代码可读性差&#xff0c;编程体验不统一 参数复杂URL难以维护 2.Feign的介绍 Feign是声明式的http客户端&#xff08;只需要将发http请求的信息写出即可&#xff09; 主要基于SpringMvc的注解来声明远程调用的信息&#xff1a; 步骤&…

(十二)K8S可视化工具Rancher部署项目应用实战

1.Rancher部署springboot私有镜像 连接私有镜像操作步骤 1.进入资源>>密文 2.进入镜像库凭证列表&#xff0c;点击添加凭证 3.输入凭证名称&#xff0c;选择自定义&#xff0c;填入自己的私有镜像仓库地址&#xff0c;这里使用的是阿里云&#xff0c;输入用户名和密码…

Zoho CRM SDK

前言 因早期公司的业务在Zoho CRM&#xff0c;现在孵化出自己的想法&#xff0c;想着能把一部分CRM的数据同步导出来&#xff0c;故研究Zoho CRM SDK的接入方法。虽说在文档上都有提及&#xff0c;但有部分细节不甚明了&#xff0c;也是一遍遍尝试出来的&#xff0c;所以分享出…

【Vue】六:路由(上)使用路由 多级路由

文章目录 1.多页面应用2. 单页面应用&#xff08;使用路由&#xff09;3. 多级路由 1.多页面应用 2. 单页面应用&#xff08;使用路由&#xff09; &#xff08;1&#xff09;安装vue-router插件 vue2 要安装 vue-router3 npm i vue-router3vu3 要安装 vue-router4 npm i vue…

字节薪酬体系被曝光,我真的酸了....

曾经的互联网是PC的时代&#xff0c;随着智能手机的普及&#xff0c;移动互联网开始飞速崛起。而字节跳动抓住了这波机遇&#xff0c;2015年&#xff0c;字节跳动全面加码短视频&#xff0c;从那以后&#xff0c;抖音成为了字节跳动用户、收入和估值的最大增长引擎。 自从字节…

【Java】数组详解

文章目录 一、数组的基本认识1.1 数组的概念1.2数组的创建与初始化1.3 数组的使用 二、数组的类型 — 引用类型2.1 JVM 内存分布2.2 什么是引用类型2.3 基本类型变量与引用类型变量的区别2.4 Java 中的 null 三、数组的应用3.1 保存数据3.2 函数参数3.3 函数返回值 一、数组的基…

STL——string和vector容器

初识STL **STL的基本概念****vector容器存放内置数据类型****在vector容器中存放自定义数据类型****vector容器嵌套vector容器****string容器——构造函数****string容器——赋值操作****string容器——字符串拼接****string容器——字符串的查找和替换****string容器——字符串…

Java阶段四Day01

Java阶段四Day01 文章目录 Java阶段四Day01Security框架通配符Vue脚手架 Vue-cli关于VUE关于VUE Cli创建Vue Cli工程解决端口被占用 Vue工程的工程结构[.idea]【重要】[node_modules]【重要】[public]favicon.icoindex.html [src][assets][compnents]【重要】[router][store]【…

Kafka详解(一)

第1章 Kafka概述 1.1 定义 1.2 消息队列 目前企业中比较常见的消息队列产品主要有Kafka、ActiveMQ、RabbitMQ、RocketMQ等。Message Queue ② 在大数据场景主要采用Kafka作为消息队列 ② 在JavaEE开发中主要采用ActiveMQ、RabbitMQ、RocketMQ Kafka存储数据&#xff0c;且保证…

虚函数+多态实现原理(一个冷门知识)

目录 多态实现 虚函数定义 先说原理 抛出问题 探究多态底层 冷门知识 多形态的大海 多态实现 完成类多态体现&#xff0c;多态两个条件: 虚函数重写 父类指针或者引用去调用虚函数。 虚函数定义 虚函数重写/覆盖条件 : 函数 三同 (函数名、参数、返回值) 不符合重写&…