3. 在高级语言里调用汇编函数
你可以使用在线汇编或者用汇编写整个子程序然后再连接到你的工程中。 如果你选择后者,建议你选择可以将高级语言直接编译成汇编的编译器。 这样你可以得到正确的函数调用原型。 所有的 C++ 编译器都能做这个工作。
传递参数的方法取决于调用形式:
调用方式 | 参数在堆栈里的次序 | 参数由谁来移去 |
_cdecl | 第一个参数在低位地址 | 调用者 |
_stdcall | 第一个参数在低位地址 | 子程序 |
_fastcall | 编译器指定 | 子程序 |
_pascal | 第一个参数在高位地址 | 子程序 |
函数调用原型和被编译器命名的函数名可能非常的复杂。 有很多不同的调用转换规则, 不同的编译器也互不兼容。 如果你从C++里调用汇编语言的子程序,最好的方法是将你的函数用 extern "C" 和 _cdecl 定义来做到兼容性和一致性。 汇编代码的函数名前面必须带一个下划线 (_) 并且在外面编译时加上大小写敏感的选项 (选项 -mx)。 例如:
; extern "C" int _cdecl square (int x); _square PROC NEAR ; 整型平方函数 PUBLIC _square MOV EAX, [ESP+4] IMUL EAX RET _square ENDP
如果你需要重载函数,重载操作符,方法,和其它 C++ 专有的东西,就必须先用 C++ 写好代码再用编译器编译成汇编代码以获得正确的连接信息和调用原型。这些细节随着编译器的不同而不同而且很少列出文档。 如果你希望汇编函数用其它的调用原型而不是 extern "C" 及 _cdecl,又可以被不同的编译器调用,那么你需要为每个编译器写一个名字。 例如重载一个 square 函数:
; int square (int x); SQUARE_I PROC NEAR ; 整型平方函数 @square$qi LABEL NEAR ; Borland 编译器的连接名字 ?square@@YAHH@Z LABEL NEAR ; Microsoft 编译器的连接名字 _square__Fi LABEL NEAR ; Gnu 编译器的连接名字 PUBLIC @square$qi, ?square@@YAHH@Z, _square__Fi MOV EAX, [ESP+4] IMUL EAX RET SQUARE_I ENDP ; double square (double x); SQUARE_D PROC NEAR ; 双精度浮点平方函数 @square$qd LABEL NEAR ; Borland 编译器的连接名字 ?square@@YANN@Z LABEL NEAR ; Microsoft 编译器的连接名字 _square__Fd LABEL NEAR ; Gnu 编译器的连接名字 PUBLIC @square$qd, ?square@@YANN@Z, _square__Fd FLD QWORD PTR [ESP+4] FMUL ST(0), ST(0) RET SQUARE_D ENDP
这个方法能够工作是因为所有这些编译器对重载的函数都缺省使用_cdecl 调用。 然而对于不同的编译器,甚至对方法(成员函数)的调用方式都不一样 (Borland 和 Gnu 编译器使用 _cdecl 方式,'this' 指针是第一个参数;而 Microsoft 使用 _stdcall 方式,'this' 指针放在 ecx 里)。
通常来说,当你使用了下列东西时,不要指望不同的编译器在目标文件级别可以兼容: long double,成员指针,虚机制,new,delete,异常,系统函数调用,以及标准库函数。
16 位模式DOS或Windows, C/C++ 的寄存器使用:
AX是16位返回值,DX:AX是32 位返回值,ST(0)是浮点返回值。寄存器 AX, BX, CX, DX, ES 和算术标志可以被过程改变; 其它的寄存器必须保存和恢复。 一个过程要不改变SI, DI, BP, DS 和 SS 的前提下才不会影响另一个过程。
32位模式Windows, C++ 和其它编程语言下的寄存器使用:
整型返回值放在 EAX, 浮点返回值放在 ST(0)。 寄存器 EAX, ECX, EDX (没有 EBX) 可以被过程修改; 其它的寄存器必须保留和恢复。段寄存器不能被改变,甚至不能被临时改变。 CS, DS, ES 和 SS 都指向平坦模式的段。 FS 被操作系统使用, GS 没有使用,但是被保留。 标记位可以在下面的限制下被过程改变: 方向标志缺省是0。方向标志可以暂时的修改, 但是必须在任何的调用或者返回前清除。
中断标志不能被清除。 浮点寄存器堆栈在过程入口处是空的,返回时也应该是空的,除了 ST(0) 被用于返回值的情况。 MMX 寄存器可以被改变但是在返回前或者在调用可能使用浮点运算的过程前必须用 EMMS 清一下。 所有的 XMM 寄存器都可以被过程修改。 在XMM 寄存器里的传递参数和返回值的描述在 Intel 的应用文档 AP 589。 一个过程可以在不改变EBX, ESI, EDI, EBP 和所有的段寄存器的前提下被另一个过程调用。