WIN32汇编语言教程:第04章 第一个窗口程序 · 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

上页:第04章 第一个窗口程序 · 4.1 开始了解窗口(3) 下页:第04章 第一个窗口程序 · 4.2 分析窗口程序(2)

第04章 第一个窗口程序

版权所有 © 中山市飞娥软件工作室 证书:粤ICP备09170368号