消息循环

消息循环
Windows是以消息驱动的操作系统。

Windows 中有一个系统消息队列,对于每一个正在执行的Windows应用程序,系统为其建立一个“消息队列”,即应用程序队列,用来存放该程序可能创建的各种窗口的消息。

应用程序中含有一段称作“消息循环”的代码,用来从消息队列中检索这些消息并把它们分发到相应的窗口函数中。

程序中的以下代码就是通常的消息循环:

.while TRUE
invoke GetMessage,addr @stMsg,NULL,0,0
.break .if eax == 0
invoke TranslateMessage,addr @stMsg
invoke DispatchMessage,addr @stMsg
.endw

消息循环中的几个函数要用到一个MSG结构,用来做消息传递:
MSG STRUCT
 Hwnd DWORD ?
 Message DWORD ?
 WParam DWORD ?
 LParam DWORD ?
 Time DWORD ?
 Pt     POINT     <>
MSG ENDS
它的各个字段的含义是:
hwnd —— 消息要发向的窗口句柄。
message —— 消息标识符,在头文件中以WM_开头的预定义值(意思为Windows Message)。
wParam —— 消息的参数之一。
lParam —— 消息的参数之二。
time —— 消息放入消息队列的时间。
pt —— 这是一个POINT数据结构,表示消息放入消息队列时的鼠标坐标。

这个结构定义了消息的所有属性,GetMessage函数就是从消息队列中取出这样一条消息来的:
invoke GetMessage, lpMsg, hWnd, wMsgFilterMin,\ wMsgFilterMax

函数的lpMsg指向一个MSG结构,函数会在这里返回取到的消息,hWnd参数指定要获取哪个窗口的消息,例子中指定为NULL,表示获取的是所有本程序所属窗口的消息,wMsgFilterMin 和wMsgFilterMax 为0表示获取所有编号的消息。

GetMessage 函数从消息队列里取得消息,填写好MSG结构并返回。
如果获取的消息是WM_QUIT 消息,那么 eax 中的返回值是0,否则 eax 返回非零值,所以用 .break .if eax == 0来检查返回值。

TranslateMessage 将 MSG 结构传给Windows进行键盘消息的转换。
当有键盘按下和放开时,Windows 产生WM_KEYDOWN 和 WM_KEYUP 或WM_SYSKEYDOWN 和 WM_SYSKEYUP 消息

这些消息的参数中包含的是按键的扫描码,我们使用很不方便。
TranslateMessage 的作用是遇到键盘消息则将扫描码转换成ASCII码并在消息队列中插入WM_CHAR 或 WM_SYSCHAR 消息,参数就是转换好的ASCII码。
如此一来,要处理键盘消息的话只要处理 WM_CHAR 消息就好了。
遇到别的消息则TranslateMessage不做处理。

最后,由 DispatchMessage 将消息发送到窗口对应的窗口过程去处理。

窗口过程返回后 DispatchMessage 函数才返回,然后开始新一轮消息循环。


其他形式的消息循环

GetMessage 函数是程序空闲的时候主动将控制权交还给 Windows 的一种方式,Windows 是一个抢占式的多任务系统,任务之间每 20ms 切换一次。

试想一下,如果窗口程序在主窗口中采用死循环等待,消息由 Windows 直接发送到窗口过程,那么程序会是下列这种样子 -->

invoke CreateWindow,…
invoke ShowWindow,…
invoke UpdateWindow,…
.while dwQuitFlag == 0 
;要退出时在窗口过程中设置dwQuitFlag
.endw
invoke ExitProcess,…
但这样一来,即使程序在空闲状态,轮到自己的20 ms 时间片的时候,CPU时间就会全部消耗在 .while循环中。

使用 GetMessage 的时候,轮到应用程序时间片的时候,如果消息队列里还没有消息,那么程序还是停留在 GetMessage 内部。

这时如果可以由Windows 当家做主没收这 20ms 的时间片,如此保证了CPU资源的合理应用。

应用程序想把所有时间充分用回来,消息队列里没有消息的时候不让 GetMessage 在 Windows内部等待,拱手交出属于自己的CPU时间,那么消息循环可以是下列这种样子 -->

.while TRUE
invoke PeekMessage,addr @stMsg,NULL,0,0,PM_REMOVE
.if eax
.break .if @stMsg.message == WM_QUIT
invoke TranslateMessage, addr @stMsg
invoke DispatchMessage, addr @stMsg
.else
<做其他工作>
.endif
.endw

PeekMessage 是一个类似于 GetMessage 的函数,区别在于当消息队列里有消息的时候,PeekMessage 取回消息,并在 eax 中返回非零值,没有消息的时候它会直接返回,并在 eax 中返回零。

PeekMessage 的前面4个参数和 GetMessage 是相同的,增加了最后一个参数,PM_REMOVE 表示取回消息的同时从消息队列里删除,否则用PM_NOREMOVE。

返回非零值的时候,程序检查消息是否是 WM_QUIT,是则结束消息循环,不是则用标准流程处理消息;

返回零的时候,表示是空闲时间,程序就可以做其他工作了,但插入做其他工作的代码执行时间不能过长,以不超过10 ms为好,否则会影响正常的消息处理,使窗口的反应看起来很迟钝。

如果必须处理很长时间的工作,那么应该将它分成很多小部分处理,以便有足够的频率来用 PeekMessage 来检查消息。

posted on 2015-10-06 10:30  木屐  阅读(283)  评论(0编辑  收藏  举报

导航