windows消息机制的有趣发现

本文探讨Windows消息机制,包括每个线程的消息队列、消息队列容量限制、无消息循环的程序行为及SendMessage与PostMessage的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近琢磨windows的消息机制,发现一些有趣的地方,可能是对的,也可能是错的,分享出来让大家评判评判。

1:每个线程都有消息队列

不仅仅是有窗口的线程,没有窗口的线程也有消息队列!编写一个win32控制台程序,代码如下:

[cpp]  view plain copy
  1. #include<windows.h>  
  2.   
  3. void main()  
  4. {     
  5.     DWORD dwThread = GetCurrentThreadId();  
  6.     ::PostThreadMessage(dwThread,10000,NULL,NULL);  
  7.     MSG msg;  
  8.     GetMessage(&msg,NULL,NULL,NULL);  
  9. }  

 

获取当前线程ID,对当前线程发送线程消息,然后调用GetMessage函数从消息队列中取消息,看看能不能取到我们之前发出的编号为10000的消息。

运行发现GetMessage函数成功从消息队列中取出了我们发送的编号为10000的消息!我们这个线程没有窗口、没有消息循环,但是有消息队列,消息队列是windows为线程提供的。

2:消息队列的容量有多大?

如果我们不停的发送消息,却不用GetMessage之类的函数获取并从消息队列中移除消息。消息队列中的消息岂不是越来越多?那么这个消息队列究竟有多大容量呢?它最多能容纳多少消息?我们编写如下代码

[cpp]  view plain copy
  1. #include<windows.h>  
  2.   
  3. void main()  
  4. {     
  5.     DWORD dwThread = GetCurrentThreadId();  
  6.     for (int i=0;i<1000;i++)  
  7.     PostThreadMessage(dwThread,10000,NULL,NULL);  
  8.     int n = 0;  
  9.     MSG msg;  
  10.     while(PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE))  
  11.     {  
  12.         if (msg.message == 10000)  
  13.             n++;  
  14.     }  
  15.   
  16. }  

循环调用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控制台项目下编写如下代码:

[cpp]  view plain copy
  1. #include <windows.h>  
  2. #define WM_TEST 10000  
  3.   
  4. LRESULT CALLBACK WndProc(HWNDUINTWPARAMLPARAM);  
  5.    
  6. void main()  
  7. {  
  8.     static TCHAR szAppName[]=TEXT("Test!");       
  9.     HWND hwnd;                                                        
  10.     WNDCLASS wndclass = {NULL};  
  11.     wndclass.lpfnWndProc = WndProc;  
  12.     wndclass.style = CS_HREDRAW|CS_VREDRAW;  
  13.     wndclass.lpszClassName  = szAppName;  
  14.     RegisterClass(&wndclass);  
  15.     hwnd=CreateWindow(szAppName,  
  16.         TEXT("The Test Program"),                                         
  17.         WS_OVERLAPPEDWINDOW,                                              
  18.         CW_USEDEFAULT,                                                
  19.         CW_USEDEFAULT,                                                
  20.         CW_USEDEFAULT,                                                    
  21.         CW_USEDEFAULT,  
  22.         NULL,  
  23.         NULL,     
  24.         NULL,  
  25.         NULL);    
  26.     ShowWindow(hwnd,SW_SHOW);  
  27.     UpdateWindow(hwnd);   
  28.   
  29.     system("pause");  
  30. }  
  31.   
  32.   
  33. LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM IParam)   
  34. {     
  35.     switch(message)  
  36.     {  
  37.     case WM_TEST:  
  38.         MessageBox(NULL,TEXT("消息响应!"),TEXT("消息响应!"),MB_OK);  
  39.         return 50;  
  40.     }  
  41.     return DefWindowProc(hwnd,message,wParam,IParam);  
  42. }  

这是一个简单的win32窗口程序,但是我们在窗口过程函数WndProc中没有定义对WN_PAINT消息的处理,在main函数中创建完窗口后也没有建立消息循环,运行程序后会发生什么呢?

运行发现,窗口确实被创建出来了。但是鼠标移动上去就会发现该窗体如图假死一样没有了响应。这是因为包括自绘消息WM_PAINT在内的所有消息都被放入了线程的消息队列里,但是我们没有消息循环!没有取出消息队列中的消息,更没有处理这些消息,我们连窗口过程中对应WM_PAINT的消息处理函数都没有。界面自然就假死了。

4.SendMessage和PostMessage消息的另一个区别

之前我写过一篇博文说过SendMessage和PostMessage的却别在于SendMessage要等消息被处理完成后才返回,如果调用SendMessage后该消息一直未处理完,SendMessage会一直阻塞到处理完为止。而PostMessage不会阻塞,不等处理结果直接返回。实际上他们还有一个区别:PostMessage发送的消息会进入消息队列等待提取,而SendMessage发送的消息不进消息队列,直接交给窗口过程函数处理。为了验证这个说法,我们编写如下代码:

[cpp]  view plain copy
  1. #include <windows.h>  
  2. #define WM_TEST 10000  
  3.   
  4. LRESULT CALLBACK WndProc(HWNDUINTWPARAMLPARAM);  
  5.    
  6. void main()  
  7. {  
  8.     static TCHAR szAppName[]=TEXT("Test!");       
  9.     HWND hwnd;                                                        
  10.     WNDCLASS wndclass = {NULL};  
  11.     wndclass.lpfnWndProc = WndProc;  
  12.     wndclass.style = CS_HREDRAW|CS_VREDRAW;  
  13.     wndclass.lpszClassName  = szAppName;  
  14.     RegisterClass(&wndclass);  
  15.     hwnd=CreateWindow(szAppName,  
  16.         TEXT("The Test Program"),                                         
  17.         WS_OVERLAPPEDWINDOW,                                              
  18.         CW_USEDEFAULT,                                                
  19.         CW_USEDEFAULT,                                                
  20.         CW_USEDEFAULT,                                                    
  21.         CW_USEDEFAULT,  
  22.         NULL,  
  23.         NULL,     
  24.         NULL,  
  25.         NULL);    
  26.     ShowWindow(hwnd,SW_SHOW);  
  27.     UpdateWindow(hwnd);   
  28.   
  29.     int i = SendMessage(hwnd,WM_TEST,NULL,NULL);  
  30.   
  31.     system("pause");  
  32. }  
  33.   
  34.   
  35. LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM IParam)   
  36. {     
  37.     switch(message)  
  38.     {  
  39.     case WM_TEST:  
  40.         MessageBox(NULL,TEXT("消息响应!"),TEXT("消息响应!"),MB_OK);  
  41.         return 50;  
  42.     }  
  43.     return DefWindowProc(hwnd,message,wParam,IParam);  
  44. }  

我们在创建完窗口后用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发送的消息要进消息队列等待分发、处理。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值