在Windows程序中,各个进程之间常常需要交换数据,进行数据通讯。
进程间通讯的方式:进程间通讯的方式有很多,常用的有共享内存、命名管道和匿名管道、发送WM_COPYDATA消息等几种方法来直接完成,另外还可以通过socket口、配置文件和注册表 等来间接实现进程间数据通讯任务。以上这几种方法各有优缺点,具体到在进程间进行大数据量数据的快速交换问题上,则可以排除使用配置文件和注册表的方法;另外,由于管道和socket套接字的使用需要有网卡的支持,因此也可以不予考虑。这样,可供选择的通讯方式只剩下共享内存和发送消息两种。这里只讲利用 WM_COPYDATA消息实现进程间通信。
SendMessage与PostMessage: 在继续之前,我们先简单了解一下Windows系统的二个API,SendMessage和PostMessage,这两个函数虽然功能非常相似,都是负责向指定的窗口发送消息,但是SendMessage() 函数发出消息后一直等到接收方的消息响应函数处理完之后才能返回,并能够得到返回值,在此期间发送方程序将被阻塞,SendMessage() 后面的语句不能被继续执行。而PostMessage() 函数在发出消息后马上返回,其后语句能够被立即执行,但是无法获取接收方的消息处理返回值。
WM_COPYDATA的使用:WM_COPYDATA消息的主要目的是允许在进程间传递只读数据。Windows在通过WM_COPYDATA消息传递期间,不提供继承同步方式。SDK文档推荐用户使用SendMessage函数,接受方在数据拷贝完成前不返回,这样发送方就不可能删除和修改数据。使用WM_COPYDATA消息时,只能用SendMessage() 函数发送而不能使用PostMessage(),否则接收方收不到WM_COPYDATA消息。WM_COPYDATA结构的Windows API原形声明如下:
typedef struct tagCOPYDATASTRUCT {
DWORD dwData; //用户定义数据
DWORD cbData; //数据大小
PVOID lpData; //指向数据的指针
} COPYDATASTRUCT;
在使用WM_COPYDATA消息时,由第一个消息参数指定发送窗口的句柄,第二个消息参数则为一同数据相关的数据结构COPYDATASTRUCT的指针。其中,只需将待发送数据的首地址赋予lpData、并由cbData指明数据块长度即可。消息发出后,接收方代码在WM_COPYDATA消息的响应函数中通过随消息传递进来的第二个参数完成对数据块的接收。
【IPC发送接收MSG原理:
首先,在发送方,用FindWindow找到接受方的句柄,然后向接受方发送WM_COPYDATA消息.
接受方在DefWndProc事件中,来处理这条消息.由于中文编码是两个字节,所以传递中文时候字节长度要搞清楚.】
上面讲了发送消息的API方法和WM_COPYDATA的结构,下面讲怎样在C#中实现。在.NET中,我们不能直接调用Windows的API,需要先用DllImport引入API方法,而WM_COPYDATA的结构体也需要自己定义为符合API的形式。在C#中定义如下:
/// <summary>
/// WM_COPYDATA对应的十六进制数为0x004A
/// 发送数据消息常量
/// </summary>
public const int WM_COPYDATA = 0x004A;
/// <summary>
/// 发送 WM_COPYDATA 消息的数据结构体
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
/// <summary>
/// 用户自定义数据
/// </summary>
public IntPtr dwData;
/// <summary>
/// 数据长度
/// </summary>
public int cbData;
/// <summary>
/// 数据地址指针
/// </summary>
public IntPtr lpData;
}
/// <summary>
/// 发送 Windows 消息方法
/// </summary>
/// <param name="hWnd"></param>
/// <param name="msg"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
/// <returns></returns>
[DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
定义了结构体和引入了发送消息的函数后,我们可以编写实现代码,在发送方,可以把发送方法写成一个函数。接收方的窗口句柄则要由你想办法得到,参考代码如下:
/// <summary>
/// 通过 SendMessage 向指定句柄发送数据
/// </summary>
/// <param name="hWnd">接收方的窗口句柄</param>
/// <param name="dwData">附加数据</param>
/// <param name="lpdata">发送的数据</param>
public static int SendCopyData(IntPtr hWnd, int dwData, byte[] lpdata)
{
COPYDATASTRUCT cds = new COPYDATASTRUCT();
cds.dwData = (IntPtr)dwData;
cds.cbData = lpdata.Length;
cds.lpData = Marshal.AllocHGlobal(lpdata.Length);
Marshal.Copy(lpdata, 0, cds.lpData, lpdata.Length);
IntPtr lParam = Marshal.AllocHGlobal(Marshal.SizeOf(cds));
Marshal.StructureToPtr(cds, lParam, true);
int result = 0;
try
{
result = SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, lParam);
}
finally
{
Marshal.FreeHGlobal(cds.lpData);
Marshal.DestroyStructure(lParam, typeof(COPYDATASTRUCT));
Marshal.FreeHGlobal(lParam);
}
return result;
}
在接收方的类,需要从能够接收消息的类(从Control继承的类都可以)继承,然后重写WndProc方法,重写代码如下:
protected override void WndProc(ref Message m)
{
if (m.Msg == WindowsApi.WM_COPYDATA)
{
int dwData;
byte[] lpData;
WindowsApi.ReceivCopyData(ref m, out dwData, out lpData);
// 这里为你的处理代码。。。
}
else
{
base.WndProc(ref m);
}
}
下面是接收函数 ReceivCopyData 的代码:
/// <summary>
/// 获取消息类型为 WM_COPYDATA 中的数据
/// </summary>
/// <param name="m"></param>
/// <param name="dwData">附加数据</param>
/// <param name="lpdata">接收到的发送数据</param>
public static void ReceivCopyData(ref Message m, out int dwData, out byte[] lpdata)
{
COPYDATASTRUCT cds = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT));
dwData = cds.dwData.ToInt32();
lpdata = new byte[cds.cbData];
Marshal.Copy(cds.lpData, lpdata, 0, cds.cbData);
m.Result = (IntPtr)0;
}