WM_COPYDATA实现的不同进程间通信
进程间通信,通过SendMessage向另一进程发送WM_COPYDATA消息,实现不同进程间的消息通信。
需求:已写好一个工具软件,想在不更改当前的软件开发的前提下,实现为后面新开发的软件提供数据推送任务。原先想到使用,WCF以实现通信级别的调用,但对于后续新开发的软件来说,所需实现的东西太多(相当于需要实现一个既定接口的服务端)。所以选择使用SendMessage,发送一个WM_COPYDATA以实现对新软件的通知任务。其中主要是需要对传输一个对象级的处理,需要进行序列化及反序列货处理,这是比较重要的一点。其他方面都比较简单,只是单独发一条WM_COPYDATA消息。
一、找到新软件需被通知的窗口(一般是以配置的形式实现)
而在原有软件(即主消息推送方)通过既定的扩展软件名称通过:FindWindow函数进行查找对象句柄,以待后续发送消息。
二、发送消息
需要创建WM_COPYDATA消息所需的传参结构,默认情况下可以传递字符串,所以如只单独传输字符串,那就没什么问题的,直接简单调用即可。但在此,需要传输的为对象,所以,需要将对象先进行一系列处理,以便格式化为字符串进行传输。在接收方再以逆运算得出传输过来的对象。
三、将对象序列化为字符串及反序列化的操作,通过一个帮助类实现
Demo代码:
传输类:
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Runtime.InteropServices; 6 using System.Runtime.Serialization.Formatters.Binary; 7 using System.Text; 8 using System.Threading.Tasks; 9 namespace Dralee.AppTransferMsg.Common 10 { 11 [Serializable] 12 public class Msg 13 { 14 public string SentBy { get; set; } 15 public string RcvBy { get; set; } 16 public string Message { get; set; } 17 public DateTime SentOn { get; set; } 18 public Msg() 19 { 20 } 21 public Msg(string sentBy, string rcvBy, string msg, DateTime sentOn) 22 { 23 SentBy = sentBy; 24 RcvBy = rcvBy; 25 Message = msg; 26 SentOn = sentOn; 27 } 28 public static byte[] Serialize(Msg msg) 29 { 30 MemoryStream ms = new MemoryStream(); 31 BinaryFormatter bf = new BinaryFormatter(); 32 bf.Serialize(ms, msg); 33 byte[] buffer = ms.GetBuffer(); 34 ms.Close(); 35 return buffer; 36 //return Encoding.Unicode.GetString(buffer); 37 } 38 public static Msg Deserialize(byte[] buffer) 39 { 40 MemoryStream ms = new MemoryStream(buffer); 41 BinaryFormatter bf = new BinaryFormatter(); 42 Msg msg = (Msg)bf.Deserialize(ms); 43 return msg; 44 } 45 } 46 /// <summary> 47 /// 传输结构 48 /// </summary> 49 public struct CopyDataStruct 50 { 51 public IntPtr dwData; 52 public int cbData; 53 [MarshalAs(UnmanagedType.LPStr)] 54 public string lpData; 55 } 56 }
转换帮助类:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 namespace Dralee.AppTransferMsg.Common 7 { 8 // CreatedBy: Jackie Lee 9 // CreatedOn: 2016-08-17 10 /// <summary> 11 /// 字节数组与字符串互转 12 /// </summary> 13 public class HexConverter 14 { 15 /// <summary> 16 /// 字节数组转 17 /// </summary> 18 /// <param name="buffer"></param> 19 /// <returns></returns> 20 public string ByteToString(byte[] buffer) 21 { 22 StringBuilder sb = new StringBuilder(); 23 for(int i = 0; i < buffer.Length; ++i) 24 { 25 sb.AppendFormat("{0:X2}", buffer[i]); 26 } 27 return sb.ToString(); 28 } 29 /// <summary> 30 /// 字符串转字节数组 31 /// </summary> 32 /// <param name="str"></param> 33 /// <returns></returns> 34 public byte[] StringToByte(string str) 35 { 36 if(string.IsNullOrEmpty(str) || str.Length % 2 != 0) 37 { 38 return null; 39 } 40 byte[] buffer = new byte[str.Length / 2]; 41 string hex; 42 int j = 0; 43 for(int i = 0; i < buffer.Length;++i) 44 { 45 hex = new string(new char[] { str[j], str[j + 1]}); 46 buffer[i] = HexToByte(hex); 47 j += 2; 48 } 49 return buffer; 50 } 51 /// <summary> 52 /// 双字节字符转byte 53 /// </summary> 54 /// <param name="hex"></param> 55 /// <returns></returns> 56 public byte HexToByte(string hex) 57 { 58 if (hex.Length > 2) 59 return 0; 60 char[] hexs = hex.ToArray(); 61 if(hexs.Length == 1) 62 { 63 return (byte)NumByChar(hexs[0]); 64 } 65 else 66 { 67 return (byte)(NumByChar(hexs[0]) * 16 + NumByChar(hexs[1])); 68 } 69 } 70 private byte NumByChar(char ch) 71 { 72 switch(ch) 73 { 74 case '0': return 0; 75 case '1': return 1; 76 case '2': return 2; 77 case '3': return 3; 78 case '4': return 4; 79 case '5': return 5; 80 case '6': return 6; 81 case '7': return 7; 82 case '8': return 8; 83 case '9': return 9; 84 case 'A': return 10; 85 case 'B': return 11; 86 case 'C': return 12; 87 case 'D': return 13; 88 case 'E': return 14; 89 case 'F': return 15; 90 default: 91 return 0; 92 } 93 } 94 } 95 }
API类:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.InteropServices; 5 using System.Text; 6 using System.Threading.Tasks; 7 namespace Dralee.AppTransferMsg.Common 8 { 9 public static class API 10 { 11 public const int WM_COPYDATA = 0x004A; 12 [DllImport("User32.dll", EntryPoint = "FindWindow")] 13 public static extern int FindWindow(string lpClassName, string lpWindowName); 14 /// <summary> 15 /// 发送消息 16 /// </summary> 17 /// <param name="hwnd">目标句柄</param> 18 /// <param name="msg"></param> 19 /// <param name="wParam">参数1</param> 20 /// <param name="lParam">参数2</param> 21 /// <returns></returns> 22 [DllImport("User32.dll", EntryPoint = "SendMessage")] 23 public static extern int SendMessage(int hwnd, int msg, int wParam, ref CopyDataStruct lParam); 24 } 25 }
发送端:
1 private void btnSend_Click(object sender, EventArgs e) 2 { 3 Msg msg = new Msg { SentBy = txtSentBy.Text.Trim(), RcvBy = txtRcvBy.Text.Trim(), Message = txtMsg.Text.Trim(), SentOn = DateTime.Parse(txtSentOn.Text) }; 4 //long size = GC.GetTotalMemory(true); 5 int hwnd = API.FindWindow(null, "(测试)->接收者"); 6 if (hwnd == 0) 7 return; 8 string msgStr = new HexConverter().ByteToString(Msg.Serialize(msg)); 9 CopyDataStruct cds; 10 cds.dwData = (IntPtr)100; 11 cds.cbData = (int)msgStr.Length + 1; 12 cds.lpData = msgStr; 13 14 API.SendMessage(hwnd, API.WM_COPYDATA, 0,ref cds); 15 }
接收端,通过重写以实现对消息监控:
1 protected override void DefWndProc(ref Message m) 2 { 3 switch(m.Msg) 4 { 5 case API.WM_COPYDATA: 6 CopyDataStruct cds = (CopyDataStruct)m.GetLParam(typeof(CopyDataStruct)); 7 //Msg msg = (Msg)m.GetLParam(typeof(Msg)); 8 byte[] buffer = new HexConverter().StringToByte(cds.lpData); 9 Msg msg = Msg.Deserialize(buffer); 10 txtMsg.Text = msg.Message; 11 txtRcvBy.Text = msg.RcvBy; 12 txtSentBy.Text = msg.SentBy; 13 txtSentOn.Text = msg.SentOn.ToLongDateString(); 14 break; 15 default: 16 base.DefWndProc(ref m); 17 break; 18 } 19 }
效果: