Win32API——窗口过程

  窗口过程是给Windows回调用的,它必须遵循规定的格式。窗口过程的子程序名可以自定义命名,比如_ProcWinMain,窗口过程子程序的参数格式为:

WindowProc proc  hwnd,uMsg,wParam,lParam

  第一个参数hwnd是窗口句柄,由于一个窗口过程可能为多个基于同一个窗口类的窗口服务,所以Windows回调的时候必须指出要操作的窗口,否则窗口过程不知道要去处理哪个窗口,如果你的程序只建立了一个窗口,那么每次传递过来的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

  该过程主要是对uMsg参数中的 消息编号 构成一个分支结构,对于需要处理的消息分别处理,这里“需要处理的程序”一般指程序的一些功能,还有一些消息交给DefWindowProc处理,交给DefWindowProc处理的消息一般都是窗口程序运行必须的,DefWindowProc这个函数很有用,后面会有它的介绍。

  要注意的是,窗口过程中要注意保存ebx,edi,esi和ebp寄存器,高级程序中不用自己操心这一点,汇编中就要注意了,Windows内部将这4个寄存器当指针使用,如果返回时改变了他们的值,程序马上会崩溃。proc后面的uses伪操作在子程序进入和退出时自动安插上push和pop寄存器指令,来保护这些寄存器的值。其实不仅是在窗口过程中是这样,所有由应用程序提供给Windows的回调函数都必须遵循这个规定,如定时器回调函数等,所有Win32API也遵循这个规定,所以调用API后,ebx,edi,esi和ebp寄存器的值总是不会被改变,但ecx和edx的值就不一定了。

  uMsg参数指定的消息有一定的范H, Windows标准窗口中已经预定义的值在0〜O3ffh 之间,用户可以自定义一些消息,通过SendMessage等函数传给窗口过程做自定义的处理 工作,这时可以使用的值是从0400h开始的,WM_USER就定义为00000400h,当程序员 定义多个用户消息的时候,一般使用WM_USER+1, WM.USER+2,…之类的定义方法。

  wParam和IParam参数是消息所附带的参数,它随消息的不同而不同,对于不同的消息, 它们的含义必须分别从手册中查明:如WM_MOUSEMOVE消息中,wParam是标志,IParam 是鼠标位置;而WM_GETTEXT消息中,wParam是要获取的字符数,IParam是缓冲区地 址;而对于WM_COPY消息来说,它不需要额外的信息,所以两个参数都没有定义。

  处理了不同的消息,必须返回规定的值给Windows,返回值也需要分别从手册中查明, 比如,处WM_CREATE消息的时候,返回0表示成功;如果程序无法初始化,如申请内存失败,那么可以返回-1, Windows就不会继续窗口的创建过程。一些消息的返回值则没有定义,但大部分的消息处理以后都以返回0表示成功,所以程序中把默认的返回语句放在最后,将eax清0后返回,如果在处理某个消息的时候需要返回不同的值,可以在分支中将eax赋值后直接用ret指令返回。对于DefWindowProc的返回值,我们不对它进行干涉,所以直接将eax不做修改地用ret返回。

  WM_CLOSE消息是按下了窗口右上角的“关闭”按钮后收到的,程序可以在这里处理和关闭窗口相关的事情,一般是相关资源的释放工作,如释放内存、保存工作和提示用户是否保存工作等,如记事本程序在未保存的时候单击“关闭”按钮, 会有提示框提示是否先保存文件,单击“取消”按钮的话,记事本不会关闭,这个步骤就是在WM_CLOSE 消息处理中完成的。如果处理WM_CLOSE消息时直接返回,那么窗口不会关闭,因为这个消息只是Windows通知窗口用户单击了 “关闭”按钮而已,窗口釆取什么样的行为是窗口的事。当窗口决定关闭的时候,需要程序自己调用DestroyWindow来摧毁窗口,并用PostQuitMessage向消息循环发送WM_QUIT消息来退出消息循环。调用PostQuitMessage 时的参数是退岀码,就是GetMessage收到WM_QUIT后MSG结构wParam字段中的内容, 在这里使用NULL。

  PostQuitMessage是初学者容易遗漏的函数,如果没有这条语句,外观上窗口 是被摧毁掉,从屏幕上消失了,但主程序中的消息循环却没有收到WM_QUIT,结 果还在那里打转。常有人调试的时候丢了这条语句,结果再一次编译的时候就收到 错误:LINK fatal error LNK1104: cannot open file “xxx.exe”,这就表示 exe 文件仍然 被使用中。

  Windows为什么不在窗口摧毁的时候自动发送一个WM_QUIT消息,而必须由用户程序自己通过PostQuitMessage函数发送呢?其实很好理解:因为屏幕上可能不止一个窗口,Windows无法确定哪个窗口关闭代表着程序结束。试想一下,用户打开了一个输入参数的小窗口,单击“确定”按钮后关闭并回到主窗口,Windows却不分三七二十一自动发送了 一个WM_QUIT,程序就会莫名其妙地退出了。

2.收到消息的顺序

  窗口过程收到消息是有一定顺序的,收到第一条消息并不是从消息循环开始以后,而 是在CreateWindowEx中就开始了,显示和刷新窗口的函数ShowWindow和Update Window 也向窗口过程发送消息,这一点并不奇怪,因为Windows在CreateWindowEx前调用 RegisterClassEx的时候就已经得到窗口过程的地址了。并且在建立窗口的过程中需要窗口过程的配合。表4.6和表4.7分别列出了调用CreateWindowEx和ShowWindow的时候窗口 过程收到的消息。
           表4.6 调用CreateWindowEx时窗口过程收到的消息

消息发生说 明
WM_GETMINMAXINFO获取窗口大小,以便初始化
WM.NCCREATEIE客户区开始建立
WM_NCCALCSIZE计算客户区大小
WM_CREATE窗口建立

           表4.7 调用ShowWindow时窗口过程收到的消息

消息发生说 明
WM.SHOWWINDOW显示窗口
WM WINDOWPOSCHANGING窗口位置准备改变\
WM_ACTIVATEAPP窗口准备激活
WM NCACTIVATE激活状态改变
WM GETTEXT取窗口名称(显示标题栏用)
WM_ ACTIVATE窗口准备激活
WM_SETFOCUS窗口获得焦点
WM.NCPAINT需要绘画窗口边椎
WM_ERASEBKGND需要擦除背景
VM_WINDOWPOSCHANGED窗口位置已经改变
WM_SIZE窗口大小已经改变
WM_MOVE窗口位置已经移动

  然后程序执行UpdateWindow,这个函数仅仅向窗口过程发送一条WM_PAINT消息, 接着,主程序开始进入消息循环,Windows根据各种因素给窗口过程发送相应的消息,一 直到调用DestroyWindow为止。表4.8列出了 DestroyWindow向窗口过程发送的消息。

             表4.8调用DestroyWindow时窗口过程收到的消息

消息发生说 明
WM_NCACTIVATE窗口激活状态改变
WM.ACTIVATE窗口准备非激活
WM_ACTIVATEAPP窗口准备非激活
WM_KILLFOCUS失去焦点、
WM_DESTROY窗口即将被摧毁
WM_NCDESTROY窗口的非客户区及所有子窗口已经被摧毁

  在所有这些阶段的消息中,大部分的消息都不需要程序自己关心,Windows只是尽义 务通知窗口过程而巳,窗口过程转手就交给DefWindowProc去处理了。程序需要关心的消 息有下面这些,可以根据需要选择使用:
  • WM_CREATE——放置窗口初始化代码,如建立各种子窗口(状态栏和工具栏等)。
  • WM_SIZE——放置位置安排的代码,因为建立的子窗口可能需要随窗口大小的改变而移动位置。
  • WM_PAINT——如果需要自己绘制客户区,则在这里安排代码。
  • WM_CLOSE——向用户确认是否退出,如果退出则摧毁窗口并发送 WM_QUIT 消息。
  • WM_DESTROY——窗口摧毁,在这里放置释放资源等扫尾代码。

3.消息的默认处理DefWindowProc

  Windows预定义的消息范围是0〜03ffh,共预留了 1024个消息编号,查看一下头文件Windows.inc,可以发现实际已定义的消息数目有几百个,这些消息中的大部分对于窗口的运行来说都是必需的,如果窗口过程要处理每一种消息,那么窗口过程中的elseif语句就会绵延数千行,但是窗口的行为就是由处理这些消息的方法来表现的,不处理又不行,怎么办呢?

  实际上,大部分窗口的行为都是差不多的,这意味着如果要窗口过程处理全部的消息, 不同窗口的窗口过程代码应该是大同小异的,完全可以用一个通用的模块来以默认的方式 处理消息,Win32中的DefWindowProc函数实现的就是这个功能。

  不要小看了这个DefWindowProc,正是它用默认的方式处理了几百种消息,才使用户 能用区区百来行代码写出一个全功能的窗口。也正是所有的窗口都用DefWindowProc默认 处理程序自己不处理的消息,才使它们的行为看上去大同小异,因为它们背后实际上是同 一块代码在处理。

  在窗口过程的分支语句中,用户处理所有需要个性化处理的消息,对于表现行为是默认行为的消息,则在else分支中用DefWindowProc来处理。由于对于Windows来说,它并不关心消息在窗口过程中是程序用自己的代码处理的还是用DefWindowProc处理的,它只看eax中的返回值来了解处理结果,所以不管消息是谁处理的,都必须在eax中返回正 确的值。DefWindowProc返回时eax中就是它对消息的处理结果,程序只要直接把eax传回给Windows就行了,所以在例子程序中,DefWindowProc后面直接用一句ret指令返回。

  注意:例子中DefWindowProc后面直接使用的这句ret非常重要,如果丢失了 这一句,那么相当于处理大多数消息时没有返回正确的值,窗口将不会正常工作。

  表4.9中列岀了DefWindowProc中对一些消息的处理方法,如果与用户期望的不同, 就必须在窗口过程中自己处理。

          表4.9 DefWindowProc对一些消息的默认处理方式

消 息DefWindowProc的处理方式
WM_PAINT发送WM ERASEBKGND消息来擦除背景
WM_ERASEBKGND用窗口类结构中的hbrBackground刷子来绘制窗口背景
WM_CLOSE调用DestroyWindow来摧毁窗口
WM_NCLBUTTONDBLCLK这是非客户区(如标题栏)鼠标双击消息,DefWindowProc测试鼠标的位置,然后再釆取相应的措施,如标题栏双击将最大化和恢复窗口
WM_NCLBUTTONUP这是非客户区鼠标释放消息,同样,DefWindowProc测试鼠标的位置然后再采取相应的措施,如鼠标在“关闭”按钮的位置释放将导致发送WM_CLOSE消息
WM NCPAINT非客户区绘制消息,DefWindowProc将绘制边框和客户区

  可以发现DefWindowProc对WM_CLOSE的默认处理是调用DestroyWindow摧毁窗口,DestroyWindow 会引发一个 WM_DESTROY 消息,WM_CLOSE 和WM_DESTROY 的不同之处是:WM_CLOSE代表用户有关闭窗口的意向,窗口过程有权不“服从”,但收 到WM_DESTROY的时候窗口已经在关闭过程中了,不管窗口过程愿不愿意,窗口的关闭已经是不可挽回的事了。

  对于这两个消息,窗口过程必须处理其中的一个,因为必须有个地方发送WM_QUIT 消息来结束消息循环,例子程序中处理WM_CLOSE消息,在其中用DestroyWindow摧毁窗口,再调用PostQuitMessage结束消息循环;程序也可以不处理WM_CLOSE消息,让 DefWindowProc以默认处理的方式摧毁窗口,但这时候必须处理WM_DESTROY消息, 在其中调用PostQuitMessage发送WM_QUIT以结束消息循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值