win32汇编-调用API

 

 

Win32API是用堆栈来传递参数的, 调用者把参数一个个压入堆栈, DLL中的函数程序再从堆栈中取出参数处理, 并在返回之前将堆栈中已经无用的参数丢弃。

在Microsoft发布的《Microsoft Win 32 Programmer's Reference》中定义了常用API的参数和函数声明, 先来看消息框函数的声明:

 

上述函数声明说明了MessageBox有4个参数, 它们分别是HWND类型的窗口句柄(hWnd) , LPCTSTR类型的要显示的字符串地址(Ip Text) 和标题字符串地址(Ip Caption) ,还有UINT类型的消息框类型(u Type) 。这些数据类型看起来很复杂, 但有一点是很重要的,对于汇编语言来说, Win 32环境中的参数实际上只有一种类型, 那就是一个32位的整数, 所有这些HWND, LPCTSTR和UINT实际上就是汇编中的dword(doubleword) , 之所以定义为不同的模样, 是为了说明其用途。可能是因为Windows是用C写成的吧, 或者是因为世界上的程序员用C语言的最多, Windows所有编程资料发布的格式都是用C格式的。

有了函数原型的定义以后,就是调用的问题了,Win32API调用中要把参数放入堆栈,顺序是最后一个参数最先进栈, 在汇编中调用MessageBox函数的方法是:
push uType
push lpcaption

push lpText
push hwnd
call MessageBox

在源程序编译链接成可执行文件后, call MessageBox语句中的MessageBox会被换成一个地址, 指向可执行文件中的导入表, 导入表中指向MessageBox函数的实际地址会在程序装入内存的时候, 根据User 32.dll在内存中的位置由Windows系统动态填入。

 

1.使用invoke语句

API是可以调用了, 另一个烦人的问题又出现了, Win 32的API动辄就是十几个参数,整个源程序一眼看上去基本上都是把参数压入堆栈的push指令, 参数的个数和顺序很容易搞错,由此引起的莫名其妙的错误源源不断,源程序的可读性看上去也很差。如果写的时候少写了一句push指令, 程序在编译和链接的时候都不会报错, 但在执行的时候必定会崩溃, 原因是堆栈对不齐了。

有没有解决的办法呢?最好是像C语言一样,能在同一句中打入所有的参数,并在参数使用错误的时候能够提示。

Microsoft终于做了一件好事, 在MASM中提供了一个伪指令实现了这个功能, 那就是invoke伪指令, 它的格式是:
invoke 函数名[,参数1][,参数2]......

对MessageBox的调用在MASM中可以写成:
invoke MessageBox, NULL, offset szText, offset szCaption, MB_OK

注意, invoke并不是80386处理器的指令, 而是一个MASM编译器的伪指令, 在编译的时候由编译器把上面的指令展开成我们需要的4个push指令和1个call指令, 同时, 进行参数数量的检查工作,如果带的参数数量和声明时的数量不符,编译器会报错:
error A2137:too few arguments to INVOKE

编译时看到这样的错误报告,首先要检查的是有没有少写了一个参数。由于对于不带参数的API调用, invoke伪指令的参数检查功能可有可无, 所以既可以用call API_Name这样的语法也可以用invoke API_Name这样的语法。

2.API函数的返回值

有的API函数有返回值, 如MessageBox定义的返回值是int类型的数, 返回值的类型对汇编程序来说也只有dword一种类型, 它永远放在eax中。如果要返回的内容不是一个eax 所能容纳的, Win32API采用的方法一般是eax中返回一个指向返回数据的指针, 或者在调用参数中提供一个缓冲区地址,干脆把数据直接返回到缓冲区中去。

3.函数的声明

在调用API函数的时候, 函数原型也必须预先声明, 否则, 编译器会不认这个函数。invoke 伪指令也无法检查参数个数。

声明函数的格式是:

函数名 proto [距离] [语言] [参数1]:数据类型,[参数2]:数据类型,......   

句中的proto是函数声明的伪指令,

距离可以是NEAR, FAR, NEAR 16, NEAR 32, FAR 16或FAR 32, Win 32中只有一个平坦的段, 无所谓距离, 所以在定义时是忽略的语言类型就是.model那些类型, 如果忽略, 则使用.model定义的默认值

后面就是参数的列表了, 由于Win32API仅仅使用dword类型的参数, 所以绝大多数的
数据类型都是dword, 另外对于编译器来说, 它只关心参数的数量, 参数的名称在这里是“无用”的,仅是为了可读性而设置的,可以省略掉,所以下面两句消息框函数的定义实际上是一样的:

在Win 32环境中, 和字符串相关的API共有两类, 分别对应两个字符集:一类是处理ANSI字符集的, 另一类是处理Unicode字符集的。前一类函数名字的尾部带一个“A”字符, 处理Unicode的则带一个“W”字符。我们比较熟悉的ANSI字符串是以NULL结尾的一串字符数组, 每一个ANSI字符占一个字节宽。对于欧洲语言体系, ANSI字符集已经足够了, 但对于有成千上万个不同字符的几种东方语言体系来说, Unicode字符集更有用。每一个Unicode字符占两个字节的宽度, 这样一来就可以同时定义65536个不同的字符了。

 

虽然《Microsoft Win 32 Programmer's Reference》中只有一个MessageBox定义, 从MSDN 上查询也是如此, 但User 32.dll中确实没有MessageBox, 而只有MessageBoxA和MessageBox W, 那么为什么在源代码中还是可以使用MessageBox呢?这是因为在程序的头文件user32.inc中有一句:
MessageBox equ <MessageBoxA>

它把MessageBox偷梁换柱变成了MessageBoxA。在源程序中继续沿用MessageBox是为了程序的可读性, 以及保持和手册的一致性, 但对于编译器来说, 实际是在使用MessageBoxA。
并不是每个Win 32系统都支持W系列的API, 在Windows 9x系列中, 对Unicode是不支持的, 绝大多数的API只有ANSI版本(为数不多的几个例外是存在MessageBox W等函数, 它们让Unicode版本的程序在检测到系统不支持的时候, 能有机会用消息框提示用户并退出) , 只有WindowsNT系列才对Unicode完全支持。为了编写在几个平台中通用的程序,一般应用程序都使用ANSI版本的API函数集。

为了使程序更有移植性, 在源程序中一般不直接指明使用Unicode还是ANSI版本, 而是使用宏汇编中的条件汇编功能来统一替换, 如在源程序中使用MessageBox,但在头文件中定义:

if UNICODE

  MessageBox equ <MessageBox w>

else
  MessageBox equ <MessageBoxA>

endif
所有涉及版本问题的API都可以按此方法定义, 然后在源程序的头部指定UNICODE=1或UNICODE=0,重新编译后就能产生不同的版本。

 

4.include语句

对于所有要用到的API函数, 在程序的开始部分都必须预先声明, 但这个步骤显然是比较麻烦的,为了简化操作,可以采用各种语言通用的解决办法,就是把所有的声明预先放在一个文件中, 在用到的时候再用include语句包含进来。现在回到Win32 HelloWorld程序,这个程序用到了两个API函数:MessageBox和ExitProcess, 它们分别在User 32.dll和Kernel 32.dll中, 在MASM 32工具包中已经包括了所有DLL的API函数声明列表, 每个DLL 对应<DLL名.inc>文件, 在源程序中只要使用include语句包含进来就可以了:
include user32.inc
include kernel32.inc

当用到其他的API函数时, 只需相应增加对应的include语句。include语句还用来在源程序中包含其他文件, 当多个源程序用到相同的函数定义、常量定义, 甚至源代码时, 可以把相同的部分写成一个文件, 然后在不同的源程序中用include 语句包含进来。
编译器对include语句的处理仅是简单地把这一行用指定的文件内容替换掉而已。include语句的语法是:
  include    文件名

或    include    <文件名>

当遇到要包括的文件名和MASM的关键字同名等可能会引起编译器混淆的情况时, 可以用“<>”将文件名括起来。

 

5.includelib语句

在DOS汇编中, 使用中断调用系统功能是不必声明的, 处理器自己知道到中断向量表中去取中断地址。在Win 32汇编中使用API函数, 程序必须知道调用的API函数存在于哪个DLL中, 否则, 操作系统必须搜索系统中存在的所有DLL, 并且无法处理不同DLL中的同名函数, 这显然是不现实的, 所以, 必须有个文件包括DLL库正确的定位信息, 这个任务是由导入库来实现的。
在使用外部函数的时候, DOS下有函数库的概念, 那时的函数库实际上是静态库, 静态库是一组已经编写好的代码模块,在程序中可以自由引用,在源程序编译成目标文件,最后要链接成可执行文件的时候, 由link程序从库中找出相应的函数代码, 一起链接到最后的可执行文件中。DOS下C语言的函数库就是典型的静态库。库的出现为程序员节省了大量的开发时间,缺点就是每个可执行文件中都包括了要用到的相同函数的代码,占用了大量的磁盘空间,在执行的时候,这些代码同样重复占用了宝贵的内存。

Win 32环境中, 程序链接的时候仍然要使用函数库来定位函数信息, 只不过由于函数代码放在DLL文件中, 库文件中只留有函数的定位信息和参数数目等简单信息, 这种库文件叫做导入库, 一个DLL文件对应一个导入库, 如User 32.dll文件用于编程的导入库是User 32.lib,MASM 32工具包中包含了所有DLL的导入库。
为了告诉链接程序使用哪个导入库,使用的语句是:

  includelib 库文件名

或    includelib <库文件名>

与include的用法一样, 在要包括让编译器混淆的文件名时加方括号。Win32 Helloworld 程序用到的两个API函数MessageBox和ExitProcess分别在User 32.dll和Kernel 32.dl中, 那么在源程序使用的相应语句为:
includelib user32.lib
includelib kernel32.lib
和include语句的处理不同, include lib不会把.lib文件插入到源程序中, 它只是告诉链接器在链接的时候到指定的库文件中去找API函数的位置信息而已。

 

API参数中的等值定义

再回过头来看显示消息框的语句:
invoke MessageBox, NULL, offset sz Text, offset sz caption, MB_OK
在uType这个参数中使用了MB_OK, 这个MB_OK是什么意思呢, 先来看《Microsoft Win 32 Programmer's Reference》中的说明:

uType 定义对话框的类型,这个参数可以是以下标志的合集:
要定义消息框上显示按钮,用下面的某一个标志:
MB_ABORT RETRY IGNORE 消息框有三个按钮:“终止”,“重试”和“忽略”

MB_HELP 消息框上显示一个“帮助”按钮, 按下后发送WM_HELP消息

MB_OK 消息框上显示一个“确定”按钮,这是默认值
MB_OK CANCEL 消息框上显示两个按钮:“确定”和“取消”
MB_RETRYCANCEL 消息框上显示两个按钮:“重试”和“忽略”
MB_YESNO 消息框上显示两个按钮:“是”和“否”
MB_YESNOCANCEL 消息框上显示三个按钮:“是”、“否”和“取消”

要在消息框中显示图标,用下面的某一个标志:

MB_ICONWARNING 显示惊叹号图标

MB_ICONINFORMATION ·显示消息图标

MB_ICONASTERISK 显示危险图标

MB_ICONQUESTION 显示问号图标

MB_ICONSTOP 显示停止图标

这些是uType参数说明中的一小半, 可以看出, 参数中可以用的值有很多种, 让我们换一个值试试看,把语句改为:
invoke MessageBox, NULL, offset szText,offset szCaption,MB_ICON WARNING or MB_YES NO

再编译执行看,屏幕上出现了一个不一样的消息框,如图所示。

 

和参数说明中的一样!消息框中出现了一个惊叹号图标,按钮也变成了“是”和“否”两个按钮!MB_ICON WARNING和MB_YES NO等参数究竟是什么意思呢, MASM中显然没有这样的预定义, 让我们先来找Visual C++的头文件, 在Win User.h中可以找到下面一段:

 

显然, MB_YESNO就是4, MB_ICONWARNING就是30h, 默认的MB_OK就是0,Win32API的参数使用这样的定义方法是为了免除程序员死记数值定义的麻烦。在编写Win 32汇编程序时, MASM32工具包中的Windows.inc也包括了所有这些参数的定义, 只要在程序的开头包含这个定义文件:
include windows.inc

就可以方便地完全按照API手册来使用Win32函数。
打开\masm 32\include目录下的Windows.inc查看一下, 可以发现整个文件总共有两万六千多行, 包括了几乎所有的Win32API参数中的常量和数据结构定义。正是有了这个文件中详尽的定义, Win32ASM才得以流行起来, 试想一下, 哪个程序员愿意每使用一个API语句,

就到函数手册中去看参数, 然后到Microsoft发布的Visual C++的头文件中去找对应的数值,再应用到汇编源程序中?这样会有80%以上的时间花在做无用功上(最后还是要骂Microsoft 为什么不提供汇编格式的头文件, 毕竟MASM 32工具包不是Microsoft出的) 。
有时候由于版本的原因, 当使用最新的API手册时, 会发现有些参数使用的常量在Windows.inc中并没有定义, 这下惨了, 谁都不知道类似于MB_XXX YYY的东西代表什么数值, Microsoft的《Microsoft Programmer's Reference》手册中从来就不会把参数对应的数值写进去。遇到这种情况, 只有拿出最原始的办法了, 就是到最新的Visual C++或SDK的include 目录中去, 在C语言格式的.h头文件中把定义找出来, 然后自行增补到Windows.inc中去。如果这样也找不到定义值的话, 那只好放弃使用这个API了。

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2023-10-11 14:08  天子骄龙  阅读(492)  评论(0编辑  收藏  举报