Windows 消息机制浅析

1.       Windows 的历史html

中国人喜欢以史为鉴,而事实也确实是,若是你能知道一件事情的前因后果,每每能够更容易地理解事物为何会表现为当前这样的现状。因此,个人介绍性开场白一般会以一段历史开始。不过,我不会以精确到年月日的那种方式详细讲述,而是选取几个对咱们的编程生涯有重要影响的关键点。程序员

Windows 是真正的图形化界面操做系统的普及者,不管任何人,争夺什么第一个实现的GUI、第一个商业化的GUI之类的虚名,都替代不了 Windows 的历史功绩,让最普通的用户可以容易地操纵PC。面试

第一个声名大噪的版本是Windows 3.0(也有人认为应该是它的更加健康强壮的弟弟Windows 3.1),从那个时候开始,咱们就和本文中如下的几个关键角色有了不尽的情缘:编程

 

  1. while(GetMessage(&msg, NULL,0, 0))  
  2. {  
  3.                TranslateMessage(&msg);  
  4.                DispatchMessage (&msg);  
  5. }  
while(GetMessage(&msg, NULL,0, 0))
{
               TranslateMessage(&msg);
               DispatchMessage (&msg);
}

上面代码中的这三个相关函数,会在后文中提到。安全

 

第二个大红大紫的版本则非Windows 95莫属。这个版本的主要变化在于,不管如何,它是一个大众化的所谓32位系统了。之因此要加上“所谓的”三个字,是由于这个系统是个混血儿,在32位代码中混杂有大量的从以前的Windows3.x上移植过来的16位代码。网络

此时间稍后,另外一支潜力股的关键进化过程结束,Windows NT 4.0隆重登场,这个分支的操做系统是全32位的,成为了 Windows 95 系列的掘墓者,也是咱们如今所使用几乎全部的 Windows 桌面系统(Windows2000/XP/2003/Vista/2008)的前辈。可是,这个版本因为对系统硬件的要求甚高(在当时),因此没有引发普通用户的普遍关注。架构

下一个里程碑就是Windows 2000了,微软实现了Windows9x/Me分支和Windows NT分支的合并。紧接着,Windows XP 现身。从有关消息方面来考察,Windows2000 作了微小的改进,在此以前,咱们在不少状况下须要建立一个正常的、隐藏的、完整的窗口来处理消息,而 Windows 2000 引入了一种特殊类型的窗口用于此类需求。道理上来说,应该会减小一些资源占用。app

此后通过五六年的时间,Windows Vista诞生。事实上,从 Windows2000 开始,Windows 家族的编程模型,尤为是对原生态代码(native code)而言,已经基本没有太大的变化。一般只是增长了新的API或者用户控件,或者现有控件增长了新的功能或者风格。尽管 Windows Vista 中有不少的变化,可是对于咱们今天要讲到的主题,影响不大。最主要的一个影响,是消息的发送方和接收方之间有了等级限制,不像以前能够随意互相进行消息传递,这是出于安全性的考虑。异步

2.       Windows 的宏观构造函数

从最原始的版本开始,有三个比较大的功能块占据了Windows系统的绝大部分,这三个块,就是赫赫有名的Kernel、GDI、User。从Windows 95起,另两个在先前不太起眼的部分也迅速崛起,那就是大名鼎鼎的Registry和Shell。

这几个大块的分工是这样的:Kernel,望文生义,负责内核部分,这是任何一个能够称之为操做系统的东西的基石,主要职责有:内存管理、任务调度、外设管理等;GDI,则是对能够进行图形化操纵的设备的操做接口,对外提供的主要功能是在设备上:提供坐标系统,绘制点、线、形状,进行填充,文本绘制,管理画笔、画刷、字体等绘图对象;User,则是前二者的粘合剂,使系统可以经过图形化操做方式和使用者(也就是User)进行交互,把零散的GDI对象有机地组织起来,抽象为窗口,用以接受用户的输入,进行相应的运算(广义上的,并非局限于算数运算),并最终将结果呈现给用户。固然,User 部分一般是指能够实现上述的功能的基础构造,真正的实现部分须要大量的额外工做,这也是 Shell 部分的主要工做。而Registry,则是提供给用户一种与物理存储无关的统一的数据访问方式。

很容易就能够看出,消息功能,这种被咱们一直以窗口间通信最为天然的方式所使用的机制,应该隶属于 User 部分。

对于 Windows Mobile 系统来讲,底层的实现上与桌面系统截然不同,例如,它自己并无kernel32.dll、gdi32.dll、user32.dll这几个众所周知的系统库,而是有一个多合一的coredll.dll,并且内核被实现为一个更接近于正常进程的nk.exe进程,而不是桌面系统下的那个抽象的执行体。尽管如此,可是在逻辑上,咱们依然能够将之与桌面系统同等看待。

3.       Windows 的消息概念

在咱们的一般认识上,消息事实就是一个数值。咱们检查一下消息相关的各个回调函数的原型就会发现,表示消息的那个参数的数据类型是 UINT,也就是无符号的整数类型。不过,咱们一般也会发现,消息每每还附带有两个其余类型的数据,一个是 WPARAM 类型的,一个是 LPARAM 类型的,若是算上消息的目标窗口的句柄,那么,一个消息以及相关信息才可以说是比较完整。为何说是比较呢?看一下 MSG 这个结构的定义就会发现,其实还有另外两个咱们不太常用的数据,是与一条消息有关系的。MSG 的完整声明以下:

 

  1. typedef struct {  
  2.     HWND hwnd;  
  3.     UINT message;  
  4.     WPARAM wParam;  
  5.     LPARAM lParam;  
  6.     DWORD time;  
  7.     POINT pt;  
  8. } MSG, *PMSG;  
typedef struct {
    HWND hwnd;
    UINT message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD time;
    POINT pt;
} MSG, *PMSG;

前四项正是咱们已经说起过的,然后两项,一个表示消息发生时的时间,一个表示此消息发生时的按屏幕坐标表示的鼠标光标的位置。

从这个结构也能够看出,咱们常常所说的消息,更可能是指表明了一个肯定的消息的数值。

咱们可能还会听到有这样的称呼:命令消息、通知消息、反射消息等等。首先须要声明的一点是,这并非对 Windows 系统中的消息的科学分类,而是在某些特定场景下的通俗称谓。命令消息,通常特指 WM_COMMAND 消息,此消息一般由控件或者菜单发出,表示用户执行/发出了一个命令。通知消息,通常特指WM_NOTIFY 消息,此消息一般由公用控件(CommonControls)发出,表示一些事件发生了,须要处理。反射消息,通常用于对 Windows API 的封装类或者类库中。这是一类消息的总称,它们的处理须要通过一种被称为“反射”的机制。这一机制的具体方式下一节中会有描述。

Windows 的消息分类很差分(若是非要划分的话,能够分为系统定义的消息和应用程序定义的消息),不过有一个区段划分。从 0x0000 到 0x03FF,为系统定义的消息,常见的 WM_PAINT、WM_CREATE 等均在其中;从 0x0400 到 0x7FFF,专用于用户自定义的消息可使用 WM_USER + x 的形式自行定义,其中WM_USER 的值就是 0x0400,x 取一个整数;从 0x8000 到 0xBFFF,从 Windows 95 开始,也用做用户自定义的消息范围,可使用 WM_APP + x 的形式自行定义。根据微软的建议,WM_APP类消息用于程序之间的消息通讯,而 WM_USER 类消息则最好用于某个特定的窗口类。微软本身遵循这一惯例,因此,公用控件的消息,如 TVM_DELETEITEM,基本都是 WM_USER 类属。从 0xC000 开始,到 0xFFFF,这个区段的消息值保留给 RegisterWindowMessage 这个 API,此 API 能够接受一个字符串,把它变换成一个惟一的消息值。在桌面系统上,最多见的源字符串,可能就是“TaskbarCreated”了,由它对应的消息会发送到全部的顶级窗口,通知任务栏刚刚被建立(多是因为资源管理崩溃后从新启动致使的)。

由上也能够看出,Windows 的消息值是一个 16 位的数字,这是 16 系统时代留给咱们的痕迹。另外的一个痕迹是WPARAM 和 LPARAM 这两个数据类型,在 16 位时代,WPARAM 是 16 位的,其名字的意思是 wordparameter,LPARAM 是 32 位的,其名字的意思是 longparameter。

4.       Windows 的消息机制

4.1.       消息队列

说到消息机制,可能连最初级的 Windows 程序员都会对消息队列(MessageQueue)这个名词耳熟(不过不见得能详)。对于这样一个基本概念,Windows 操做系统提供的针对消息队列的API 却少的可怜(GetQueueStatus、GetInputState、GetMessageExtraInfo、SetMessageExtraInfo),并且,这些 API 的出镜率也至关的低,甚至有很多经验丰富的程序员也历来没有使用过它们。在 Windows Mobile 上,这些 API 干脆付诸阙如,不过有一个一样极少使用的GetMessageQueueReadyTimeStamp 函数在充门面。

这一切,都归功于在 API 层极好的封装性,减小了开始接触这个平台时须要了解的概念。可是,对于咱们这样既想知其然,又想知其因此然的群体,仍是有必要对消息队列有充分的了解。

4.1.1.      系统消息队列

这是一个系统惟一的队列,输入设备(键盘、鼠标或者其余)的驱动程序会把用户的操做输入转化成消息放置于系统队列中,而后系统会把此消息转到目标窗口所在线程的消息队列中等待处理。

4.1.2.      线程消息队列(应用程序消息队列)

应用程序消息队列这个名称是历史遗留,在 32 位(以及以后的 64 位)系统中,正确的名称应该是线程消息队列。每个GUI线程都会维护这样一个线程消息队列。(这个队列只有在线程调用 User 或者 GDI 函数时才会建立,默认并不建立)。而后线程消息队列中的消息会被本线程的消息循环(有时也被称为消息泵)派送到相应的窗口过程(也叫窗口回调函数)处理。

4.2.       消息的生命期

4.2.1.      消息的产生

消息产生的源头有两个,一个是系统,一个是应用程序。系统产生的消息又能够大体分为两类,一类是由输入设备致使的,例如 WM_MOUSEMOVE,一类是User部分(或者是系统内的其余部分经过User部分)为了实现自身的正常行为或者管理功能而主动生成的,如 WM_WINDOWPOSCHANGED。

产生的方式也有两种,一种称为发送(Send),另外一种称为投递(Post,也有译做张贴的),对应于你们极为熟悉的两个 API,SendMessage 和 PostMessage。系统产生的消息,虽然咱们看不到代码,不过咱们仍是能够粗略地划拨一下,基本上全部的输入类消息,都是以投递的方式抵达应用的,而其余的消息,则大部分是采起了发送方式。

至于应用程序,能够随意选用适合本身的消息产生方式。

4.2.2.      消息的处理

在绝大部分状况下,消息老是有一个目标窗口的,所以,消息也绝大部分是被某个窗口所处理的。处理消息的地方,就是这个窗口的回调函数。

窗口的回调函数,之因此被称做“回调”,就是由于这个函数通常并非由用户(程序员)主动调用它的,而是系统认为在恰当的时候对它进行调用。那么,这个“恰当的时候”是何时呢?根据消息产生的方式,“恰当的时候”也有两个时机。

第一个时机是,DispatchMessage 函数被调用时,另外一个时机是SendMessage 函数被调用时。

咱们正常状况下以系统处理对一个顶级窗口的关闭按钮的鼠标左键点击事件为例来讲明。

这个点击事件完成的标志性消息是 WM_NCLBUTTONUP,表示在一个窗口的非客户区的鼠标左键释放动做,另外,这个鼠标消息的其余数据中会代表,发生这个动做的位置是在关闭按钮上(HTCLOSE)。这是一个鼠标输入事件,从前文能够知道,它会被系统投递到消息队列中。

因而,在消息循环中GetMessage 的某次执行结束后,这个消息被取到了 MSG 结构里。从文章开头的消息循环代码可知,这个消息接下来会被 TranslateMessage 函数作必要的(事实上是“可能的”)翻译,而后交给 DispatchMessage 来全权处理。

DispatchMessage 拿到了 MSG 结构,开始本身的一套办事流程。

首先,检查消息指定的目标窗口句柄。看系统内(其实是本线程内)是否是确实存在这样一个窗口,若是没有,那说明这个消息已经不会有须要对它负责的人选了,那么这个消息就会被丢弃。

若是有,它就会直接调用目标窗口的回调函数。终于看到,咱们写的回调函数出场了,这就是“恰当的时机”之一。固然,为了叙述清晰,此处省略了系统作的一些其余处理。

这样,对于系统来讲,一条投递消息就处理完成,转而继续 GetMessage。

不过对于咱们上面的例子,事情尚未完。

咱们都清楚,对于 WM_NCLBUTTONUP 这样一条消息,一般咱们是无暇去作额外处理的(正事还忙不过来呢……)。因此,咱们通常都会把它扔到那个著名的垃圾堆里,没错,DefWindowProc。尽管如此,咱们仍是能够看出,DefWindowProc其实已经成了咱们的回调函数的一个组成部分,惟一的差异在,这个函数不是咱们本身写的而已。

DefWindowProc 对这个消息的处理也是至关轻松,它基本上没有作什么实质性的事情,而是生成了另一个消息,WM_SYSCOMMAND,同时在 wParam 里指定为 SC_CLOSE。这一次,消息没有被投递到消息队列里,而是直接 Send 出来的。

因而,SendMessage 的艰难历程开始。

第一步,SendMessage 的方向和DispatchMessage 几乎如出一辙,检查句柄。

第二步,事情就来了,它须要检查目标窗口和本身在不在一个线程内。若是在,那就比较好办,按照 DispatchMessage 趟出来的老路走:调用目标窗口的回调函数。这,就是“恰当的时机”之二。

但是要是不在一个线程内,那就麻烦了。道理很简单,别的线程有本身的运行轨迹,没有办法去让它当即就来处理这个消息。

如今,SendMessage该怎么处理手里的这个烫手山芋呢?(做者注:写到此处时,颇有写上“欲知后事如何,且听下回分解”的冲动)

微软的架构师作了个很是聪明的选择:不干涉其余线程的内政。我不会生拉硬拽让你来处理个人消息,我会把消息投递给你(这个投递是内部操做,从外面看,这条消息应该一直被认为是发送过去的),而后—— 我等着。

这下,球踢到了目标线程那边。目标线程一点也不含糊,既然消息来到了个人队列里,那个人 GetMessage 会按照既定的流程走,不过,和上文WM_NCLBUTTONUP 的经历有所不一样。鉴于这条消息是外来客,并且是Send 方式,因而它以优先于线程内部的其余消息进行处理(毕竟友邦在等着啊),处理完毕以后,把结果返回给消息的源线程。能够参见下文中对 GetMessage 函数的叙述。

在咱们的如今讨论的这个例子里,因为 SendMessage(WM_SYSCOMMAND) 是属于本线程内的,因此就会递归调用回窗口的回调函数里。此后的处理,仍是另外的几个消息被衍生出来,如 WM_CLOSE 和 WM_DESTROY。这个例子仅仅出于概念性的展现,而不是彻底精确可靠的,并且,在 Windows Mobile 上,干脆就没有非客户区的概念。

这就是系统内全部消息的处理方式。

不过稍等,PostThreadMessage 投递到消息队列里的消息怎么办?答案是:你本身看着办。最好的处理位置,就是在消息循环中的TranslateMessage 调用以前。

另一个须要稍作注解的问题是消息的返回值问题,这个问题有些微妙。对于大多数的消息,返回值都没有什么意义。对于另外的一些消息,返回值意义重大。我相信有不少人对 WM_ERASEBKGND 消息的返回值会有印象,该消息的返回值直接影响到系统是否是要进行缺省的绘制窗口背景操做。因此,处理完一条消息究竟应该返回什么,查一下文档会更稳妥一些。

这才算是功德圆满了。

4.2.3.      消息的优先级

上一节中其实已经暗示了这一点,来自于其余线程的发送的消息优先级会高一点点。

不过还须要注意,还有那么几个优先级比正常的消息低一点点的。它们是:WM_PAINT、WM_TIMER、WM_QUIT。只有在队列中没有其余消息的时候,这几个消息才会被处理,多个 WM_PAINT 消息还会被合并以提升效率(内幕揭示:WM_PAINT 其实也是一个标志位,因此看上去是被“合并了”)。

其余全部消息则以先进先出(FIFO)的方式被处理。

4.2.4.      没有处理的消息呢?

有人会问出这个问题的。事实上,这差很少就是一个伪命题,基本不存在没有处理的消息。从 4.2.2 节的叙述也能够看出,消息总会流到某一个处理分支里去。

那么,我本人倾向于提问者在问这样一个问题:若是窗口回调函数没有处理某个消息,那这个消息最终怎么样了?其实这仍是取决于回调函数实现者的意志。若是你只是简单地返回,那事实上也是进行了处理,只不过,处理的方式是“什么都没作”而已;若是你把消息传递给 DefWindowProc,那么它会处理本身感兴趣的若干消息,对于别的消息,它也一律无论,直接返回。

4.3.       消息死锁

假设有线程A和B, 如今有如下步骤:

1) 线程A SendMessage 给线程B,A 等待消息在线程B 中处理后返回

2) 线程 B 收到了线程A 发来的消息,并进行处理,在处理过程当中,B 也向线程 A SendMessage,而后等待从A 返回。

此时线程A正等待从线程B返回,没法处理B发来的消息,从而致使了线程A 和B相互等待,造成死锁。

以此类推,多个线程也能够造成环形死锁。

可使用 SendNotifyMessage 或 SendMessageTimeout来避免出现此类死锁。

(做者注:对两个线程互相 SendMessage 曾经专门写程序进行过验证,结果却没有死锁,不知道是否是新一些的 Windows 系统做了特殊的处理。请你们自行验证。)

4.4.       模态(Modal)

这个词汇曾给我带来极大的困惑,我曾经作过很多的努力,想弄清楚为何当初系统的构建者使用“模态”这个词汇来表达这样一种情景,可是最后失败了。我不得不接受这个词,并运用它。直到数天前,我找到了一个对模态的简要介绍,若是有兴趣,各位能够本身去看:http://www.usabilityfirst.com/glossary/main.cgi?function=display_term&term_id=320。(我曾作过的另一个努力是想知道为何Handle会被翻译为“句柄”,或者,是谁首先这样翻译的,迄今无解)。Windows 中的模态有好几个场景,比较典型的有:

显示了一个对话框

显示出一个菜单

操做滚动条

移动窗口

改变窗口大小

把个人体会概括起来,那就是:若是进入了一个模态场景,那么,除了这个模态自己的明确目标,其他操做被一律禁止。概念上能够理解为,模态,是一种独占模式、一种强制模式,一种霸道模式。

在 Windows 里,模态的实现其实很简单,只不过就是包含了本身的消息循环而已,说穿了毫无悬念可言,可是若是不明白这个内幕的话,就会以为很神秘。那么,根据此结论,咱们就能够作一些有趣(或者有意义)的事情了,看一下如下代码,预测一下 TestModal 的执行结果:

 

  1. void CALLBACK RequestQuit(HWNDhwnd, UINT uMsg, UINT idEvent, DWORD dwTime);  
  2. void TestModal()  
  3. {  
  4.                UINT uTimerId =SetTimer(NULL, 66, 1000, RequestQuit);  
  5.                MessageBox(NULL, NULL, NULL,MB_OK);  
  6.                KillTimer(NULL, uTimerId);  
  7. }  
  8.   
  9. void CALLBACK RequestQuit(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)  
  10. {  
  11.                PostMessage(NULL, WM_QUIT,0, 0);  
  12. }  
void CALLBACK RequestQuit(HWNDhwnd, UINT uMsg, UINT idEvent, DWORD dwTime);
void TestModal()
{
               UINT uTimerId =SetTimer(NULL, 66, 1000, RequestQuit);
               MessageBox(NULL, NULL, NULL,MB_OK);
               KillTimer(NULL, uTimerId);
}

void CALLBACK RequestQuit(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
               PostMessage(NULL, WM_QUIT,0, 0);
}

答案见本大节末尾。

须要提醒的是,模态是用户界面里至关重要而广泛的一个概念,不只存在于 Windows 环境下,也存在于其余的用户界面系统中,例如 Symbian。

4.5.       与消息处理有关的钩子(Hook)

不少人都或多或少地据说过或者接触过钩子。钩子在处理事务的正常流程以外,额外给予了咱们一种监听或者控制的方式(注意:在 Windows Mobile 系统下,钩子并不被正式支持)。

TODO: 细化,不过因为这个内容针对桌面系统更多,因此暂时能够略过

4.6.       所谓的反射(Reflection)

上文也已经提到,反射一般会在对 Windows API 的封装类或者类库中出现,这是因为Windows SDK 的 API 是以 C 的风格暴露给使用者的,与 C++ 语言的主要用类编程的风格有一些须要啮合的地方。

举例来讲,一个Button,在 SDK 中是一个已经定型的控件,基本上实现了自包容,要扩展它的功能的话(例如,绘制不一样的外观),系统把接口(广义上的接口,即一种交互上的契约)制定为发给 Button 的属主(一般就是父窗口)的两条消息(WM_MEASUREITEM 和 WM_DRAWITEM)。其道理在于,使用 Button 控件的父窗口,每每是用户本身实现的,处理起来更方便,而不须要对 Button 自身作什么手脚。

可是,这种交互方式在 C++ 的世界里是至关忌讳的。C++ 的自包容单位是对象,那么一个 Button 对象的封装类,假定是CButton,不能本身处理本身的绘制问题,这是不太符合法则的(尽管不是不能够)。

为了消除这一不和谐音,就有人提出了反射机制。其核心就在于,对于本该子控件本身处理的事件所对应的消息(如前面的 WM_DRAWITEM),父窗口即便收到,也不进行直接处理,而是把这个消息从新发回给子控件自己。

这样带来一个问题,当 Button 收到一个 WM_DRAWITEM消息时,弄不清楚到底是本身的子窗口发来的(虽然说往 Button 上创建子窗口不常见,但不是不能够),仍是父窗口把本来是本身的消息反射回来了。因此,最后微软给出一个解决办法,就是反射消息的时候,把消息的值上加一个固定的附加值,这个值就是 OCM__BASE。尽管最初只是微软本身在这样作,这个值也彻底能够各取各的,可是后来别的类/类库的编制者几乎都无一例外地和微软保持了一致。

当控件收到消息以后,先把这个附加值减掉,就能够知道是哪一条消息被反射回来了,而后再做相应的处理。

4.4节小测试的答案:一个消息框显示大概 1 秒钟的时间,而后自动消失。有的人根据这一表现,写出了本身的超时候自动关闭的消息框。若是各位有兴趣,能够本身尝试也实现一下。(提示:须要考虑一下用户先于定时器触发就手动关闭了消息框的状况)

5.       Windows 的消息本质

一个特殊的事件同步机制,使用多种常规线程间同步机制实现。

6.       Windows 的消息操纵

注意:如下讨论中用浅绿色标注的函数,表示在 WindowsMobile 平台上是没有的。

 

  1. SendMessage  
  2.   
  3. PostMessage  
SendMessage

PostMessage

在使用消息的过程当中,这两个函数的使用率是最高的。初学者有时会搞不清楚这两个发送消息的函数的使用场景,容易误用。因此放在这里一块儿说。其实上面已经对 SendMessage 作了不少的介绍,因此在这儿的重点会放在 PostMessage 上。相较 SendMessage而言,PostMessage 的工做要轻松许多,只要找到知道那个的窗口句柄所在的线程,把消息放到该线程的消息队列里就能够了,彻底不理会这条消息最终的命运,是否是被正确处理了。

这一点,从 PostMessage 和 SendMessage 的返回值的不一样也有体现。PostMessage 函数的返回值是 BOOL 类型,体现的是投递操做是否成功。投递操做是有可能失败的,尽管咱们不肯意同时也确实不多看到。例如,目标线程的消息队列已经满(在 16 位时代出现几率较高),或者更糟糕,目标线程根本就没有消息队列。

固然,PostMessage 也要检查窗口句柄的合法性,不过和SendMessage 不一样的一点是,它容许窗口句柄是 NULL。在此状况下,对它的调用就等价于调用 PostThreadMessage 向自身所在线程投递一条消息。

从上面的描述能够很容易地看出,PostMessage 和 SendMessage 的本质区别在于前者发出的消息是异步处理的,然后者发出的消息是同步处理的。理解这一点很是重要。

从上面的这个结果推演,还能够获得另一个有时会颇有用的推论。在本线程以内,若是你在处理某个窗口消息的时候,但愿在处理以后开展另外一项以此消息为前提的工做,那么能够向本窗口 Post 一条消息,来做为该后续工做的触发机制。


 

  1. GetMessage  
GetMessage

检查线程的消息队列,若是有消息就取出该消息到一个传入的 MSG 结构中并返回,没有消息,就等待。等待时线程处于休眠状态,CPU被分配给系统内的其余线程使用。

须要注意的是,由其它线程 Send 过来的消息,会在这里就地处理(即调用相应的窗口回调函数),而不会返回给调用者。

 

  1. DispatchMessage  
DispatchMessage

这个消息的前因后果在上文中已经有较为详细的叙述,故此略去。

 

  1. TranslateMessage(<SPAN style="COLOR: #33ff33">TranslateAccelerator</SPAN>)  
TranslateMessage(TranslateAccelerator

这个函数在本质上与消息机制的关系不大,绝大多数的消息循环中都出现它的身影是由于绝大多数的程序员都不知道这个函数真正是干什么的,仅仅是出于惯例或者初学时教科书上给出的范例。这个函数的做用主要和输入有关,它会把 WM_KEYDOWN 和 WM_KEYUP 这样的消息恰当地、适时地翻译出新的消息来,如 WM_CHAR。若是你确信某个线程根本不会有用户输入方面的需求,基本上能够安全地将之从循环中移除。

能够和它相提并论的就是列出的 TranslateAccelerator 函数,这个函数会把用户输入根据指定的加速键(Accelerator)表翻译为适当的命令消息。

 

  1. PeekMessage  
PeekMessage

窥探线程的消息队列。不管队列中有没有消息,这个函数都当即返回。它的参数列表与 GetMessage 基本一致,只是多了一个标志参数。这个标志参数指定了若是队列中若是有消息的话,PeekMessage 的行为。若是该标志中含有PM_REMOVE,则 PeekMessage 会把新消息返回到 MSG 结构中,正如 GetMessage 的行为那样。若是标志中指定了 PM_NOREMOVE,则不会取出任何消息。

 

  1. <SPAN style="COLOR: #33ff33">WaitMessage</SPAN>  
WaitMessage

这个函数的做用是等待一条消息的到来。等待期间线程处于休眠状态,一旦有新消息到来,则当即返回。

了解了 PeekMessage 和 WaitMessage 以后,理论上,咱们能够写出本身的 GetMessage 了。

 

  1. SendNotifyMessage  
SendNotifyMessage

这个函数颇有意思,它的行为属于看人下菜碟型。若是目标线程就是自身所处线程,那么它就是SendMessage;而一旦发现目标线程是其余线程,那它就相似于PostMessage,不等待目标窗口处理完成。不过,仅仅是相似,由于它发出的消息仍然会被目标线程认为是 Send 过来的。

SendMessageTimeout

这个函数能够说是 SendMessage 函数家族(相对PostMessage 而言)之中最强大的函数。它在标准的SendMessage 函数的功能前提下,加入了许多额外的控制选项以及一个超时设定。例如,它能够指定,若是发现目标窗口已经失去响应的话,那么就当即返回;也能够指定若是目标窗口的响应时间超过了指定的超时时限的话也返回,而不是无限等待下去。并且咱们知道,SendMessage 是会执拗地等待下去的。(内幕揭示:SendMessage 其实就是对 SendMessageTimeout的一个浅封装)

 

  1. <SPAN style="COLOR: #33ff33">SendMessageCallback</SPAN>  
SendMessageCallback

与 SendMessageTimeout 不一样,这个函数在另一个方向上对标准的 SendMessage 进行了扩展。它的行为与SendNotifyMessage 相似,只不过容许在对方处理完消息以后,指定一个本线程内的后续处理函数。仔细观察能够发现,SendNotifyMessage 实际上是本函数的一个特例。

对这个函数的使用场景较少,实际上,做者几乎历来没有见到必须使用它的状况。网上有一些对此函数的讨论和测试代码,但不多有实用价值。(恐怕这也是 Windows Mobile 没有实现此函数的缘由之一。)

 

  1. PostQuitMessage  
PostQuitMessage

这个函数的名字具备迷惑性。事实上,它自己并不会投递任何消息,而是偷偷在系统内部置了一个标志,当调用 GetMessage 时会检测此标志位。若此标志位被置位,并且队列中已经没有别的符合条件的投递消息,则 GetMessage 返回 FALSE,用以终止消息循环。

不过,有人会有这样的疑惑。咱们知道,PostMessage 当窗口句柄为 NULL 的时候,就至关于 PostThreadMessage(GetCurrentThreadId(), …),那么,为何不用 PostMessage(NULL, WM_QUIT, 0, 0),而要引入这么一个单独的 API 呢?有的人给出的缘由是,这个 API 出如今 Windows 的 16 位时代,当时尚未线程的概念。这个答案仔细推敲的话,其实似是而非,由于彻底能够把进程的执行看做是一个线程。真正的缘由,可能从前文能获得一些思考线索,尤为注意“队列中已经没有别的符合条件的投递消息”这个叙述。

 

  1. PostThreadMessage  
PostThreadMessage

跨线程投递消息。咱们知道,消息队列是属于线程的,因此,能够不指定目标窗口而只指定目标线程就投递消息。投递到目标线程的消息一般会被 GetMessage取出,可是,因为没有指定目标窗口,因此不会被派发到任何一个窗口回调函数中。

请注意上文中的一般二字。这是由于在通常的状况下,咱们是按照 GetMessage(&msg, NULL, 0, 0) 这样的形式对 GetMessage 进行调用的,可是,第二个参数是一个窗口句柄,若是指定了一个合法的窗口句柄,那么 GetMessage 就只会取出与该窗口有关的投递消息。若是这样的调用放在线程的主消息循环中,就可能会形成消息积压(这和你在本线程中究竟建立了多少个窗口有关)。所幸的是,迄今我尚未见到过有谁这样使用 GetMessage。

 

  1. <SPAN style="COLOR: #33ff33">BroadcastSystemMessage[Ex]</SPAN>  
BroadcastSystemMessage[Ex]

咱们通常所接触到的消息都是发送给窗口的,其实, 消息的接收者能够是多种多样的,它能够是应用程序(application)、可安装驱动程序(installable driver)、网络驱动程序(networkdriver)、系统级设备驱动程序(system-leveldevice driver)等,用 BroadcastSystemMessage这个API能够对以上系统组件发送消息。

 

  1. InSendMessage<SPAN style="COLOR: #33ff33">[Ex]</SPAN>  
InSendMessage[Ex]

这个函数用于在处理某条消息时,检查消息是否是来自于其余线程的发送操做。它的使用场景也极其有限,除非你确实计划限制某些消息的来源和产生方式。

 

  1. <SPAN style="COLOR: #33ff33">ReplyMessage</SPAN>  
ReplyMessage

这个函数在 MSDN 中的解释很是简单,只有寥寥数语,几乎到了模糊不清的地步。从示例代码段来推测,其做用大概是:消息的接收线程(目标线程)在处理过程当中能够经过调用此函数使得消息的发送线程(源线程)结束等待状态继续执行。

根据微软的文档,其官方建议是:在处理每一个有可能来自于其余线程的消息的时候,若是某一步骤的处理会调用到致使线程移交控制的函数(原文如此:any function that causes the thread to yield control),都应该先调用InSendMessage 类属的函数进行判断,若是返回TRUE,则要当即使用 ReplyMessage 答复消息的源线程。

“会致使线程移交控制的函数”,MSDN 给出的例子是 DialogBox,这使得我作出本身的推测,这样的函数,至少包括会致使进入某种模态场景的函数。

至于“有可能来自于其余线程的消息”,在 Windows 世界里的现实情况是,几乎任何一个消息都会来自于其余线程。

我多年以来的观察能够判定,现实中有无数没有进行以上流程判断的代码都在运行,并且也几乎没有暴露出什么严重的不良后果。这使得我有理由猜想,微软也许已经把对此状况的处理隐含到了系统内部。更况且,Windows Mobile 中根本就没有ReplyMessage 这个 API。

 

  1. GetMessagePos  
  2.   
  3. <SPAN style="COLOR: #33ff33">GetMessageTime</SPAN>  
GetMessagePos

GetMessageTime

这两个函数用于访问当前处理的消息的另外两个信息,对应于 MSG 结构里的相应域。它们存在的缘由是由于窗口回调/消息处理函数通常都不会传递这两个数据。

 

  1. MsgWaitForMultipleObjects[Ex]  
MsgWaitForMultipleObjects[Ex]

这是一个在讲到消息相关的内容时,十有八九会被人遗忘的 API。它属于传统的 ITC、IPC 和 Windows 特有的消息机制的交叉地带。不过,在 Windows 平台上,若是尚未了解并掌握这个函数,那必定不能称其为专家。

这个函数揭示了如下平时不太为人所注意的细节:

一、  消息和内核对象,有千丝万缕的联系

二、  消息和内核对象能够按照类似的方式去处理

若是说,SendMessageTimeout 是 Windows 平台下最强大的发送消息的机制,那么,MsgWaitForMultipleObjects[Ex] 就是最强大等待机制,它是 WaitMessage 和 WaitFor… 函数族的集大成者。根据咱们上面使用 WaitMessage 和 PeekMessage 结合使用能够取代 GetMessage 的论断,咱们也能够这样说,MsgWaitForMultipleObjects[Ex]是最强大的消息循环发动机。

仔细描述此函数会超出单纯的消息机制范畴,因此把深刻学习它的工做遗留给各位本身去实践。

7.       Windows 的消息辨析

7.1.       SendMessage和PostMessage的区别

请考虑有面试考官问及此问题时你如何组织回答。J

7.2.       SendMessage发送的消息不进入消息队列吗

提示:请考虑跨线程的状况。

这个说法不彻底正确。当SendMessage发送的消息跨越线程边界时,消息其实被加入到了目标线程的消息队列里。不过,在线程队列里,别的线程Send过来的消息会被优先处理。

7.3.       PostMessage(WM_QUIT)和PostQuitMessage()的区别,可能会产生怎样的差别化执行效果

提示:请考虑发生以上某个调用时,消息队列里不为空的状况。

7.4.       文章开头的经典消息循环正确么?

提示:请注意 GetMessage 的返回值。

曾经有很长一段时间,连微软的例子也这样写。可是,这样写实际上是不对的。缘由很简单,GetMessage不只仅是取道消息返回 TRUE,取不到(遇到WM_QUIT 消息)返回FALSE这么单纯,它还会出错。出错时返回 -1。这就了能使得经典循环在GetMessage发生错误时变成死循环。微软的建议是,当GetMessage返回 -1 时,跳出循环,结束程序。

 

注:本文乃是数年前的培训讲义,文中有某处不完整,迄今未补,读者自察之。

转载于:https://www.cnblogs.com/skyofbitbit/p/3649104.html