WIN32汇编语言教程:第04章 第一个窗口程序 · 4.2 分析窗口程序(4)

4.2.3 消息循环

1. 消息循环的一般形式

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

   .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来检查返回值,如果消息队列中有WM_QUIT则退出消息循环。

TranslateMessage将MSG结构传给Windows进行一些键盘消息的转换,当有键盘按下和放开时,Windows产生WM_KEYDOWN和WM_KEYUP或WM_SYSKEYDOWN和WM_SYSKEYUP消息,但这些消息的参数中包含的是按键的扫描码,转换成常用的ASCII码要经过查表,很不方便,TranslateMessage遇到键盘消息则将扫描码转换成ASCII码并在消息队列中插入WM_CHAR或WM_SYSCHAR消息,参数就是转换好的ASCII码,如此一来,要处理键盘消息的话只要处理WM_CHAR消息就好了。遇到别的消息则TranslateMessage不做处理。

最后,由DispatchMessage将消息发送到窗口对应的窗口过程去处理。窗口过程返回后DispatchMessage函数才返回,然后开始新一轮消息循环。

2. 其他形式的消息循环

GetMessage函数是程序空闲的时候主动将控制权交还给Windows的一种方式,Windows是一个抢占式的多任务系统,任务之间每20 ms切换一次,试想一下,如果窗口程序在主窗口中采用死循环等待,消息由Windows直接发送到窗口过程,那么程序会是下列这种样子:

   invoke CreateWindow,...
   invoke ShowWindow,...
   invoke UpdateWindow,...
   .while dwQuitFlag == 0   ;要退出时在窗口过程中设置dwQuitFlag
   .endw
   invoke ExitProcess,...

但这样一来,即使程序在空闲状态,轮到自己的20 ms时间片的时候,CPU时间就会全部消耗在 .while循环中,使用GetMessage的时候,轮到应用程序时间片的时候,如果消息队列里还没有消息,那么程序还是停留在GetMessage内部,这时就可以由Windows当家做主没收这20 ms的时间片,如此保证了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中返回零。所以在返回非零值的时候,程序检查消息是否是WM_QUIT,是则结束消息循环,不是则用标准流程处理消息;返回零的时候,表示是空闲时间,程序就可以做其他工作了,但插入做其他工作的代码执行时间不能过长,以不超过10 ms为好,否则会影响正常的消息处理,使窗口的反应看起来很迟钝。如果必须处理很长时间的工作,那么应该将它分成很多小部分处理,以便有足够的频率来用PeekMessage来检查消息。PeekMessage的前面4个参数和GetMessage是相同的,增加了最后一个参数,PM_REMOVE表示取回消息的同时从消息队列里删除,否则用PM_NOREMOVE。

4.2.4 窗口过程

窗口过程是给Windows回调用的,它必须遵循规定的格式。对窗口过程的子程序名并没有规定,对Windows来说,窗口过程的地址才是惟一需要的,例子程序中的子程序名是_ProcWinMain,读者可以改用任何名称。窗口过程子程序的参数格式为:

WindowProc proc   hwnd,uMsg,wParam,lParam

第一个参数是窗口句柄,一个窗口过程可能为多个基于同一个窗口类的窗口服务,所以Windows回调的时候必须指出要操作的窗口,否则窗口过程不知道要去处理哪个窗口,FirstWindow程序只建立了一个窗口,所以每次传递过来的hwnd和用CreateWindowEx函数返回的窗口句柄是一样的;第二个参数是消息标识,后面两个参数是消息的两个参数。这4个参数和消息循环中MSG结构中的前4个字段是一样的。

1. 窗口过程的结构

窗口过程一般有如下的结构:

WindowProc      proc      uses ebx edi esi,hWnd,uMsg,wParam,lParam
 
                mov       eax,uMsg
                .if       eax == WM_XXX
                           <处理WM_XXX消息>
                .elseif   eax == WM_YYY
                           <处理WM_YYY消息>
                .elseif   eax == WM_CLOSE
                           invoke DestroyWindow,hWinMain
                          invoke PostQuitMessage,NULL
                .else
                           invoke DefWindowProc,hWnd,uMsg,wParam,lParam
                           ret
                .endif
                xor       eax,eax
                ret
 
WindowProc      endp

上页:第04章 第一个窗口程序 · 4.2 分析窗口程序(3) 下页:第04章 第一个窗口程序 · 4.2 分析窗口程序(5)

第04章 第一个窗口程序

版权所有 © 云南伯恩科技 证书:粤ICP备09170368号