注:转自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. JavaWeb学习笔记(十二)—— JDBC的基本使用

    一.JDBC概述 1.1 数据库驱动 这里的驱动的概念和平时听到的那种驱动的概念是一样的,比如平时购买的声卡,网卡直接插到计算机上面是不能用的,必须要安装相应的驱动程序之后才能够使用声卡和网卡,同样道 ...

  2. php 常用字符集

    ASCII 字符集 单字节编码,7位(bits)表示一个字符,共128字符 包含内容 控制字符:回车键.退格.换行键等. 可显示字符:英文大小写字符.阿拉伯数字和西文符号 ANSI 码  ANSI编码 ...

  3. R语言学习笔记(三)

    5. 数据结构 5.1 数据结构简介 (1)向量 一个向量的所有元素必须有相同的类型(模式) (2)列表 列表可以非同质的 列表可按位置索引:lst[[2]] 抽取子列表:lst[c(2,5)] 列表 ...

  4. apche 配置

    http://www.tootoogo.org/wordpress/?p=230 apache的配置由httpd.conf 文件配置. 1.基本配置 ServerRoot "/mnt/sof ...

  5. Q680 验证回文字符串 Ⅱ

    给定一个非空字符串 s,最多删除一个字符.判断是否能成为回文字符串. 示例 1: 输入: "aba" 输出: True 示例 2: 输入: "abca" 输出: ...

  6. 【研究】Struts2-052漏洞

    漏洞描述: CVE-2017-9805 2017年9月5日,Apache Struts官方发布最新的安全公告称,Apache Struts 2.5.x的REST插件存在远程代码执行高危漏洞,漏洞编号为 ...

  7. 使用electron构建跨平台Node.js桌面应用经验分享

    by zhangxinxu from http://www.zhangxinxu.com/wordpress/?p=6154 本文可全文转载,但需得到原作者书面许可,同时保留原作者和出处,摘要引流则随 ...

  8. css垂直居中几种方法(二)

    方法1:table-cell .box1{ display: table-cell; vertical-align: middle; text-align: center; } <div cla ...

  9. spring利用cors处理跨域问题

    参考 http://blog.csdn.net/isea533/article/details/50449907 写的很全面 http://blog.csdn.net/a317560315/artic ...

  10. Lucene常用类

    1.1 IndexWriter: 充当  创造/在索引过程中更新指标的   核心组成部分 1.2 Lucene目录 Directory: 索引的存储位置: 通常是文件的列表: 这些文件被称为索引文件. ...