这次我们来说一下通过windows消息机制来进行进程间的通信。
如果只是简单的传递一个值,可以定义一个自己的消息类型。
如果需要传递较复杂的类型,可以使用Data Copy
说明:
WM_USER常量
WM_USER常量是用来给帮助应用程序定义自己的用户消息,常用的形式如:
WM_USER + X,其中X 是一个整数。通过这个常量的定义消息的发送方与接收方就可以行成一个协议。
public const int WM_USER = 0x0400;
更多关于WM_USER的信息请参考:
http://msdn.microsoft.com/zh-cn/library/ms644931(en-us,VS.85).aspx
WM_COPYDATA 消息
一个应用程序可以通过WM_COPYDATA发送数据给接收方。
WM_COPYDATA只可以能过SendMessage发送,如果使用PostMessage,接收方就不会收到发送的消息,因为PostMessage不是同步的调用,这样就无法保证在接收方收到消息的时个WM_COPYDATA中的数据还正确的保存在内存中。我在一开始实现WM_COPYDATA消息的时候就犯了这个错误。
SendMessage的声明:
[DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtrhWnd, int msg, IntPtr wParam, ref COPYDATASTRUCT lParam);
COPYDATASTRUCT结构是真正用来传递数据的结构,我们有必要先来看一个这个结构的样子:代码中有注释,不多说了。
public struct COPYDATASTRUCT
{
public IntPtr dwData;//Specifies data to be passed to the receiving application.
public int cbData;//Specifies the size, in bytes, of the data pointed to by the lpData member.
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;//Pointer to data to be passed to the receiving application. This member can be NULL.
}
也可以参考:
http://msdn.microsoft.com/zh-cn/partners/ms649011(en-us,VS.85).aspx
得到接收消息窗口的句柄
在上边SendMessage的方法声明中第一个参数需要一个hWnd(IntPtr)的数据,其实这应该是接收消息的窗口句柄,如何来得到这个句柄呢?这是一个很关键的问题。有两种方法:
1. 通过窗体的Title找到窗体,从而得到句柄
我是通过遍历系统中所有窗体的方法来找到窗体,在这过程中用到的API:
public delegate bool EnumWindowsProc(IntPtr p_Handle, int p_Param);
[DllImport("user32.dll")]
public static extern int EnumWindows(EnumWindowsProc ewp, int lParam);
[DllImport("user32.dll")] public static extern int GetWindowText(IntPtrhWnd, StringBuilder title, int size);
部分代码:
public void EnumWindwos(string name)
{
this.name = name;
Win32Wrapper.EnumWindowsProc _EunmWindows = newWin32Wrapper.EnumWindowsProc(NetEnumWindows);
Win32Wrapper.EnumWindows(_EunmWindows, 0);
}
private bool NetEnumWindows(IntPtr handle, int p_Param)
{
StringBuilder sb = new StringBuilder(256);
Win32Wrapper.GetWindowText(handle, sb, 256);
if (sb != null)
//System.Windows.Forms.MessageBox.Show(sb.ToString());
if (string.Compare(name, sb.ToString(), true) == 0)
hwnd = handle;
return hwnd == IntPtr.Zero;
}
2. 通过进程名字来得到进程主窗体的句柄。
在C#是这个实现起来比较简单,但是有一点需要注意的是:Visual Studio在调试应用程序和真正运行应用程序的时候进程的名字不一样。
具体代码:
private IntPtr GetProcessMainFormHandle(string name, FindHandleTypetype)
{
if (type == FindHandleType.ProcessName)
{
Process process = GetProcessByName(name);
if (process != null)
return process.MainWindowHandle;
return IntPtr.Zero;
}
else if (type == FindHandleType.WindowName)
{
Win32EnumWindows win32Enum = new Win32EnumWindows();
win32Enum.EnumWindwos(name);
return win32Enum.Hwnd;
//return Win32Wraper.FindWindow(string.Empty, name);
}
return IntPtr.Zero;
}
private Process GetProcessByName(string processName)
{
Process[] processes = Process.GetProcesses();
foreach (Process p in processes)
{
//just for debug
//this method has some question because the visual studio started process name
//is not same with the release. so yan can close visual studio to test the project.
//normal, you should use if(p.ProcessName == processName)
//debug
//if (p.ProcessName.StartsWith(processName))
// return p;
//release
if (p.ProcessName == processName)
return p;
}
return null;
}
到现在,发送方已经可以把消息发送到接收方的消息队列中了,那么接收方如何接收消息呢?
通过在WndProc方法中来接收双方协议好的消息:
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case Win32Wrapper.WM_USER_TEST:
AddMessage("WndProc", m);
//if you call base.WndProc then the DefWndProc will handle this msg agaion.
//base.WndProc(ref m);
break;
case Win32Wrapper.WM_COPYDATA:
Win32Wrapper.COPYDATASTRUCT mystr = newWin32Wrapper.COPYDATASTRUCT();
Type mytype = mystr.GetType();
mystr = (Win32Wrapper.COPYDATASTRUCT)m.GetLParam(mytype);
AddMessage("WndProc", mystr);
//base.WndProc(ref m);
break;
default:
base.WndProc(ref m);
break;
}
}
一定不要忘记,自己不处理的消息要调用base.WndProc(ref m);这样才可以保证自己不处理的消息可以被其它需要处理的地方接收到。在.Net所以在WndProc中没有被处理的消息都会送到别一个消息处理函数中,名字叫做:DefWndProc, 这们来看,在这个消息函数中处理我们的消息也是可以的,但是最好还是在WndProc中处理比较好,这们大家比较好理解。
protected override void DefWndProc(ref Message m)
{
switch (m.Msg)
{
case Win32Wrapper.WM_USER_UNDEFINED:
AddMessage("DefWndProc", m);
break;
default:
base.DefWndProc(ref m);
break;
}
}
一点补充:
实际上我们还可以在别外一个地方来处理我们的消息,这个函数的名字叫做:OnNotifyMessage。在.NET下如果想在这个方法中统一处理我们自己的消息,需要设置一个FormStyle:
this.SetStyle(ControlStyles.EnableNotifyMessage, !this.GetStyle(ControlStyles.EnableNotifyMessage));
通过这个语句可以在打开NotifyMessage和关闭NotifyMessage之间转换,当然你也可以直接设置其值为true.
OnNotifyMessage函数中的实现方法是一样的,给出一个例子:
protected override void OnNotifyMessage(Message m)
{
switch (m.Msg)
{
case Win32Wrapper.WM_USER_TEST:
AddMessage("OnNotifyMessage", m);
break;
case Win32Wrapper.WM_COPYDATA:
Win32Wrapper.COPYDATASTRUCT mystr = newWin32Wrapper.COPYDATASTRUCT();
Type mytype = mystr.GetType();
mystr = (Win32Wrapper.COPYDATASTRUCT)m.GetLParam(mytype);
AddMessage("OnNotifyMessage", mystr);
break;
default:
base.OnNotifyMessage(m);
break;
}
}
过滤消息功能:
最后来看一下关于过滤消息的应用。如果我们想在一个统一的地方把一些我们不想处理的消息过滤掉,如何处理呢。可以通过如下的函数来实现:
添加过滤:
Application.AddMessageFilter(MessageFilter.Filter);
取消过滤:
Application.RemoveMessageFilter(MessageFilter.Filter);
过滤消息的类:
public class MessageFilter : IMessageFilter
{
static MessageFilter filter = new MessageFilter();
public static MessageFilter Filter
{
get { return MessageFilter.filter; }
}
#region IMessageFilter Members
public bool PreFilterMessage(ref Message m)
{
return (m.Msg >= Win32Wrapper.WM_USER);
}
#endregion
}
太简单了,不写了。
好了,最后给一下完整的代码,上面提到在代码中都有实现,写的有点乱,请包含。因为在调试环境中,进程名字不同的原因,调试环境下通过进程名字的方法应该工作不正常,你可以通过把注释的代码放开,也可以直接运行编译应用程序。
http://houzhengqing.qupan.com/4596371.html