友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!阅读过程发现任何错误请告诉我们,谢谢!! 报告错误
八万小说网 返回本书目录 我的书架 我的书签 TXT全本下载 进入书吧 加入书签

windows环境下32位汇编语言程序设计-第9部分

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!




(2)注册窗口类(RegisterClassEx)。在注册之前,要先填写RegisterClassEx的参数WNDCLASSEX结构。

(3)建立窗口(CreateWindowEx)。

(4)显示窗口(ShowWindows)。

(5)刷新窗口客户区(UpdateWindow)。

(6)进入无限的消息获取和处理的循环。首先获取消息(GetMessage),如果有消息到达,则将消息分派到回调函数处理(DispatchMessage),如果消息是WM_QUIT,则退出循环。

程序的另一半_ProcWinMain子程序是用来处理消息的,它就是窗口的回调函数(Callback),也叫做窗口过程,之所以是回调函数是因为它是由Windows而不是我们自己调用的,我们调用DispatchMessage,而DispatchMessage再回过来调用窗口过程。

所有的用户操作都是通过消息来传给应用程序的,如用户按键,鼠标移动,选择了菜单和拖动了窗口等,应用程序中由窗口过程接收消息并处理,在例子程序中就是_ProcWinMain。窗口过程构造了一个分支结构,对应不同的消息执行不同的代码,所以一个应用程序中几乎所有的功能代码都集中在窗口过程里。

窗口程序运行中消息传输的流程可以由图4。4来表示。

先来看看Windows对消息的处理。Windows在系统内部有一个系统消息队列,当输入设备有所动作的时候,如用户按动了键盘、移动了鼠标,按下或放开了鼠标等,Windows都会产生相应的记录放在系统消息队列里,如图4。4中的箭头a和b所示,每个记录中包含消息的类型、发生的位置(如鼠标在什么坐标移动)和发生的时间等信息。


图4。4  窗口程序的运行过程

同时,Windows为每个程序(严格地说是每个线程)维护一个消息队列,Windows检查系统消息队列里消息的发生位置,当位置位于某个应用程序的窗口范围内的时候,就把这个消息派送到应用程序的消息队列里,如图4。4中的箭头c所示。

当应用程序还没有来取消息的时候,消息就暂时保留在消息队列里,当程序中的消息循环执行到GetMessage的时候,控制权转移到GetMessage所在的USER32。DLL中(箭头1),USER32。DLL从程序消息队列中取出一条消息(箭头2),然后把这条消息返回应用程序(箭头3)。

应用程序可以对这条消息进行预处理,如可以用TranslateMessage把基于键盘扫描码的按键消息转换成基于ASCII码的键盘消息,以后也会用到TranslateAccelerator把键盘快捷键转换成命令消息,但这个步骤不是必需的。

然后应用程序将处理这条消息,但方法不是自己直接调用窗口过程来完成,而是通过DispatchMessage间接调用窗口过程,Dispatch的英文含义是“分派”,之所以是“分派”,是因为一个程序可能建有不止一个窗口,不同的窗口消息必须分派给相应的窗口过程。当控制权转移到USER32。DLL中的DispatchMessage时,DispatchMessage找出消息对应窗口的窗口过程,然后把消息的具体信息当做参数来调用它(箭头5),窗口过程根据消息找到对应的分支去处理,然后返回(箭头6),这时控制权回到DispatchMessage,最后DispatchMessage函数返回应用程序(箭头7)。这样,一个循环就结束了,程序又开始新一轮的GetMessage。

有个很常见的问题:为什么要由Windows来调用窗口过程,程序取了消息以后自己处理不是更简便吗?事实上并非如此,如果程序自己处理消息的“分派”,就必须自己维护本程序所属窗口的列表,当程序建立的窗口不止一个的时候,这个工作就变得复杂起来;另一个原因是:别的程序也可能用SendMessage通过Windows直接调用你的窗口过程;第三个原因:Windows并不是把所有的消息都放进消息队列,有的消息是直接调用窗口过程处理的,如WM_SETCURSOR等实时性很强的消息,所以窗口过程必须开放给Windows。

应用程序之间也可以互发消息,PostMessage是把一个消息放到其他程序的消息队列中,如图4。4中箭头d所示,目标程序收到了这条消息就把它放入该程序的消息队列去处理;而SendMessage则越过消息队列直接调用目标程序的窗口过程(如图4。4中箭头I所示),窗口过程返回以后才从SendMessage返回(如图4。4中箭头II所示)。

窗口过程是由Windows回调的,Windows又是怎么知道往哪里回调呢?答案是我们在调用RegisterClassEx函数的时候告诉了Windows。



 
来源:电子工业出版社 作者:罗云彬 上一页         回书目         下一页          
上一页         回书目         下一页          
  


第4章 第一个窗口程序


4。2 分析窗口程序(1)

    
了解了消息驱动体系的工作流程以后,让我们来分析如何用Win32汇编实现这一切,本节和下一节将详细分析FirstWindow源程序。

4。2。1  模块和句柄

1。 模块的概念

一个模块代表的是一个运行中的exe文件或dll文件,用来代表这个文件中所有的代码和资源,磁盘上的文件不是模块,装入内存后运行时就叫做模块。一个应用程序调用其他DLL中的API时,这些DLL文件被装入内存,就产生了不同的模块,为了区分地址空间中的不同模块,每个模块都有一个惟一的模块句柄来标识。

很多API函数中都要用到程序的模块句柄,以便利用程序中的各种资源,所以在程序的一开始就先取得模块句柄并存放到一个全局变量中可以省去很多的麻烦,在Win32中,模块句柄在数值上等于程序在内存中装入的起始地址。

取模块句柄使用的API函数是GetModuleHandle,它的使用方法是:

        invoke  GetModuleHandle;lpModuleName

lpModuleName参数是一个指向含有模块名称字符串的指针,可以用这个函数取得程序地址空间中各个模块的句柄,例如,如果想得到User32。dll的句柄以便使用其中包含的图标资源,那么可以如下使用:

szUserDll       db  'User32。dll';0

                    …

                    invoke  GetModuleHandle;addr szUserDll

                    。if     eax

                            mov hUserDllHandle;eax

                    。endif

                    …

如果使用参数NULL调用GetModuleHandle,那么得到的是调用者本模块的句柄,我们的源程序中就是这样使用的:

invoke  GetModuleHandle;NULL

mov     hInstance;eax

可以注意到,把返回的句柄放到了hInstance变量里而并不是放在hModule中,为什么是 hInstance呢? Instance是“实例”,它的概念来自于Win16,Win16中不同运行程序的地址空间并非是完全隔离的,一个可执行文件运行后形成“模块”,多次加载同一个可执行文件时,这个“模块”是公用的,为了区分多次加载的“拷贝”,就把每个“拷贝”叫做实例,每个实例均用不同的“实例句柄”(hInstance)值来标识它们。

但在Win32中,程序运行时是隔离的,每个实例都使用自己私有的4 GB空间,都认为自己是惟一的,不存在一个模块的多个实例的问题,实际上在Win32中,实例句柄就是模块句柄,但很多API原型中用到模块句柄的时候使用的名称还是沿用hInstance,所以我们还是把变量名称取为hInstance。

在C语言的编程中,hInstance通过WinMain由系统传入,WinMain的原型是:
WinMain(hInstance;hPrevInstance;lpzCmdParam;nCmdShow),程序不用自己去获得hInstance,但在Win32汇编中必须自己获取,如果不了解hModule就是hInstance的话,就无法得知如何得到hInstance,因为并没有一个类似于GetInstanceHandle之类的API函数。

2。 句柄是什么

随着分析的深入,句柄(handle)一词也出现得频繁了起来,“句柄”是什么呢?句柄只是一个数值而已,它的值对程序来说是没有意义的,它只是Windows用来表示各种资源的编号而已,所以只有Windows才知道怎么使用它来引用各种资源。 

举例说明,屏幕上已经有10个窗口,Windows把它们从1到10编号,应用程序又建立了一个窗口,现在Windows把它编号为11,然后把11当做窗口句柄返回给应用程序,应用程序并不知道11代表的是什么,但在操作窗口的时候,把11当做句柄传给Windows,Windows自然可以根据这个数值查出是哪个窗口。当该窗口关闭的时候,11这个编号作废。第二次运行的时候,如果屏幕上现有5个窗口,那么现在句柄可能就是6了,所以,应用程序并不用关心句柄的具体数值是多少。打个比方,可以把句柄当做是商场中寄放书包时营业员给的纸条,纸条上的标记用户并不知道是什么意思,但把它交还给营业员的时候,她自然会找到正确的书包。

Windows中几乎所有的东西都是用句柄来标识的,文件句柄、窗口句柄、线程句柄和模块句柄等,同样道理,不必关心它们的值究竟是多少,拿来用就是了!

4。2。2  创建窗口

在创建窗口之前,先要谈到“类”。“类”的概念读者都不陌生,主要是为了把一组物体的相同属性归纳整理起来封装在一起,以便重复使用,在“类”已定义的属性基础上加上其他个性化的属性,就形成了各式各样的个体。

Windows中创建窗口同样使用这样的层次结构。首先定义一个窗口类,然后在窗口类的基础上添加其他的属性建立窗口。不用一步到位的办法是因为很多窗口的基本属性和行为都是一样的,如按钮、文本输入框和选择框等,对这些东西Windows都预定义了对应的类,使用时直接使用对应的类名建立窗口就可以了。只有用户自定义的窗口才需要先定义自己的类,再建立窗口。这样可以节省资源。

1。 注册窗口类

建立窗口类的方法是在系统中注册,注册窗口类的API函数是RegisterClassEx,最后的“Ex”是扩展的意思,因为它是Win16中RegisterClass的扩展。一个窗口类定义了窗口的一些主要属性,如:图标、光标、背景色、菜单和负责处理该窗口所属消息的函数。这些属性并不是分成多个参数传递过去的,而是定义在一个WNDCLASSEX结构中,再把结构的地址当参数一次性传递给RegisterClassEx,WNDCLASSEX是WNDCLASS结构的扩展。

WNDCLASSEX的结构定义为:

WNDCLASSEX STRUCT

  CbSize             DWORD      ?     ;结构的字节数

  Style              DWORD      ?     ;类风格

  LpfnWndProc            DWORD      ?     ;窗口过程的地址

  CbClsExtra         DWORD      ?

  CbWndExtra         DWORD      ?

  HInstance          DWORD      ?     ;所属的实例句柄

  HIcon              DWORD      ?     ;窗口图标

  HCursor                DWORD      ?     ;窗口光标

  HbrBackground      DWORD      ?     ;背景色

  LpszMenuName       DWORD      ?     ;窗口菜单

  LpszClassName      DWORD      ?     ;类名字符串的地址

  HIconSm                DWORD      ?     ;小图标

WNDCLASSEX ENDS

在FirstWindow程序中,注册窗口类的代码是:

    local   @stWndClass:WNDCLASSEX  ;定义一个WNDCLASSEX结构

    …

 

    invoke  RtlZeroMemory;addr @stWndClass;sizeof @stWndClass

    invoke  LoadCursor;0;IDC_ARROW

    mov     @stWndClass。hCursor;eax

    push        hInstance

    pop     @stWndClass。hInstance

    mov     @stWndClass。cbSize;sizeof WNDCLASSEX

    mov     @stWndClass。style;CS_HREDRAW or CS_VREDRAW

    mov     @stWndClass。lpfnWndProc;offset _ProcWinMain

    mov     @stWndClass。hbrBackground;COLOR_WINDOW + 1

    mov     @stWndClass。lpszClassName;offset szClassName

    invoke  RegisterClassEx;addr @stWndClass



 
来源:电子工业出版社 作者:罗云彬 上一页         回书目         下一页          
上一页         回书目         下一页          
  


第4章 第一个窗口程序


4。2 分析窗口程序(2)

    
程序定义了一个WNDCLASSEX结构的变量@stWndClass,用RtlZeroMemory将它填为全零(局部变量初始化的重要性在第3章中已经强调过),再填写结构的各个字段,这样,没有赋值的部分就保持为0,结构各字段的含义如下:

●   hIcon——图标句柄,指定显示在窗口标题栏左上角的图标。Windows已经预定义了一些图标,同样,程序也可以使用在资源文件中定义的图标,这些图标的句柄可以用LoadIcon函数获得。例子程序没有用到图标,所以Windows给窗口显示了一个默认的图标。

●   hCursor——光标句柄,指定了鼠标在窗口中的光标形状。同样,Windows也预定义了一些光标,可以用LoadCursor获取它们的句柄,IDC_ARROW是Windows预定义的箭头光标,如果想使用自定义的光标,也可以自己在资源文件中定义。

●   lpszMenuName——指定窗口上显示的默认菜单,它指向一个字符串,描述资源文件中菜单的名称,如果资源文件中菜单是用数值定义的,那么这里使用菜单资源的数值。窗口中的菜单也可以在建立窗口函数CreateWindowEx的参数中指定。如果在两个地方都没有指定,那么建立的窗口上就没有菜单。

●   hInstance——指定要注册的窗口类属于哪个模块,模块句柄在程序开始的地方已经用GetModuleHandle函数获得。

●   cbSize——指定WNDCLASSEX结构的长度,用sizeof伪操作来获取。很多Win32 API参数中的结构都有cbSize字段,它主要是用来区分结构的版本,当以后新增了一个字段时,cbSize就相应增大,如果调用的时候cbSize还是老的长度,表示运行的是基于旧结构的程序,这样可以防止使用无效的字段。

●   style——窗口风格。CS_HREDRAW和CS_VREDRAW表示窗口的宽度或高度改变时是否重画窗口。比较重要的是CS_DBLCLKS风格,指定了它,Windows才会把在窗口中快速两次单击鼠标的行为翻译成双击消息WM_LBUTTONDBLCLK发给窗口过程。笔者就曾经忘了指定它,结果怎么也搞不出双击消息来。

●   hbrBackground——窗口客户区的背景色。前面的hbr表示它是一个刷子(Brush)的句柄,“刷子”一词形象地表示了填充一个区域的着色模式。Windows预定义了一些刷子,如BLACK_BRUSH和WHITE_BRUSH等,可以用下列语句来得到它们的句柄:

      invoke   GetObjectStock; WHITE_BRUSH

但在这里也可以使用颜色值,Windows已经预定义了一些颜色值,分别对应窗口各部分的颜色,如COLOR_BACKGROUND,COLOR_HIGHLIGHT,COLOR_MENU和COLOR_WINDOW等,使用颜色值的时候,Windows规定必须在颜色值上加1,所以程序中的指令是:

mov      @stWndClass。hbrBackground;COLOR_WINDOW + 1

●   lpszClassName——指定程序员要建立的类命名,以便以后用这个名称来引用它。这个字段是一个字符串指针,在程序里,它指向“MyClass”字符串。

●   cbWndExtra和cbClsExtra——分别是在Windows内部保存的窗口结构和类结构中给程序员预留的空间大小,用来存放自定义数据,它们的单位是字节。不使用自定义数据的话,这两个字段就是0。

●   lpfnWndProc——最重要的参数,它指定了基于这个类建立的窗口的窗口过程地址。通过这个参数,Windows就知道了在DispatchMessage函数中把窗口消息发到哪里去,一个窗口过程可以为多个窗口服务,只要这些窗口是基于同一个窗口类建立的。Windows中不同应用程序中的按钮和文本框的行为都是一样的,就是因为它们是基于相同的Windows预定义类建立的,它们背后的窗口过程其实是同一段代码。

结构中的style表示窗口的风格,Windows已经有一些预定义的值,它们是以CS(Class Style的缩写)开始的标识符,如表4。1所示。

表4。1  一些窗口类的style预定义值

预定义值
 16进制值
 对应二进制位
 
CS_VREDRAW
 00000001h
 0
 
CS_HREDRAW
 00000002h
 1
 
CS_KEYCVTWINDOW
 00000004h
 2
 
返回目录 上一页 下一页 回到顶部 7 5
未阅读完?加入书签已便下次继续阅读!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!