进程间通讯-发送消息
一、发送方
User32.dll中提供了发送消息的系统API。
[DllImport("User32.dll", EntryPoint = "SendMessage")] private static extern int SendMessage( IntPtr hWnd, //目标窗体句柄 int Msg, //WM_COPYDATA int wParam, //自定义数值 ref CopyDataStruct lParam//传递消息的结构体 );
调用此函数可以实现进程间的通讯,但是这种方式的实现需要有一定条件的。分析函数参数可以发现,第一个参数需要传递目标窗体句柄,所以消息接收方必须是一个窗体,这样才可以有一个能被获取到的句柄。
下面进行参数分析。
(1)IntPtr hwnd 目标窗体句柄,即接收方的窗体句柄
句柄:整个Windows编程的基础。一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是指针,程序不能利用句柄来直接阅读文件中的信息。如果句柄不在I/O文件中,它是毫无用处的。 句柄是Windows用来标志应用程序中建立的或是使用的唯一整数,Windows大量使用了句柄来标识对象。
User32.dll中提供了查找窗体并获取句柄的系统API。
/// <summary> /// Find Window by Name /// </summary> /// <param name="lpClassName">指向一个以null结尾的字符串,指定了窗口类(一个WNDCLASS结构)的名字。如果lpszClassName为NULL,则所有的类名都匹配。</param> /// <param name="lpWindowName">指向一个以null结尾的字符串,指定了窗口的名字(窗口的标题)。如果lpWindowName为NULL,所有的窗口名都匹配。</param> /// <returns></returns> [DllImport("User32.dll", EntryPoint = "FindWindow")] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
具体用法如下
(2)WM_COPYDATA
消息传递时区别传递消息的类型,不同的消息类型会有不同的处理方式,常见的类型如下
#region 定义常量消息值 public const int WM_GETTEXT = 0x0D; public const int WM_SETTEXT = 0x0C; public const int WM_SIZEING = 0x0214; public const int WM_COPYDATA = 0x004A; public const int WM_LBUTTONDBLCLK = 0x0203; #endregion
这里我们只需将WM_COPYDATA传递给形参即可。
(3)wParam自定义数值:相同的参数类型下可以根据此数值做进一步区分。
我们定义了两种消息类型,命令Command和消息Message,在发送消息时将对应消息类型传递到形参中,接收端可根据此参数的不同做处理(可见第二部分):
#region 自定义消息 public const int WM_MESSAGE = 0x0001; public const int WM_COMMAND = 0x0002; #endregion
(4)CopyDataStruct lParam 传递消息的结构体
此结构体的定义如下
[StructLayout(LayoutKind.Sequential)] public struct CopyDataStruct { public IntPtr dwData;//用户定义的数据 public int cbData;//字符串长度 [MarshalAs(UnmanagedType.LPTStr)] public string lpData;//字符串 }
在封装消息时,需要将消息组装到结构体中,然后将对应参数传递到函数形参中。
public static int SendMessage(string message, int wmtype, IntPtr hWnd) { CopyDataStruct data; data.dwData = (IntPtr)wmtype; data.lpData = message; data.cbData = Encoding.UTF8.GetBytes(message).Length + 1; return SendMessage(hWnd, WM_COPYDATA, 0, ref data); }
二、接收方
由一可知,此时接收方是一个窗体,这里我们只讨论WPF窗体。winform更加简单,直接重写重写winform的消息处理方法WndProc即可。
而对WPF,没有这个消息处理方法,因此要利用HwndSource来接收并处理消息。
实现如下
/// <summary> /// 重写资源初始化函数,增加HwndSource消息处理 /// </summary> /// <param name="e"></param> protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource; if (hwndSource != null) { hwndSource.AddHook(new HwndSourceHook(WndProc)); } } /// <summary> /// 消息处理函数 /// </summary> /// <param name="hwnd"></param> /// <param name="msg"></param> /// <param name="wParam"></param> /// <param name="lParam"></param> /// <param name="handled"></param> /// <returns></returns> IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == WM_COPYDATA) { CopyDataStruct cds = (CopyDataStruct)Marshal.PtrToStructure(lParam, typeof(CopyDataStruct));//从发送方接收到的数据结构 string param = cds.lpData;//获取发送方传过来的消息 switch ((int)cds.dwData) { case WM_COMMAND: receive.Text = receive.Text + "\n" + "command:" + param; break; case WM_MESSAGE: receive.Text = receive.Text + "\n" + "message:" + param; break; } } return hwnd; }