Windows 线程消息队列和GetMessage实现内幕
2016-06-24 21:09
381 查看
也许题目有些夸张,但是Windows消息方面确实存在一些不去探究就摸不着头脑的事情,这种问题不是明显错误,不会抛出异常,但却是最棘手的问题,给调试带来很大麻烦,所以我将实际遇到的问题整理如下,以供参考。
一、Windows 消息以及消息处理算法
Windows以消息驱动的方式,使得线程能够通过处理消息来响应外界。Windows 为每个需要接受消息和处理消息的线程建立消息队列(包括发送消息队列,登记消息队列,输入消息队列,响应消息队列),其中发送消息队列保存其他线程通过SendMessage发送给该线程建立窗口的消息,登记消息队列保存通过PostMessage发送给该线程或者该线程建立窗口的消息,输入消息队列保存系统的输入(包括键盘,鼠标输入),响应消息队列包含该线程调用SendMessage给指定窗口的窗口函数处理完后通知该线程的信息。
Windows通过QS_SENDMESSAGE、QS_POSTMESSAGE、QS_QUIT、QS_INPUT、QS_PAINT、QS_TIMER表示是否有发送消息、登记消息、退出消息、输入消息、重绘消息、定时消息。消息的优先级是QS_SENDMESSAGE > QS_POSTMESSAGE > QS_QUIT > QS_INPUT > QS_PAINT > QS_TIMER。
Windows处理消息的方式大概是这样的:
消息循环伪算法:
上面要注意的是各种消息被处理的优先级顺序,在发送队列中有发送消息时,GetMessage不返回,直到将发送队列中消息处理完毕为止,然后复位QS_SENDMESSAGE,没有发送消息时,GetMessage才查看登记消息,如果没有登记消息,则依着优先级从高到低的顺序依次处理各种消息。 如果此过程中发现了优先级低的消息,则GetMessage填充一个MSG,然后返回。如果是QS_QUIT被置位,则GetMessage返回FALSE,否则返回TRUE。
当GetMessage返回FALSE时,消息循环也就结束了。看消息循环可知,当消息循环再次调用GetMessage时,依然按照优先级顺序依次处理各种消息。请注意SendMessage发送到目标线程消息队列的消息在目标线程调用GetMessage时被处理掉,直到没有发送消息为止GetMessage才回去查询其他消息,如果有消息GetMessage取到消息返回,否则GetMessage使得线程陷入IDLE状态,被挂起,当有消息到达线程时GetMessage被唤醒,获取消息返回。
二、Windows 消息之WM_TIMER
WM_TIMER消息的优先级最低,所以在有其他消息的情况下,WM_TIMER消息得不到处理,这也是我以前使用SetTimer注册一个回调函数,而回调函数一直未被调用的原因。因为我在UI环境中使用,处理WM_PAINT消息时又触发了界面的重绘,导致了始终有WM_PAINT消息要处理,WM_TIMER于是得不到处理的机会。处理WM_PAINT消息时要小心,不然程序就可能消耗很高的cpu,并且使得低于WM_PAINT优先级的WM_TIMER得不到处理。
三、Windows 消息相关函数之SendMessageTimeOut
SendMessageTimeOut是发送消息,在消息被处理或者超时的情况下会返回。但是查阅了MSDN和Windows核心编程,都没有发现这个超时值设为0时有什么效果。直到最近一次在服务中对外广播消息,将此值设为0,服务启动后在没有将服务状态设为RUNNING时调用SendMessageTimeOut对外广播消息,超时值设为0,原本以为该函数会立刻返回,但是调用导致了线程的挂起。由于处理广播消息的另外线程一直在等待RUNNING状态,而服务又等待外界处理完该消息然后继续,这就产生了一个死锁。
这都是超时值设置为0引起的后果。现在看来超时值设为0就等同于调用SendMessage了。
上面没有分析线程消息(即通过PostThreadMessage发送的消息),关于Windows消息更详细的解释,以及消息处理机制请参考《Windows 核心编程第26章》。
一、Windows 消息以及消息处理算法
Windows以消息驱动的方式,使得线程能够通过处理消息来响应外界。Windows 为每个需要接受消息和处理消息的线程建立消息队列(包括发送消息队列,登记消息队列,输入消息队列,响应消息队列),其中发送消息队列保存其他线程通过SendMessage发送给该线程建立窗口的消息,登记消息队列保存通过PostMessage发送给该线程或者该线程建立窗口的消息,输入消息队列保存系统的输入(包括键盘,鼠标输入),响应消息队列包含该线程调用SendMessage给指定窗口的窗口函数处理完后通知该线程的信息。
Windows通过QS_SENDMESSAGE、QS_POSTMESSAGE、QS_QUIT、QS_INPUT、QS_PAINT、QS_TIMER表示是否有发送消息、登记消息、退出消息、输入消息、重绘消息、定时消息。消息的优先级是QS_SENDMESSAGE > QS_POSTMESSAGE > QS_QUIT > QS_INPUT > QS_PAINT > QS_TIMER。
Windows处理消息的方式大概是这样的:
消息循环伪算法:
BOOL bRet = FALSE; MSG msg; while ((bRet = GetMessage(&msg, NULL, 0, 0))) { if (bRet == -1) break; // On Error exit the loop TranslateMessage(&msg); //转换消息 DispatchMessage(&msg); //发送消息,其实就是调用指定窗口的窗口函数 }GetMessage伪算法如下:
BOOL GetMessage(MSG *lpMsg, HWND hWnd , UINT wMsgFilterMin, UINT wMsgFilterMax) { //查看QS_SENDMESSAGE标志,如果有的话循环处理,直到没有消息位置 DWORD dwRetVal = 0; ThreadInfo threadInfo; FLAG_SENDPROCLOOP: GetThreadInfo(GetCurrentThreadId(), &threadInfo); while (threadInfo.QS_SENDMESSAGE == QS_SIGNALSET) { //从发送消息队列中获取消息 dwReturnVal = GetMsgFromQueue(QUEUE_SEND, lpMsg, hWnd,wMsgFilterMin, wMsgFilterMax); //判断是否取到消息,有则调用窗口函数,无则复位QS_SENDMESSAGE标志 If (dwReturnVal == GETMESSAGE_HASMESSAGE) { //调用指定窗口的窗口函数 CallWindowProc(hWnd, &threadInfo, lpMsg); } else { QS_SENDMESSAGE = QS_SIGNALRESET; break; } } //在继续处理之前再次检查发送消息队列 if (threadInfo.QS_SENDMESSAGE == QS_SIGNALSET) goto FLAG_SENDPROCLOOP; if (threadInfo.QS_POSTMESSAGE == QS_SIGNALSET) { //从登记消息队列中获取消息 dwReturnVal = GetMsgFromQueue(QUEUE_POST, lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax); //判断是否还有登记消息,没有了则复位QS_POSTMESSAGE标志 if (dwReturnVal == GETMESSAGE_LASTMESSAGE) threadInfo.QS_POSTMESSAGE = QS_SIGNALRESET; return TRUE; } //如果退出标志被置位 if (threadInfo.QS_QUIT == QS_SIGNALSET) { threadInfo.QS_QUIT = QS_SIGNALRESET; FillMessage(lpMsg, MESSAGE_QUIT); return FALSE; } //检查输入消息队列 if (threadInfo.QS_INPUT == QS_SIGNALSET) { DWORD dwRetVal = GetMessageFromQueue(QUEUE_INPUT, lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax); //检查是否有键盘,鼠标消息 if (Test(dwRetVal, QS_KEY) == QS_LASTMOUSEKEYMESSAGE) threadInfo.QS_KEY = QS_SIGNALRESET; if (Test(dwRetVal, QS_MOUSEBUTTON) == QS_LASTMOUSEMESSAGE) threadInfo.QS_MOUSEBUTTON = QS_SIGNALRESET; return TRUE; } //测试QS_PAINT if (threadInfo.QS_PAINT == QS_SIGNALSET) { //填充MSG,如果没有窗口过程确认窗口,则复位QS_PAINT标志 //... //返回TRUE threadInfo.QS_PAINT = QS_SIGNALRESET; return TRUE; } if (threadInfo.QS_TIMER == QS_SIGNALSET) { //填充MSG,如果没有定时器报时,则复位QS_TIMER标志 //... //返回TRUE return TRUE; } //等待有消息到达 dwRetVal = MsgWaitForMultipleObjectsEx(...); if (...) goto FLAG_SENDPROCLOOP; //等待失败 return FALSE; }
上面要注意的是各种消息被处理的优先级顺序,在发送队列中有发送消息时,GetMessage不返回,直到将发送队列中消息处理完毕为止,然后复位QS_SENDMESSAGE,没有发送消息时,GetMessage才查看登记消息,如果没有登记消息,则依着优先级从高到低的顺序依次处理各种消息。 如果此过程中发现了优先级低的消息,则GetMessage填充一个MSG,然后返回。如果是QS_QUIT被置位,则GetMessage返回FALSE,否则返回TRUE。
当GetMessage返回FALSE时,消息循环也就结束了。看消息循环可知,当消息循环再次调用GetMessage时,依然按照优先级顺序依次处理各种消息。请注意SendMessage发送到目标线程消息队列的消息在目标线程调用GetMessage时被处理掉,直到没有发送消息为止GetMessage才回去查询其他消息,如果有消息GetMessage取到消息返回,否则GetMessage使得线程陷入IDLE状态,被挂起,当有消息到达线程时GetMessage被唤醒,获取消息返回。
二、Windows 消息之WM_TIMER
WM_TIMER消息的优先级最低,所以在有其他消息的情况下,WM_TIMER消息得不到处理,这也是我以前使用SetTimer注册一个回调函数,而回调函数一直未被调用的原因。因为我在UI环境中使用,处理WM_PAINT消息时又触发了界面的重绘,导致了始终有WM_PAINT消息要处理,WM_TIMER于是得不到处理的机会。处理WM_PAINT消息时要小心,不然程序就可能消耗很高的cpu,并且使得低于WM_PAINT优先级的WM_TIMER得不到处理。
三、Windows 消息相关函数之SendMessageTimeOut
SendMessageTimeOut是发送消息,在消息被处理或者超时的情况下会返回。但是查阅了MSDN和Windows核心编程,都没有发现这个超时值设为0时有什么效果。直到最近一次在服务中对外广播消息,将此值设为0,服务启动后在没有将服务状态设为RUNNING时调用SendMessageTimeOut对外广播消息,超时值设为0,原本以为该函数会立刻返回,但是调用导致了线程的挂起。由于处理广播消息的另外线程一直在等待RUNNING状态,而服务又等待外界处理完该消息然后继续,这就产生了一个死锁。
这都是超时值设置为0引起的后果。现在看来超时值设为0就等同于调用SendMessage了。
上面没有分析线程消息(即通过PostThreadMessage发送的消息),关于Windows消息更详细的解释,以及消息处理机制请参考《Windows 核心编程第26章》。
相关文章推荐
- spring mvc 传中文 到controller层乱码的 解决方法
- Codeforces Beta Round #14 (Div. 2) D. Two Paths
- weblogic 集群出现socket错误,导致应用服务器无法启动
- 动画 方块旋转
- eclipse编译问题
- ASP.NET MVC Bootstrap 个人博客站 www.zynblog.com
- ReactJS学习系列课程(props 组件属性)
- int 和 string 相互转换(简洁版)
- OPENSTACK常见问题及注意事项汇总
- 【全球囧闻】英国公投真不是逗你玩!“脱欧”龙卷风突袭全球……
- .Net开源Excel、Word操作组件-NPOI、EPPlus、DocX[转]
- iShare.js分享插件
- 加载图片的三种模式
- typedef int int_array[4]
- Ceph由于更换IP地址导致MON异常
- HTML速查列表
- 笔试题81. LeetCode OJ (68)
- 我的PHP笔记(杂一)
- 骆驼吃香蕉
- HDU 4411 Arrest