注:转自http://blog.csdn.net/FreeWave/article/details/2056469?reload

       清晰地讲解了Windows线程的消息队列和GetMessage内幕。好文。

     也许题目有些夸张,但是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处理消息的方式大概是这样的:

消息循环伪算法:

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章》

Windows 线程消息队列和GetMessage实现内幕的更多相关文章

  1. 细说UI线程和Windows消息队列(经典)

    在Windows应用程序中,窗体是由一种称为“UI线程(User Interface Thread)”的特殊类型的线程创建的. 首先,UI线程是一种“线程”,所以它具有一个线程应该具有的所有特征,比如 ...

  2. 细说UI线程和Windows消息队列

    在 Windows应用程序中,窗体是由一种称为“ UI线程( User Interface Thread)”的特殊类型的线程创建的. 首先, UI线程是一种“线程”,所以它具有一个线程应该具有的所有特 ...

  3. 【转】细说UI线程和Windows消息队列

    在Windows应用程序中,窗体是由一种称为“UI线程(User Interface Thread)”的特殊类型的线程创建的. 首先,UI线程是一种“线程”,所以它具有一个线程应该具有的所有特征,比如 ...

  4. Windows 消息以及消息处理算法--线程和消息队列详解

    Windows以消息驱动的方式,使得线程能够通过处理消息来响应外界. Windows 为每个需要接受消息和处理消息的线程建立消息队列(包括发送消息队列,登记消息队列,输入消息队列,响应消息队列),其中 ...

  5. 【转】windows消息和消息队列详解

    转载出处:http://blog.csdn.net/bichenggui/article/details/4677494  windows消息和消息队列 与基于MS - DOS的应用程序不同,Wind ...

  6. windows消息和消息队列

    windows消息和消息队列 转自:http://blog.163.com/zhangjie_0303/blog/static/990827062010113062446767/ 与基于MS - DO ...

  7. Windows进程间通讯(IPC)----消息队列

    消息队列 windows系统是通过消息驱动的,每移动一下鼠标,点击一下屏幕都会产生一个消息.这些消息会先被放在windows的一个系统消息队列(先进先出)中,windows系统会为每一个GUI线程创建 ...

  8. ZOJ 2724 Windows 消息队列 (优先队列)

    链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2724 Message queue is the basic fund ...

  9. 消息队列NetMQ 原理分析2-IO线程和完成端口

    消息队列NetMQ 原理分析2-IO线程和完成端口 前言 介绍 目的 IO线程 初始化IO线程 Proactor 启动Procator线程轮询 处理socket 获取超时时间 从完成端口获取处理完的状 ...

随机推荐

  1. Spring学习笔记(四)—— Spring中的AOP

    一.AOP概述 AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善.O ...

  2. DEM反应不收敛问题

    之前算DEM反应一直不收敛,后来把计算规模减少到两个固定颗粒,也就是仿照reactive-chemistry和evaperation这两个算例. 目前reactive-chemistry这个算例,把各 ...

  3. codeforces 1100F Ivan and Burgers 线性基 离线

    题目传送门 题意: 给出 n 个数,q次区间查询,每次查询,让你选择任意个下标为 [ l , r ] 区间内的任意数,使这些数异或起来最大,输出最大值. 思路:离线加线性基. 线性基学习博客1 线性基 ...

  4. Codeforces - 240F 是男人就上26棵线段树

    #include<bits/stdc++.h> using namespace std; const int maxn = 1e5+11; typedef long long ll; ch ...

  5. 1091 N-自守数 (15 分)

    如果某个数 K 的平方乘以 N 以后,结果的末尾几位数等于 K,那么就称这个数为“N-自守数”.例如 3×92​2​​=25392,而 25392 的末尾两位正好是 92,所以 92 是一个 3-自守 ...

  6. panda强化练习2

    In [1]: import pandas as pd import numpy as np import matplotlib.pyplot as plt plt.rcParams['font.sa ...

  7. 爬虫之re块解析

    一.re 这个去匹配比较麻烦,以后也比较少用,简单看一个案例就行 ''' 爬取数据流程: 1.指定url 2.发起请求 3.获取页面数据 4.数据解析 5.持久化存储 ''' import reque ...

  8. SQL语句练习45题(从第11题开始)

    CREATE TABLE student (sno VARCHAR(3) NOT NULL, sname VARCHAR(4) NOT NULL, ssex VARCHAR(2) NOT NULL, ...

  9. 人生苦短,我用pycharm

    一.安装 1.首先到官网上下载正版,然后点击安装,只需要更改下面一个地方即可 2.激活码或者帐号的话,可以去百度搜,也可以去某宝买,也就3块钱(一年),不建议使用破解版,如果你真的差这三块钱的话,你还 ...

  10. PIE SDK地图鹰眼图

    鹰眼图,是GIS的一个基本功能,在鹰眼图上可以像从空中俯视一样查看地图框中所显示的地图在整个图中的位置,是对全局地图的一种概述表达,能够起到很好的空间提示和导航的作用.网上有很多Arcengine 二 ...