(Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu)
参考:https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage
参考:https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage
参考:https://docs.microsoft.com/en-us/windows/win32/winmsg/about-messages-and-message-queues#application-defined-messages
引言
在windows下做开发的话,避免不了会使用windows的消息机制。
windows消息机制被windows封装的还是比较好用的一种通信方式。提供了相对比较完备的:消息发送、读取、超时、同步异步、异常处理(消息队列满等)、消息清理等,这些可以比较全面的支持了线程内,进程间通信的需求。
相比其它通信方法Socket、Pipe、ShareMemmory,消息机制还是完备了很多的,简化了使用的难度。
注:下面的部分结论来源于测试,并不保证完全的正确性,请采纳时自行评估。
Win进程间消息通信
对于在进程间的消息通信,和进程内还是有较大的区别的。不过windows消息是操作系统提供的机制,有全局性的消息队列,是能够支撑进程间消息通信的。
跨进程传递消息的方式有两种:
一种是知道对端的HWND,通过HWND发送消息,这个一般是需要先找出对端进程,然后再找出进程窗口,之后进行消息的发送。
另一种是发送广播消息,广播消息会发送到所有进程的顶层窗口HWND中,顶层窗口接收到消息,对消息进行接收分发处理。这种也是有一定限制的,一是要求对端有窗口HWND,二是对它的消息ID范围有要求。
主要使用函数:
发送消息使用SendMessage/PostMessage/SendMesssageTimeOut;
接受消息使用GetMessage/PeekMessage。
使用消息ID范围
windows下的消息id是按范围划分的,分为四个范围,如下所示。
对于我们做进程间通信来说,主要关注私用窗口消息id范围 与 RegisterWindowsMessage消息id范围。
如果采用HWND发送跨进程消息的话,使用这两者消息id都可以;如果发广播消息到其它进程的话,需要使用RegisterWindowsMessage申请的id范围才可以。
系统消息id范围:0x0000-0x03FF
system reserves message: in the range 0x0000 through 0x03FF (the value of WM_USER – 1)
私有窗口消息id范围:0x0400-0x7FFF
private window classes:in the range 0x0400 (the value of WM_USER) through 0x7FFF
应用4.0版本应用消息id范围:0x8000-0xBFFF
application is marked version 4.0:in the range 0x8000 (WM_APP) through 0xBFFF
使用RegiesterWindowMessage申请的消息id范围:0xC000-0xFFFF
an application calls the RegisterWindowMessage:in the range 0xC000 through 0xFFFF
创建一个简单的窗口
跨进程发送消息,使用广播消息的话,要求接收端需要有窗口,这样才能接收到广播的消息;对于没有窗口的程序,是接受不到消息的;这就需要创建一个简单的windows窗口了,创建不可见的窗口就可以的。
下面是一个创建窗口,个人觉得极简的例子:
// 使用预定义Class类型STAIC来注册一个windows:
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
_T("STATIC"), // Window class
_T("STATIC Windows"), // Window text
WS_OVERLAPPED, // Window style
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, // Size and position
NULL, // Parent window
NULL, // Menu
NULL, // Instance handle
NULL // Additional application data
);
通常创建窗口的话,会涉及注册一个窗口类,设定消息回调函数,下面是一个通常创建窗口的例子:
// callback函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
if (message = WM_DESTROY){
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
// Register the window class.
const TCHAR CLASS_NAME[] = _T("Empty Windows");
WNDCLASS wc = {};
wc.lpfnWndProc = WndProc;
wc.hInstance = NULL;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
_T("Empty Windows Example"), // Window text
WS_OVERLAPPEDWINDOW, // Window style
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, // Size and position
NULL, // Parent window
NULL, // Menu
NULL, // Instance handle
NULL // Additional application data
);
消息发送接受的例子
有了窗口之后,跨进程广播消息就可以传入到进程中了,就可以接受消息,处理消息了:
// 发送侧样例
const UINT MY_MSG = ::RegisterWindowMessage(_T("MY_MSG_TEST"));
for (int i = 0; i < 10000; i++) {
if (PostMessage(HWND_BROADCAST, MY_MSG, (WPARAM)i, (LPARAM)i)) {
printf("Post Message %d\n", i);
}
else {
printf("Post Message %d failed\n", i);
}
Sleep(100);
}
// 接收侧样例:
const UINT MY_MSG = ::RegisterWindowMessage(_T("MY_MSG_TEST"));
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
if (msg.message == MY_MSG)
{
printf("Get Message, hwnd: %x, param: %d, %d\n", msg.hwnd, (int)msg.wParam, (int)msg.lParam);
}
}
测试使用
做了一些跨进程通信测试,看到的一些约束项,或和预期有点差异的点;
简单罗列如下,仅供参考:
- PostMessage经过测试,当发到10000条之后,还没有接收者的话,会报:配额不足,无法处理此命令。
- PostMessage的进程终止后,发出的消息也随之被销毁了;未被接收的消息也就收不到了。
- 位于WM_USER范围的消息,使用广播模式时,发送成功,但是其它进程接收不到;指定HWND时,可以发送成功;
- 接收方进程必须有窗口才能接收消息,否则Get/Peek不到消息;
- 使用RegisterWindowMessage范围的消息,使用广播模式时,发送成功,其它进程也接收成功;另外接受消息时HWND要使用NULL或主窗口HWND。
- 发广播消息,如果消息没有被及时接收处理,在其它窗口中也感受到明显的卡顿感;消息过于频繁的情况,可能不太适合发广播消息;
- ChangeWindowMessageFilter在接收端的使用好像用处不大,是否执行都未受影响,也可能是因为我所用测试程序没有权限差异
ChangeWindowMessageFilter(MY_MSG, MSGFLT_ADD)
(Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu)