.Net中封装Windows 消息实现进程间通讯
.NET Framework 中对进程间的通讯支持不多,不过Windows API 已经为我们提供了丰富的进程间通讯的特性。我们可以使用Windows API SendMessage、PostMessage来实现windows 窗体之间的通讯。本文就是将SendMessage封装在一个窗体基类WinMsgData中,把它变成类中的一个方法以方便调用,而接收其他窗体的消息则封装成事件通知的形式提供。其中还对接收到的消息做队列处理,避免对消息发送方造成阻塞。所以只要程序中的WinForm从这个基类继承,就可以很方便的与其他的窗体进行通讯了。
下面看看具体的实现吧。
首先在窗体的构造函数中,我使用RegisterWindowMessage为希望通讯的窗体在系统中注册一个消息值。然后执行一个“Connect”的动作,就是使用PostMessage做广播,看看是否有“志同道合”者。代码如下:
- publiceventWindowsMessageHandlerOnMessage;
- publicdelegatevoidWindowsMessageHandler(objectsender,
- WindowsMessageEventArgse);
- privateconststringDefaultAttachWindowMessage
- ="Peanut.DefaultAttachWinMsgString";
- privateboolconnected=false;
- privateintattachMessage;
- privateintthisHandle;
- privatestringattachMessageString;
- privateList<IntPtr>AttachWindows;
- privateQueue<WindowsMessageEventArgs>incomingMessageQueue
- =newQueue<WindowsMessageEventArgs>();
- publicWinMsgData():this(DefaultAttachWindowMessage){}
- publicWinMsgData(stringattachMessageString)
- {
- this.thisHandle=this.Handle.ToInt32();
- AttachWindows=newList<IntPtr>();
- this.attachMessageString=attachMessageString;
- this.attachMessage=User32.RegisterWindowMessage(attachMessageString);
- Connect();//“连接”
- }
- privatevoidConnect()
- {
- if(User32.PostMessage(User32.HWND_BROADCAST,
- attachMessage,thisHandle,1)==0)//连接
- thrownewException("PostMessagefailed.");
- else
- connected=true;
- }
调用到Windows API代码如下:
具体如何调用Windows的API请看Calling Win32 DLLs in C# with P/Invoke。
- publicstructCOPYDATASTRUCT
- {
- publicIntPtrdwData;
- publicintcbData;
- [MarshalAs(UnmanagedType.LPStr)]
- publicstringlpData;
- }
- publicclassUser32
- {
- publicconstintWM_COPYDATA=0x4A;
- publicconstintWM_DESTROY=0x0002;
- publicconstintWM_QUERYENDSESSION=0x0011;
- publicconstintWM_QUEUE_NOTIFY=0x401;
- [DllImport("user32")]
- publicstaticexternintPostMessage(IntPtrhwnd,intwMsg,intwParam,intlParam);
- [DllImport("user32")]
- publicstaticexternintRegisterWindowMessage(stringlpString);
- [DllImport("user32")]
- publicstaticexternintSendMessage(IntPtrhWnd,intMsg,
- intwParam,refCOPYDATASTRUCTlParam);
- publicstaticIntPtrHWND_BROADCAST
- {
- get{return(IntPtr)0xFFFF;}
- }
- }
然后就要重写WndProc来接收处理窗体消息了:
- protectedoverridevoidWndProc(refMessagem)
- {
- if(m.Msg==attachMessage&
- m.WParam.ToInt32()!=0&
- m.WParam.ToInt32()!=thisHandle)
- {
- if(m.LParam.ToInt32()==1)
- User32.PostMessage(m.WParam,attachMessage,thisHandle,0);//回复
- if(m.LParam.ToInt32()==-1)
- AttachWindows.Remove(m.WParam);//删除窗体句柄
- else//0,1
- AttachWindows.Add(m.WParam);//保存窗体句柄
- }
- switch(m.Msg)
- {
- caseUser32.WM_COPYDATA:
- COPYDATASTRUCTcopyData=newCOPYDATASTRUCT();
- copyData=(COPYDATASTRUCT)m.GetLParam(copyData.GetType());
- MessageNotify(m.WParam.ToInt32(),copyData.lpData);
- break;
- caseUser32.WM_QUEUE_NOTIFY:
- if(incomingMessageQueue.Count==0)
- break;
- WindowsMessageEventArgstempArgs=incomingMessageQueue.Dequeue();
- if(OnMessage!=null)
- OnMessage(thisHandle,tempArgs);
- if(incomingMessageQueue.Count>0)
- User32.PostMessage(this.Handle,User32.WM_QUEUE_NOTIFY,0,0);//下一个
- break;
- caseUser32.WM_QUERYENDSESSION:
- caseUser32.WM_DESTROY:
- User32.PostMessage(User32.HWND_BROADCAST,
- attachMessage,thisHandle,-1);//让其他人删除别再给我发消息
- break;
- }
- base.WndProc(refm);
- }
假如已经有窗体进程已经在运行,那么它就会收到新窗体起来时是所发送的“Connect”消息。上面的代码中第一个if就时处理这个消息的。首先会给消息发送者一个响应。然后会将发送者(窗体的句柄)保存到的列表AttachWindows中。
窗体后面部分就是真正处理通讯消息的部分了。MessageNotify方法首先将消息入队,接着给自己发送一个PostMessage通知自己处理,然后就马上返回。大家可能已经注意到WM_QUEUE_NOTIFY了,不错,这个就是通知自己处理消息的。“处理消息”的就是从消息队列中取出一个消息,将消息作为事件参数触发事件。子窗体只要订阅这个事件,并做处理即可。
上面已经把处理消息的过程讲完了,但还没看到发送消息的部分。下面马上给出。
还是先看代码吧,下面的代码是上面提到的MessageNotify方法及发送消息的方法SendMessage();
- privatevoidMessageNotify(inttarget,stringmessage)
- {
- incomingMessageQueue.Enqueue(newWindowsMessageEventArgs(target,message));
- User32.PostMessage(this.Handle,User32.WM_QUEUE_NOTIFY,0,0);
- }
- publicvoidSendMessage(inttarWin,stringmessage)
- {
- if(!connected)
- thrownewException("notconnected!");
- byte[]bytes=System.Text.Encoding.UTF8.GetBytes(message);
- intlength=bytes.Length;
- COPYDATASTRUCTcopyData=newCOPYDATASTRUCT();
- copyData.dwData=(IntPtr)1;
- copyData.lpData=message;
- copyData.cbData=length+1;
- System.Threading.Thread.Sleep(100);
- try
- {
- User32.SendMessage((IntPtr)tarWin,User32.WM_COPYDATA,thisHandle,refcopyData);
- }
- catch(Exceptione)
- {
- thrownewException("windowssendmessageerror",e);
- }
- }
还有消息的参数类:
- publicclassWindowsMessageEventArgs:EventArgs
- {
- [====Fields====]#region[====Fields====]
- privateinttargetWinHandle;
- privatestringdata;
- #endregion
- publicWindowsMessageEventArgs(inttargetWinHandle,stringdata)
- {
- this.targetWinHandle=targetWinHandle;
- this.data=data;
- }
- [====Properties====]#region[====Properties====]
- /**////<summary>
- ///消息发送者窗体句柄值
- ///</summary>
- publicintTargetWinHandle
- {
- get{returntargetWinHandle;}
- }
- publicstringData
- {
- get{returndata;}
- }
- #endregion
子窗体只要从WinMsgData继承,并订阅消息事件:
base.OnMessage += new WinMsgData.WindowsMessageHandler(HandleMyMessage);
要发送消息的时候调用base.SendMessage(yourMessageToSend)就可以了。使用方便。
效果图: