最近琢磨windows的消息机制,发现一些有趣的地方,可能是对的,也可能是错的,分享出来让大家评判评判。
1:每个线程都有消息队列
不仅仅是有窗口的线程,没有窗口的线程也有消息队列!编写一个win32控制台程序,代码如下:
- #include<windows.h>
- void main()
- {
- DWORD dwThread = GetCurrentThreadId();
- ::PostThreadMessage(dwThread,10000,NULL,NULL);
- MSG msg;
- GetMessage(&msg,NULL,NULL,NULL);
- }
获取当前线程ID,对当前线程发送线程消息,然后调用GetMessage函数从消息队列中取消息,看看能不能取到我们之前发出的编号为10000的消息。
运行发现GetMessage函数成功从消息队列中取出了我们发送的编号为10000的消息!我们这个线程没有窗口、没有消息循环,但是有消息队列,消息队列是windows为线程提供的。
2:消息队列的容量有多大?
如果我们不停的发送消息,却不用GetMessage之类的函数获取并从消息队列中移除消息。消息队列中的消息岂不是越来越多?那么这个消息队列究竟有多大容量呢?它最多能容纳多少消息?我们编写如下代码
- #include<windows.h>
- void main()
- {
- DWORD dwThread = GetCurrentThreadId();
- for (int i=0;i<1000;i++)
- PostThreadMessage(dwThread,10000,NULL,NULL);
- int n = 0;
- MSG msg;
- while(PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE))
- {
- if (msg.message == 10000)
- n++;
- }
- }
循环调用PostThreadMessage发送1000个消息,然后用PeekMessage取出消息,最后一个参数PM_REMOVE说明每取出一个消息后将该消息从消息队列中移除。我们用变量n来统计从消息队列中取出了多少个编号为10000的消息。这里为什么不用GetMessage呢?因为GetMessage把消息队列中的消息取完了以后会阻塞起来一直等待,我们的while循环无法退出,后面的断点也无法命中。而PeekMessage把消息队列中的消息取完了以后,取不到消息就返回0,我们的while也就结束了。
运行后发现,果然,n的值为1000,取出了1000个我们发送的消息。接下来我们再增大消息的发送数量,看看还能取出多少。
这次我们发送了50000个消息,但是运行后发现只从消息队列中取出了10000个消息!消息队列的容量为10000个吗?我们上网查资料,确实如此。消息队列的容量默认为10000个,这个值可以在注册表中修改。
展开注册表:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\
找到USERPostMessageLimit 项,发现该项的默认值为10000。我们可以通过修改该值来改变消息队列的容量,重启后生效。
3.一个有窗口和窗口过程函数但没有消息循环的程序
一个程序,如果我们创建了窗口,也定义了窗口过程函数,但是没有建立消息循环会怎样呢?我们在win32控制台项目下编写如下代码:
- #include <windows.h>
- #define WM_TEST 10000
- LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
- void main()
- {
- static TCHAR szAppName[]=TEXT("Test!");
- HWND hwnd;
- WNDCLASS wndclass = {NULL};
- wndclass.lpfnWndProc = WndProc;
- wndclass.style = CS_HREDRAW|CS_VREDRAW;
- wndclass.lpszClassName = szAppName;
- RegisterClass(&wndclass);
- hwnd=CreateWindow(szAppName,
- TEXT("The Test Program"),
- WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT,
- CW_USEDEFAULT,
- CW_USEDEFAULT,
- CW_USEDEFAULT,
- NULL,
- NULL,
- NULL,
- NULL);
- ShowWindow(hwnd,SW_SHOW);
- UpdateWindow(hwnd);
- system("pause");
- }
- LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM IParam)
- {
- switch(message)
- {
- case WM_TEST:
- MessageBox(NULL,TEXT("消息响应!"),TEXT("消息响应!"),MB_OK);
- return 50;
- }
- return DefWindowProc(hwnd,message,wParam,IParam);
- }
这是一个简单的win32窗口程序,但是我们在窗口过程函数WndProc中没有定义对WN_PAINT消息的处理,在main函数中创建完窗口后也没有建立消息循环,运行程序后会发生什么呢?
运行发现,窗口确实被创建出来了。但是鼠标移动上去就会发现该窗体如图假死一样没有了响应。这是因为包括自绘消息WM_PAINT在内的所有消息都被放入了线程的消息队列里,但是我们没有消息循环!没有取出消息队列中的消息,更没有处理这些消息,我们连窗口过程中对应WM_PAINT的消息处理函数都没有。界面自然就假死了。
4.SendMessage和PostMessage消息的另一个区别
之前我写过一篇博文说过SendMessage和PostMessage的却别在于SendMessage要等消息被处理完成后才返回,如果调用SendMessage后该消息一直未处理完,SendMessage会一直阻塞到处理完为止。而PostMessage不会阻塞,不等处理结果直接返回。实际上他们还有一个区别:PostMessage发送的消息会进入消息队列等待提取,而SendMessage发送的消息不进消息队列,直接交给窗口过程函数处理。为了验证这个说法,我们编写如下代码:
- #include <windows.h>
- #define WM_TEST 10000
- LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
- void main()
- {
- static TCHAR szAppName[]=TEXT("Test!");
- HWND hwnd;
- WNDCLASS wndclass = {NULL};
- wndclass.lpfnWndProc = WndProc;
- wndclass.style = CS_HREDRAW|CS_VREDRAW;
- wndclass.lpszClassName = szAppName;
- RegisterClass(&wndclass);
- hwnd=CreateWindow(szAppName,
- TEXT("The Test Program"),
- WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT,
- CW_USEDEFAULT,
- CW_USEDEFAULT,
- CW_USEDEFAULT,
- NULL,
- NULL,
- NULL,
- NULL);
- ShowWindow(hwnd,SW_SHOW);
- UpdateWindow(hwnd);
- int i = SendMessage(hwnd,WM_TEST,NULL,NULL);
- system("pause");
- }
- LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM IParam)
- {
- switch(message)
- {
- case WM_TEST:
- MessageBox(NULL,TEXT("消息响应!"),TEXT("消息响应!"),MB_OK);
- return 50;
- }
- return DefWindowProc(hwnd,message,wParam,IParam);
- }
我们在创建完窗口后用SendMessage发送了一条WM_TEST的自定义消息,在窗口过程函数WndProc中我们定义对WM_TEST的处理方式为弹出一个MessageBox并返回50。
运行后发现,纵使我们创建的窗体依然是假死状态,仍然弹出了MessageBox,并且SendMessage的返回值i为50。说明我们在窗口过程函数WndProc中定义的对WM_TEST消息的处理代码成功执行!注意,我们这个程序中是没有消息循环的,但是我们用SendMessage发送的消息还是被窗口过程函数WndProc处理了。
我们把SendMessage改为PostMessage再试。运行后发现没有弹出MessageBox。为什么呢?因为PostMessage发送的消息要进消息队列,但我们没有消息循环,没有用GetMessage之类的函数从消息队列中取消息,更没有用DispatchMessage分发消息,所以我们用PostMessage发送的WM_TEST还在消息队列里待着呢。窗口过程函数WndProc中的代码没有执行。
这就证明了SendMessage和PostMessage的另一个不同之处:SendMessage发送的消息直接交给对应的窗口过程函数处理,不进消息队列,而PostMessage发送的消息要进消息队列等待分发、处理。