效果
功能:打开窗体自动连接主程序,并自动添加到列表,可以向子程序群发消息
可以向单个程序单独发送消息
在退出程序后,添加的程序列表会自动移除
一、概述
参考: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