.NET Framework 中对进程间的通讯支持不多,不过Windows API 已经为我们提供了丰富的进程间通讯的特性。我们可以使用Windows API SendMessage、PostMessage来实现windows 窗体之间的通讯。本文就是将SendMessage封装在一个窗体基类WinMsgData中,把它变成类中的一个方法以方便调用,而接收其他窗体的消息则封装成事件通知的形式提供。其中还对接收到的消息做队列处理,避免对消息发送方造成阻塞。所以只要程序中的WinForm从这个基类继承,就可以很方便的与其他的窗体进行通讯了。
下面看看具体的实现吧。
首先在窗体的构造函数中,我使用RegisterWindowMessage为希望通讯的窗体在系统中注册一个消息值。然后执行一个“Connect”的动作,就是使用PostMessage做广播,看看是否有“志同道合”者。代码如下:
public event WindowsMessageHandler OnMessage;
public delegate void WindowsMessageHandler(object sender,
WindowsMessageEventArgs e);

private const string DefaultAttachWindowMessage
= "Peanut.DefaultAttachWinMsgString";
private bool connected = false;
private int attachMessage;
private int thisHandle;
private string attachMessageString;
private List<IntPtr> AttachWindows;
private Queue<WindowsMessageEventArgs> incomingMessageQueue
= new Queue<WindowsMessageEventArgs>();
public WinMsgData() : this (DefaultAttachWindowMessage){}

public WinMsgData(string attachMessageString)
{
this.thisHandle = this.Handle.ToInt32();
AttachWindows = new List<IntPtr>();
this.attachMessageString = attachMessageString;
this.attachMessage = User32.RegisterWindowMessage(attachMessageString);
Connect();//“连接”
}

private void Connect()
{
if (User32.PostMessage(User32.HWND_BROADCAST,
attachMessage, thisHandle ,1) == 0)//连接
throw new Exception("PostMessage failed.");
else
connected = true;
}
调用到Windows API代码如下:
具体如何调用Windows 的API请看Calling Win32 DLLs in C# with P/Invoke。
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}

public class User32
{
public const int WM_COPYDATA = 0x4A;
public const int WM_DESTROY = 0x0002;
public const int WM_QUERYENDSESSION = 0x0011;
public const int WM_QUEUE_NOTIFY = 0x401;

[DllImport("user32")]
public static extern int PostMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
[DllImport("user32")]
public static extern int RegisterWindowMessage(string lpString);
[DllImport("user32")]
public static extern int SendMessage(IntPtr hWnd, int Msg,
int wParam, ref COPYDATASTRUCT lParam);

public static IntPtr HWND_BROADCAST
{
get { return (IntPtr)0xFFFF; }
}
}
然后就要重写WndProc来接收处理窗体消息了:
protected override void WndProc(ref Message m)
{
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)
{
case User32.WM_COPYDATA:
COPYDATASTRUCT copyData = new COPYDATASTRUCT();
copyData = (COPYDATASTRUCT)m.GetLParam(copyData.GetType());
MessageNotify(m.WParam.ToInt32(), copyData.lpData);
break;
case User32.WM_QUEUE_NOTIFY:
if (incomingMessageQueue.Count == 0)
break;

WindowsMessageEventArgs tempArgs = incomingMessageQueue.Dequeue();
if (OnMessage != null)
OnMessage(thisHandle, tempArgs);

if (incomingMessageQueue.Count > 0)
User32.PostMessage(this.Handle, User32.WM_QUEUE_NOTIFY, 0, 0); //下一个
break;
case User32.WM_QUERYENDSESSION:
case User32.WM_DESTROY:
User32.PostMessage(User32.HWND_BROADCAST,
attachMessage, thisHandle, -1);//让其他人删除别再给我发消息
break;
}
base.WndProc(ref m);
}
假如已经有窗体进程已经在运行,那么它就会收到新窗体起来时是所发送的“Connect”消息。上面的代码中第一个if就时处理这个消息的。首先会给消息发送者一个响应。然后会将发送者(窗体的句柄)保存到的列表AttachWindows中。
窗体后面部分就是真正处理通讯消息的部分了。MessageNotify方法首先将消息入队,接着给自己发送一个PostMessage通知自己处理,然后就马上返回。大家可能已经注意到WM_QUEUE_NOTIFY了,不错,这个就是通知自己处理消息的。“处理消息”的就是从消息队列中取出一个消息,将消息作为事件参数触发事件。子窗体只要订阅这个事件,并做处理即可。
上面已经把处理消息的过程讲完了,但还没看到发送消息的部分。下面马上给出。
还是先看代码吧,下面的代码是上面提到的MessageNotify方法及发送消息的方法SendMessage();
private void MessageNotify(int target, string message)
{
incomingMessageQueue.Enqueue(new WindowsMessageEventArgs(target, message));
User32.PostMessage(this.Handle, User32.WM_QUEUE_NOTIFY, 0, 0);
}

public void SendMessage(int tarWin, string message)
{
if (!connected)
throw new Exception("not connected!");

byte[] bytes = System.Text.Encoding.UTF8.GetBytes(message);
int length = bytes.Length;

COPYDATASTRUCT copyData = new COPYDATASTRUCT();
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, ref copyData);
}
catch (Exception e)
{
throw new Exception("windows send message error", e);
}
}
还有消息的参数类:
下面看看具体的实现吧。
首先在窗体的构造函数中,我使用RegisterWindowMessage为希望通讯的窗体在系统中注册一个消息值。然后执行一个“Connect”的动作,就是使用PostMessage做广播,看看是否有“志同道合”者。代码如下:
public event WindowsMessageHandler OnMessage;
public delegate void WindowsMessageHandler(object sender, WindowsMessageEventArgs e);

private const string DefaultAttachWindowMessage = "Peanut.DefaultAttachWinMsgString";
private bool connected = false;
private int attachMessage;
private int thisHandle;
private string attachMessageString;
private List<IntPtr> AttachWindows;
private Queue<WindowsMessageEventArgs> incomingMessageQueue = new Queue<WindowsMessageEventArgs>();
public WinMsgData() : this (DefaultAttachWindowMessage){}
public WinMsgData(string attachMessageString)
{
this.thisHandle = this.Handle.ToInt32();
AttachWindows = new List<IntPtr>();
this.attachMessageString = attachMessageString;
this.attachMessage = User32.RegisterWindowMessage(attachMessageString);
Connect();//“连接”
}
private void Connect()
{
if (User32.PostMessage(User32.HWND_BROADCAST, attachMessage, thisHandle ,1) == 0)//连接
throw new Exception("PostMessage failed.");
else
connected = true;
}调用到Windows API代码如下:
具体如何调用Windows 的API请看Calling Win32 DLLs in C# with P/Invoke。
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
public class User32
{
public const int WM_COPYDATA = 0x4A;
public const int WM_DESTROY = 0x0002;
public const int WM_QUERYENDSESSION = 0x0011;
public const int WM_QUEUE_NOTIFY = 0x401;
[DllImport("user32")]
public static extern int PostMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
[DllImport("user32")]
public static extern int RegisterWindowMessage(string lpString);
[DllImport("user32")]
public static extern int SendMessage(IntPtr hWnd, int Msg,
int wParam, ref COPYDATASTRUCT lParam);
public static IntPtr HWND_BROADCAST
{
get { return (IntPtr)0xFFFF; }
}
}然后就要重写WndProc来接收处理窗体消息了:
protected override void WndProc(ref Message m)
{
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)
{
case User32.WM_COPYDATA:
COPYDATASTRUCT copyData = new COPYDATASTRUCT();
copyData = (COPYDATASTRUCT)m.GetLParam(copyData.GetType());
MessageNotify(m.WParam.ToInt32(), copyData.lpData);
break;
case User32.WM_QUEUE_NOTIFY:
if (incomingMessageQueue.Count == 0)
break;
WindowsMessageEventArgs tempArgs = incomingMessageQueue.Dequeue();
if (OnMessage != null)
OnMessage(thisHandle, tempArgs);
if (incomingMessageQueue.Count > 0)
User32.PostMessage(this.Handle, User32.WM_QUEUE_NOTIFY, 0, 0); //下一个
break;
case User32.WM_QUERYENDSESSION:
case User32.WM_DESTROY:
User32.PostMessage(User32.HWND_BROADCAST, attachMessage, thisHandle, -1);//让其他人删除别再给我发消息
break;
}
base.WndProc(ref m);
}窗体后面部分就是真正处理通讯消息的部分了。MessageNotify方法首先将消息入队,接着给自己发送一个PostMessage通知自己处理,然后就马上返回。大家可能已经注意到WM_QUEUE_NOTIFY了,不错,这个就是通知自己处理消息的。“处理消息”的就是从消息队列中取出一个消息,将消息作为事件参数触发事件。子窗体只要订阅这个事件,并做处理即可。
上面已经把处理消息的过程讲完了,但还没看到发送消息的部分。下面马上给出。
还是先看代码吧,下面的代码是上面提到的MessageNotify方法及发送消息的方法SendMessage();
private void MessageNotify(int target, string message)
{
incomingMessageQueue.Enqueue(new WindowsMessageEventArgs(target, message));
User32.PostMessage(this.Handle, User32.WM_QUEUE_NOTIFY, 0, 0);
} 
public void SendMessage(int tarWin, string message)
{
if (!connected)
throw new Exception("not connected!");
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(message);
int length = bytes.Length;
COPYDATASTRUCT copyData = new COPYDATASTRUCT();
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, ref copyData);
}
catch (Exception e)
{
throw new Exception("windows send message error", e);
}
}还有消息的参数类:
子窗体只要从WinMsgData继承,并订阅消息事件:
base.OnMessage += new WinMsgData.WindowsMessageHandler(HandleMyMessage);
要发送消息的时候调用base.SendMessage(yourMessageToSend)就可以了。使用方便。
效果图:
个人的第一篇blog,请大家指教。

浙公网安备 33010602011771号