深刻GetMessage和PeekMessage[1]

 这篇文章解释了GetMessage和PeekMessage的内部运做方式,同时也是一类与“消息及消息在16位 MS-DOS?/Microsoft? Windows?环境之下的影响”相关文章的基础。咱们将讨论下面这些主题:编程

·系统和应用程序队列(译者注:如下简称为“程序队列”)网络

·GetMessage和PeekMessage函数ide

·消息过滤函数

·WM_QUIT消息测试

·让步和休眠ui

·让步的问题spa

·WaitMessage操作系统

 

16位MS-DOS/Windows环境和32位Win32?/Windows NT?环境有些很重要的不一样之处。虽然这些不一样之处在这儿没法被忽视,但咱们仍是把它们作为遗留问题,由之后的文章去解释吧。翻译

 

队列对象

要理解GetMessage和PeekMessage的运做,必须首先明白Microsoft? Windows?操做系统是如何储存事件和消息的。在Windows中有两种类型的队列为此目的工做,它们分别是系统队列和消息队列。

 

硬件输入:系统队列

Windows有一些驱动程序,它们负责响应来自于键盘和鼠标等硬件的中断服务。在中断时间中,键盘和鼠标驱动程序会调用USER.EXE中指定的一些入口点去报告一个事件的发生。在Windows中服务于光笔计算的光笔驱动程序,一样会在原始的光笔事件中调用这些入口点。

  

在Windows3.1中,系统队列是一个有着120个入口空间的定长的队列。在通常情形下这些“小房间”是足够了,但若是应用程序挂起了或者在一段长的时间里没有及时处理任何消息就可能致使系统队列被填满。若是真的发生了,任未尝试添加到系统队列的新事件都将会引发系统蜂鸣。(译者注:在DOS中,若是一个程序在一段时间内占用了全部的系统资源,使机器没法响应,这时若是你按住一个键不放,你就会听到机箱喇叭嘀嘀做响)

 

发送的消息和程序队列

当一个应用程序开始时,一个队列将会所以而被建立。程序队列(有时会称为任务队列)经常用于储存“正在被发往应用程序的一个窗口” 的消息。惟一常驻程序队列的消息是那些由PostMessage或PostAppMessage明确发送的消息。(SendMessage从不使用系统队列)PostQuitMessage函数不会发送一个消息到程序队列。(WM_QUIT消息将在下文中论讨)

默认的,每一个程序队列能够保持八个消息。通常状况下这是至关足够的,由于PostMessage极少被使用。可是若是一个应用程序试图强制调用不少的PostMessage到某个应用程序时,那么这类应用程序将会用使用SetMessageQueue函数来增长消息队列的长度。你必须当心的使用SetMessageQueue函数,由于它不管什么时候都会先删掉当前的程序队列,并建立一个预期大小的新队列,此时任何在旧队列中的消息都会被销毁。所以,它必须在你的WinMain例程中在全部其它的应用程序编程接口(API)以前调用或在应用程序用PeekMessage明确的清除队列以后调用。

 

GetMessage和PeekMessage是怎样工做的

在Windows的内部,GetMessage和PeekMessage执行着相同的代码。而二者最大的不一样之处则体如今没有任何消息返回到应用程序的状况下。在此种状况下,PeekMessage会返回一个空值到应用程序,GetMessage会在此时让应用程序休眠。在它们之间还有一些其它的不一样,咱们将会在下面讨论,但它们至关次要。

 

GetMessage和PeekMessage逻辑

下面一步步的讲述了在Windows3.1版的GetMessage和PeekMessage公用代码。

提示:下面所示步骤按照消息类型的优先权进行排序。举个例子,发送的消息总在键盘和鼠标消息以前被返回,而键盘和鼠标的消息又会在绘图(paint)消息以前反回,以此类推。

1.         检视在为“活动中任务”服务的程序队列中是否有消息的存在。若是是,首先在队首删除此消息并将其返回到应用程序。而后,应用程序中的GetMessage和PeekMessage会调用一些代码,用以从程序队列中接收此消息,这些代码是由该应用程序调用的动态连接库(DLL)生成的。记住,只有由PostMessage发送的消息会常驻于此队列中。

2.         与全部消息和窗体句柄过滤器进行对照,核查此消息。若是此消息不匹配指定的过滤器,就会把此消息留在程序队列中。若是队列中在此消息的后面还有其它消息,则会转向对下一个消息的处理。

3.         若是在程序队列中没有消息了,就扫描系统队列中的事件。这个过程至关复杂,而且咱们将在下面的“扫描系统队列”小节中XX。通常来说,在系统队列首部的事件是供这个应用程序所使用的,系统会将其转化为消息,并将消息返回到这个应用程序中(它不会首先被置于应用队列中)。注意,这个扫描系统队列的过程可能致使当前活动的应用程序将控制权让给其它的应用程序。

4.         若是在系统队列中没有等待处理的事件,则核查全部与当前应用程序(任务)相关的窗体以肯定更新区域。当一个窗体的一部分须要被重绘时,一个更新区域就被建立在那个窗体部分之上。这个区域将与此窗体中现存的全部更新区域相结合,并储存在内部窗体结构体中。若是GetMessage或PeekMessage在这个任务中发现某些窗体有一些未处理的更新区域,将产生一个WM_PAINT消息,并为那个窗体返回到应用程序中。WM_PAINT从不驻留在任何队列中。此时,一个应用程序将为某个窗体不断的接收WM_PATIN消息,直到更新区域由BeginPaint/EndPaint,ValidateRect,或ValidateRgn所清除。

5.         若是这个任务中没有任何窗体须要被更新,GetMessage和PeekMessage就会在这一点让出控制权,除非PeekMessage调用被设置为PM_NOYIELD属性。

6.         当让步返回时,检视在当前任务中是否有计时器到期。若是是,建立一个WM_TIMER消息并返回。它不但发生在“返回一个WM_TIMER消息到窗体”的计时器上,一样也发生在“调用一个计时器处理过程”的计时器上。如要了解更多信息,请看在微软开发者网络(MSDN)光盘(包括技术文章、Windows文章、核心和驱动程序文章)中的文章“Timers and Timing in Microsoft Windows”(译者注:若是读者可以承认个人工做,我会竭尽全力地翻译这篇关于计时器的文章)。

7.         若是这个应用程序没有计时器事件服务,而且一个应用程序正在被终止,代码将尝试去缩小图形设备界面(GDI)的本地内存堆。一些应用程序,好比绘图应用程序(像Paintbrush?),为GDI分配了大量的堆内存。当应用程序终止时释放这些对象时,会使GDI本地内存堆被空闲空间填满而膨胀。为了恢复这些空闲的空间, 在GetMessage/PeekMessage处理中,LocalShrink将在这一点被调用于GDI的内存堆。这个被完成一次,(每次)一个应用程序将终止。

8.         在这一时刻,代码将分叉为两条路,一是代码任意的返回一个有效的消息,另外一个是彻底没有这个应用程序去处理的消息、事件,而代码最终会走哪条路决定于PeekMessage和GetMessage中的哪个被调用。

·PeekMessage. 若是PeekMessage被调用,并设置了PM_NOYIELD标记,PeekMessage在此刻返回一个空值,这个空返回值指出已经没有要处理的消息了。若是没有设置PM_NOYIELD标记,PeekMessage就在此刻让出控制权。它不会休眠,但会单一的交给其它已准备好的应用程序一个执行的机会。(请参阅下面的“让步与休眠的不一样)当让步返回,PeekMessage直接将控制权返回到应用程序,并返回一个空值,它指出这个应用程序没有要处理的消息了。

?GetMessage. 在此刻,GetMessage会让应用程序休眠、等待,直到一些事件发生须要唤醒应用程序。控制权不会返回到调用GetMessage的应用程序,直到有应用程序必须去处理的消息出现。一旦这个应用程序从被置入休眠状态中醍来,GetMessage内部的循环将回到最开始(步骤1)。

 

WH_GETMESSAGE钩子

在GetMessage和PeekMessage将一个消息返回到调用的应用程序以前,会作一个验证是否存在一个WH_GETMESSAGE钩子的测试。若是有一个已经被安装了,那这个钩子会被调用。若是PeekMessage没有发现可用的消息并返回一个空值时,这个钩子将不会被调用。在钩子处理过程当中,你不可能得知是究竟是GetMessage被调用仍是PeekMessage被调用。

 

扫描系统队列

综上所述,在系统队列中的事件仅仅是硬件事件的记录。那些代码扫描系统队列的主要任务是,从这些事件中建立消息,并肯定哪个窗体将接收这个消息。

代码第一次在系统队列首部找到事件时,并不会立刻将其删除。由于鼠标和键盘事件只是队列中的两种事件,而代码会分枝(译者注:相似于C语言中的switch语句)并单独处理每一种类型的事件。

相关文章
相关标签/搜索