13.3 使用Delphi 的BASM
由于Delphi 是基于真正编译器的编程环境,因此可以把汇编语言代码嵌入Object Pascal 的过程和函数中。这种能力主要得益于Delphi 内建了汇编程序(BASM)。在学习BASM 之前,首先应该清楚在Delphi程序中什么时候需要用汇编语言。虽然,能嵌入汇编语言代码是很让人兴奋的事情;但有时,使用BASM也会变成坏事。不过,如果能遵守下列的BASM 的规则,就会写出更好、更简捷而且更可移植的代码。
• 只要能够利用Object Pascal 语言实现的事情就不要用汇编语言完成。例如:不要用汇编语言编写串口通信的例程,因为Win32 API 提供了串口通信的函数。
• 不要过分依赖汇编语言来优化程序。汇编语言代码虽然比Object Pascal 语言代码稍快一些,但在可读性和可维护性上要差许多。另外,Delphi 的优化编译器能使代码更优化,其效果并不亚于手工编写的汇编语言代码。
• 应该总是对汇编语言代码进行注释。因为,代码可能会被别的程序员阅读;而且,在没有注释
的情况下,即使自己读起来也会感到困难。
• 不要使用B A S M 来访问机器硬件。虽然Windows 95/98 对此宽容些,但Windows NT 对此绝对禁止。
• 尽量把汇编语言代码封装在Object Pascal 语言的过程或函数中。这样会使代码可读性更强,并且更易于移植到其他开发平台。
注意本章并不教你学习汇编语言,而是在假设你已经熟悉汇编语言的情况下,学习在Delphi程序中如何使用汇编语言。
而且,如果你曾经在Delphi 1 中使用过汇编语句,请记住在3 2 位的Delphi 里中,BASM 是
全新的事物。新的开发平台对于原有的16 位版本Delphi 的BASM 代码,要求全部重写成32 位的
BASM 代码。事实上,我们还是尽可能少用BASM 为好。
13.3.1 BASM 是如何工作的
在Delphi 程序中使用汇编语句要比想像的简单。事实上,它只需要先键入关键字asm ,然后直接输入汇编语句,最后加上end 就行了。如下面代码所示:
var
  i:integer;
begin
  i:=0
  asm
    mov eax,i
    inc eax
    mov i,eax
  end;
{ i 现在已加1 }
以上程序片断声明了一个变量i 并把初始化为0 。然后,将变量i 移至寄存器e a x 中,把这个寄存器加上1 后,再把寄存器的值返还给变量i 。示例不仅演示了BASM 是多么容易使用,而且还演示了汇编语句中如何访问Pascal 的变量。
13.3.2 简易的参数访问
不仅访问全局变量以及过程或函数中声明的局部变量是非常容易的,而且,访问传递给过程或函
数的参数也同样很方便。请看下列代码:
procedure Foo(I:integer);
begin
  {一些代码}
  asm
    mov eax,I
    inc eax
    mov I,eax
  end;
{I 已经加1 }
{ 其他代码}
end;
直接通过参数的名称来访问参数的能力是很重要的,这样就不需要再通过ebp 寄存器了。在一般
的汇编语言过程中,必须用[ ebp + 4 ]代表I 。
注意当使用BASM 去访问一个传递给过程的参数时,可以只利用它的名称访问,而不需要通过ebp 寄存器的偏移来访问。这使代码更易于维护。
13.3.3 var 声明的参数
记住,如果一个参数是在一个函数或过程参数列表中被用var 声明的,它实际上是一个指针而不是值。这就是说,如果要在汇编代码中引用这个参数,一定要明确,它是一个3 2 位的指针而不是一个值。
下面代码片断中阐述了如何利用汇编代码访问用var 声明的变量:
procedure Foo(var I:integer);
begin
  {一些代码}
  asm
    mov eax,I
    inc dword ptr [eax]
  end;
{I 已经加1 }
{ 其他代码}
end;
在Object Pascal 函数和过程中默认的调用约定是Register 。利用这种方法传递参数可以使代码更加优化。因为编译器把头三个32 位参数分别用eax 、edx 和ecx 寄存器来传递。参看下面声明:
function BlahBlah(I1,I2,I3:Integer):Integer;
可以认为参数I1 的值被存储在eax 中,I2 在edx 中,I3 在ecx 中。
再看另一个声明:
Procedure TSomeObject.SomeProc(s1,s2:pchar);
这里参数S1 的值被存储在eax 中,S2 在edx 中,隐含的self 参数被存储在ecx 中。
13.3.5 全汇编过程
Object Pascal 中允许用关键字asm 取代begin 作为一个函数或过程的开始符号来建立一个全汇编的过程。
例如:
function IncAnInt(I:Integer):Integer;
asm
  mov eax,I
  inc eax
end;
注意如果你还在迷恋于16 位代码的开发,应该知道Delphi 1 时代的assembler 指示字已经过时。它已被32 位的Delphi 编译器忽略。
上面这个函数的作用是把变量I 加1 ,由于变量的值放在eax 寄存器中,这也就是函数的返回值。表1 3 - 1 列出了各种类型的变量是怎么样返回的。
表13-1 返回值的返回方式
返回类型                                            返回方式
-------------------------------------------------------------------------------
C har, Byteal                                       寄存器 
SmallInt , Wordax                                   寄存器
Integer, LongWord,AnsiString, Pointer,class        eax 寄存器
Real48                                             eax 寄存器中是栈内返回值的指针
I n t 6 4                                          edx :eax 寄存器对
Single, Double , Extended, Comp                    栈首寄存器ST(0)
-----------------------------------------------------------------------------
注意一个ShortString 类型是作为一个指向栈内字符串临时实例的指针来返回的。
13.3.6 记录
B A S M 提供了一种灵活的手段访问Object Pascal 记录中的域,可以在B A S M 块中访问任意记录的域,其访问的语法是Rgister.Type.Field 。请看下列代码:
type
  TDumbRec=record
    i:integer; 
    c:char;
  end;
下面的函数中有一个TDumbRec 类型的参数:
procedure ManipulateRec(var DR:TDumbRec):
asm
  mov [eax].TDumbRec.i, 24
  mov [eax].TDumbRec.c, 's'
end;
你可能已注意到了,BASM 对记录域的访问非常简单。另一种方式是通过计算域的偏移量来获取或设置域的值。在BASM 中使用记录的地方使用这项技术,将会使BASM 更能适应数据类型的潜在变
化。代码是我手抄的,可能有误