⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 003.txt

📁 会变语言实现的一些程序
💻 TXT
📖 第 1 页 / 共 5 页
字号:

……

 

结构名   ends

上面的WNDCLASS结构定义用汇编的格式来表示就是:

WNDCLASS   struct

 

Style DWORD   ?

LpfnWndProc DWORD   ?

cbClsExtra DWORD   ?

cbWndExtra   DWORD   ?

hInstance DWORD   ?

hIcon DWORD   ?

hCursor DWORD   ?

hbrBackground DWORD   ?

lpszMenuName DWORD   ?

lpszClassName DWORD   ?

 

WNDCLASS   ends

和大部分的常量一样,几乎所有API所涉及的数据结构在Windows.inc文件中都已经有定义了。要注意的是,定义了数据结构实际上只是定义了一个“样板”,上面的定义语句并不会在哪个段中产生数据,和Word中使用各种“信纸”与“文书”等模板类似,定义了数据结构以后就可以多次在源程序中用这个“样板”当做数据类型来定义数据,使用数据结构在数据段中定义数据的方法如下:

  .data?

stWndClass   WNDCLASS   <>


或者:

  .data

stWndClass   WNDCLASS   <1,1,1,1,1,1,1,1,1,1>

  ……

这个例子定义了一个以WNDCLASS为结构的变量stWndClass,第一段的定义方法是未初始化的定义方法,第二段是在定义的同时指定结构中各字段的初始值,各字段的初始值用逗号隔开,在这个例子中10个字段的初始值都指定为1。

在汇编中,数据结构的引用方法有好几种,以上面的定义为例,如果要使用stWndClass中的lpfnWndProc字段,最直接的办法是:

mov eax,stWndClass.lpfnWndProc

它表示把lpfnWndProc字段的值放入eax中去,假设stWndClass在内存中的地址是403000h,这句指令会被编译成mov eax,[403004h],因为lpfnWndProc是stWndClass中的第二个字段,第一个字段是dword,已经占用了4字节的空间。

在实际使用中,常常有使用指针存取数据结构的情况,如果使用esi寄存器做指针寻址,可以使用下列语句完成同样的功能:

mov esi,offset stWndClass

mov eax,[esi + WNDCLASS.lpfnWndProc]

注意:第二句是[esi + WNDCLASS.lpfnWndProc]而不是[esi + stWndClass.lpfnWndProc],因为前者会被编译成mov eax,[esi+4],而后者会被编译成mov eax,[esi+403004h],后者的结果显然是错误的!如果要对一个数据结构中的大量字段进行操作,这种写法显然比较烦琐,MASM还有一个用法,可以用assume伪指令把寄存器预先定义为结构指针,再进行操作:

mov esi,offset stWndClass

assume  esi:ptr WNDCLASS

mov eax,[esi].lpfnWndProc


assume  esi:nothing

这样,使用寄存器也可以用逗点引用字段名,程序的可读性比较好。这样的写法在最后编译成可执行程序的时候产生同样的代码。注意:在不再使用esi寄存器做指针的时候要用assume esi:nothing取消定义。

结构的定义也可以嵌套,如果要定义一个新的NEW_WNDCLASS结构,里面包含一个老的WNDCLASS结构和一个新的dwOption字段,那么可以如下定义:

NEW_WNDCLASS struct

 

DwOption dword ?

OldWndClass   WNDCLASS  <>

 

NEW_WNDCLASS ends

假设现在esi是指向一个NEW_WNDCLASS的指针,那么引用里面嵌套的oldWndClass中的lpfnWndProc字段时,就可以用下面的语句:

mov eax,[esi].oldWndClass.lpfnWndProc

结构的嵌套在Windows的数据定义中也常有,比如在第13章13.3节中使用的DEBUG_EVENT结构中竟然使用了4层数据结构的嵌套。 熟练掌握数据结构的使用对Win32汇编编程是很重要的!



3.3.5  变量的使用

1. 以不同的类型访问变量

这个话题有点像C语言中的数据类型强制转换,C语言中的类型转换指的是把一个变量的内容转换成另外一种类型,转换过程中,数据的内容已经发生了变化,如把浮点数转换成整数后,小数点后的内容就丢失了。在MASM中以不同的类型访问不会对变量造成影响。

举一个简单的例子,先以db方式定义一个缓冲区:

szBuffer   db   1024 dup (?)

然后从其他地方取得了数据,但数据的格式是以字方式组织的,要处理数据,最有效的方法是两个字节两个字节地处理,但如果在程序中把szBuffer的值放入ax:

mov ax,szBuffer

编译器会报一个错:

error A2070: invalid instruction operands

意思是无效的指令操作,为什么呢?因为szBuffer是用db定义的,而ax的尺寸是一个word,等于两个字节,尺寸不符合。MASM中,如果要用指定类型之外的长度访问变量,必须显式地指出要访问的长度,这样,编译器忽略语法上的长度检验,仅使用变量的地址。使用的方法是:

类型 ptr 变量名

类型可以是byte,word,dword,fword,qword,real8和real10。如:

mov ax,word ptr szBuffer

mov eax,dword ptr szBuffer

上述语句能通过编译,当然,类型必须和操作的寄存器长度匹配。在这里要注意的是,指定类型的参数访问并不会去检测长度是否溢出,看下面一段代码:

    .data

bTest1   db   12h

wTest2   dw   1234h

dwTest3 dd   12345678h


 

  .code


  mov al,bTest1

  mov ax,word ptr bTest1

  mov eax,dword ptr bTest1


上面的程序片断,每一句执行后寄存器中的值是什么呢,mov al,bTest1这一句很显然使al等于12h,下面的两句呢,ax和eax难道等于0012h和00000012h吗?实际运行结果很“奇怪”,竟然是3412h和78123412h,为什么呢?先来看反汇编的内容:

;.data段中的变量

:00403000 12 34 12 78 56 34 12 ... 

    │ │    │

    │ │   └─→ dwTest3

  │ └──────→ wTest2

  └─────────→   bTest1

 

;.code段中的代码

:00401000 A000304000   mov al, byte ptr [00403000]

:00401005 66A100304000 mov ax, word ptr [00403000]

:0040100B A100304000 mov eax, dword ptr [00403000]

.data段中的变量是按顺序从低地址往高地址排列的,对于超过一个字节的数据,80386处理器的数据排列方式是低位数据在低地址,所以wTest2的1234h在内存中的排列是34h 12h,因为34h是低位。同样,dwTest3在内存中以78h 56h 34h 12h从低地址往高地址存放,在执行指令mov ax,word ptr bTest1的时候,是从bTest1的地址403000h处取一个字,其长度已经超过了bTest1的范围并落到了wTest2中,从内存中看,是取了bTest1的数据12h和wTest2的低位34h,在这两个字节中,12h位于低地址,所以ax中的数值是3412h。同样道理,看另一条指令:

mov eax,dword ptr bTest1

这条指令取了bTest1,wTest2的全部和dwTest3的最低位78h,在内存中的排列是12h  34h 12h 78h,所以eax等于78123412h。

这个例子说明了汇编中用ptr强制覆盖变量长度的时候,实质上是只用了变量的地址而禁止编译器进行检验,编译器并不会考虑定界的问题,程序员在使用的时候必须对内存中的数据排列有个全局概念,以免越界存取到意料之外的数据。

如果程序员的本意是类似于C语言的强制类型转换,想把bTest1的一个字节扩展到一个字或一个双字再放到ax或eax中,高位保持0而不是越界存取到其他的变量,可以用80386的扩展指令来实现。80386处理器提供的movzx指令可以实现这个功能,例如:

movzx ax,bTest1   ;例1

movzx eax,bTest1 ;例2

movzx eax,cl ;例3

movzx eax,ax ;例4

● 例1把单字节变量bTest1的值扩展到16位放入ax中。

● 例2把单字节变量bTest1的值扩展到32位放入eax中。

● 例3把cl中的8位值扩展到32位放入eax中。

● 例4把ax中的16位值扩展到32位放入eax中。

用movzx指令进行数据长度扩展是Win32汇编中经常用到的技巧。

2. 变量的尺寸和数量

在源程序中用到变量的尺寸和数量的时候,可以用sizeof和lengthof伪指令来实现,格式是:

sizeof   变量名、数据类型或数据结构名

lengthof   变量名、数据类型或数据结构名

sizeof伪指令可以取得变量、数据类型或数据结构以字节为单位的长度,lengthof可以取得变量中数据的项数。假如定义了以下数据:

stWndClass WNDCLASS <>

szHello   db ~Hello,world!~,0

dwTest dd 1,2,3,4


.code


mov   eax,sizeof stWndClass

mov   ebx,sizeof WNDCLASS

mov   ecx,sizeof szHello

mov   edx,sizeof dword

mov   esi,sizeof dwTest

执行后eax的值是stWndClass结构的长度40,ebx同样是40,ecx的值是13,就是“Hello,world!”字符串的长度加上一个字节的0结束符,edx的值是一个双字的长度:4,而esi则等于4个双字的长度16。

如果把所有的sizeof换成lengthof,那么eax会等于1,因为只定义了1项WNDCLASS,而ecx同样等于13,esi则等于4,而lengthof WNDCLASS和lengthof dword是非法的用法,编译程序会报错。

要注意的是,sizeof和lengthof的数值是编译时候产生的,由编译器传递到指令中去,上边的指令最后产生的代码就是:

mov   eax,40

mov   ebx,40

mov   ecx,13

mov   edx,4

mov   esi,16

 如果为了把Hello和World分两行定义,szHello是这样定义的:

szHello db   ~Hello~,0dh,0ah

  db   ~World~,0

那么sizeof szHello是多少呢?注意!是7而不是13,MASM中的变量定义只认一行,后一行db ~World~,0实际上是另一个没有名称的数据定义,编译器认为sizeof szHello是第一行字符的数量。虽然把szHello的地址当参数传给MessageBox等函数显示时会把两行都显示出来,但严格地说这是越界使用变量。虽然在实际的应用中这样定义长字符串的用法很普遍,因为如果要显示一屏幕帮助,一行是不够的,但要注意的是:要用到这种字符串的长度时,千万不要用sizeof去表示,最好是在程序中用lstrlen函数去计算。

 

3. 获取变量地址

获取变量地址的操作对于

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -