编写inline汇编语言方法的关键技术
董占山
Borland公司1989年推出的Turbo Pascal 5.5把面向对象的技术引入Pascal&127;语言,1990年推出的Turbo
Pascal 6.0把内嵌式汇编语言(&127;inline&127;汇编语言&127;)&127;引入Pascal,1992年推出的Borland
Pascal 7.0系统,功能更为强大,集DOS&127;实模式、DOS保护模式和Windows模式三种编程平台于一体,把Pascal语言推到了一个更臻完美的境地,使Pascal不仅可以编写DOS实模式程序,而且可以用于编写DOS保护模式程序和Windows程序。本文重点讨论用inline汇编语言编写Pascal&127;对象方法的关键技术。
一、inline汇编语言
Turbo Pascal 6.0的inline汇编语言实现了Turbo汇编和Microsoft汇编支持的大部分语法,实际上可以认为inline汇编语言是由Turbo
Pascal编译器支持的汇编语言。inline汇编语言除支持DB、DW和DD汇编命令外,不支持EQU、PROC、&127;STRUC&127;、SEGMENT和MACRO等汇编命令。
在Pascal过程和函数中使用inline汇编语言,通过汇编语句asm实现。asm语句的语法如下:
asm
… { 汇编指令集 }
end;
inline汇编语句的格式与Turbo汇编相同,但其注释不是使用";",而是Pascal风格的{和}或(*和*)括起来。
在asm语句中使用寄存器的规则基本上同external过程与函数,必须保护BP&127;、SP、SS和DS寄存器,可以自由修改AX、BX、CX、DX、SI、DI、ES和标志寄存器,在asm语句入口处,BP指向当前堆栈,SP指向栈顶,SS含有栈段的段地址,DS&127;含有数据段的段地址。
inline汇编语言允许在汇编表达式中存取Turbo Pascal的符号,包括标号、常量、变量、过程和函数,另外,inline汇编语言还实现了三个特殊的符号@CODE&127;、@DATA、@RESULT,@CODE和@DATA分别表示当前的代码段和数据段,它们只能与&127;SEG操作符结合使用,@RESULT表示函数的结果变量。例如:
MOV AX,SEG @DATA
MOV DS,AX
有关inlinei汇编语言的详细说明请参阅Turbo Pascal的语言手册。
二、方法的调用约定
方法使用与普通过程和函数一样的约定,只是每个方法有一个附加的隐含参数self,它对应于一个与该方法的对象类型相同的var参数,self参数总是以32&127;位指针形式作为最后一个参数传递的,该指针指向一个对象的实例,通过该实例来调用方法。在返回时,对象必须象从栈中删除任何普通参数一样删除self参数。所有的方法调用均采用远调用(far)模式。
1.静态方法的调用
静态方法的调用完全遵照上述的调用约定,例如调用MyObj.GetReal,&127;使用以下代码:
les di,MyObj { 把TMyObj对象的实例MyObj的地址装入ES:DI }
push es { 段地址进栈 }
push di { 偏移地址进栈 }
call far ptr GetReal { 调用方法 }
2.虚拟方法的调用
当一个对象首次定义虚拟方法时,其数据域的最后两个字节就是虚拟方法表&127;(VMT)在数据段中的偏移量。当一个对象类型其祖先有VMT时,它也继承VMT&127;域。&127;从VMT偏移量8开始出现32位的虚拟方法指针表,以虚拟方法的声明顺序依次排放,对象类型的每个虚拟方法均有一个方法指针。为调用虚拟方法,必须先求得对象实例的VMT域的地址,然后从VMT&127;表中得到虚拟方法的入口地址。&127;例如,&127;调用&127;MyObj.GetWord需要如下的代码:
les di,MyObj { 把TMyObj对象的实例MyObj的地址装入ES:DI }
push es { 段地址进栈 }
push di { 偏移地址进栈 }
mov di,es:[di+71h] { 从VMT域得到VMT的偏移量 }
call dword ptr [di+12] { 调用GetWord }
三、引用对象数据域的技术
在对象方法的实现部分,如果使用inline汇编语句,若引用对象的数据域,存在引用域值与域地址的问题。下面分别介绍:
1.引用对象的域值
上节已经讲到,当调用对象的方法时,隐含地传递了一个指向对象实例的32位指针self,这是引用对象域的关键所在。在方法中引用对象的数据域时,首先要取self的地址,然后以此地址为基础进行寻址。
在对象方法中引用对象的域值时,应当使用与下面代码类似的代码:
lds si,self { 把对象实例的地址装入DS:SI }
mov ax,[si].X { 将对象实例地址指明的对象域X的值装入AX }
其中X为其对象的一个域。
2.引用对象的域地址
在对象方法中引用对象数据域的地址时,应当使用与下面代码类似的代码:
lds si, self { 把对象实例的地址装入DS:SI }
mov ax, si { 把对象实例的偏移量装入AX }
add ax, X { 求出对象域X的实际偏移地址 }
其中X为对象的一个域。
四、引用局部变量的技术
在方法体内定义的变量为局部变量,它们不是在数据区分配空间,而是在堆栈区分配空间。当执行方法时建立,随方法的返回而消失,要在对象的方法中使用这些局部变量,只能通过地址进行,其典型的程序段如下:
asm
lea di,X1 { 把局部变量X1的地址装入DI }
mov SEGSS:[di],ax { 把AX的内容装入SS:[DI]指定的内存区,即X1 }
end;
五、引用方法形参的技术
形式参数分为值参与变参,值参可以直接引用,变参只能通过地址引用。下面分别介绍:
1.值参的引用
值参用值传递或是用地址传递,取决于参数的类型和长度。一般来说,如果值参为1、2或4字节长,用值传递,否则压入值的指针,&127;然后过程或函数把其值拷入局部存储空间。浮点数是一个例外,Real、Single、Double、Extended和Comp作为6、4、8、10或8字节压入栈中,直接引用其值。
对于尺寸小于等于4字节的值参的引用比较简单,而对字符串或尺寸大于4字节的值参稍后再讲。值参可以直接引用其值,下面是直接引用一个字节型值参的方法:
asm mov al,order end; { 把形参order的值装入AL寄存器 }
2.变参的引用
在inline汇编语言中,存取变参的内容要先取出其地址,然后按地址去取值,例如:
asm
les bx, XA { 把变参XA的地址装入ES:BX }
mov ah, es:[bx] { 把AH的内容装入变参XA的第一个字节 }
end;
3.字符串值参的引用
字符串值参在inline汇编语句中使用时,被认为是一个局变变量,使用的方法如下:
asm
lea si,Str1 { 取值参Str1的偏移地址 }
les di,Str2 { 把变参Str2的地址装入ES:DI }
SEGSS lodsb { 通过栈段地址覆盖引用Str1 }
stosb
end;
其中Str1是值参型字符串,Str2是变参型字符串。
六、函数的返回值
在inline汇编语言中特别定义了一个符号@RESULT,表示函数的结果变量,&127;其类型依函数的返回类型而变化。下面是TMyObj对象的一个方法,它演示了如何使用@RESULT。
function TMyObj.GetWord: word;
begin
asm
lds si,self { 把对象的一个实例的地址装入DS:SI }
mov ax,[si].I { 取对象域I的值 }
mov @RESULT,ax { 返回结果 }
end;
end;
七、实例
下面是一个完整的例子,综合使用了应用inline汇编语言编写Pascal对象方法的各项技术。读者通过对例子的学习和解剖,不难用&127;inline&127;汇编语言编写自己的Pascal对象的方法。该程序在Turbo
Pascal 7.0&127;系统下编译通过。&127;如果是&127;TurboPascal 6.0,在调用虚拟方法时,其在VMT中偏移量应减去4个字节。
program InlineDemo;
uses Strings;
Type
PMyPtr = ^LongInt;
PMyObj = ^TMyObj;
TMyObj = object
I : word;
R : real;
P : PMyPtr;
TempStr : array[0..100] of char;
constructor init(IA:word;RA:real;PA:PMyPtr;ATempStr:PChar);
destructor done; virtual;
function GetWord: word; virtual;
function GetPtr: Pointer;
function GetReal: real;
function GetStr: PChar;
function GetAChar(order : byte): char;
Procedure GetAllField(var IA:word;var RA:real;
var PA:Pointer;var ATempStr:PChar;Var Achar: Char);
end;
constructor TMyObj.init(IA:word;RA:real;PA:PMyPtr;ATempStr:PChar);
begin
asm
les di,self { 把对象的一个实例的地址装入ES:DI }
cld
mov ax,IA { 取形参IA的值 }
stosw { 存到对象的域I }
mov ax,word ptr RA { 取形参RA的值的低字 }
stosw { 存到对象的域R的低字 }
mov ax,word ptr RA+2 { 取形参RA的值的中字 }
stosw { 存到对象的域R的中字 }
mov ax,word ptr RA+4 { 取形参RA的值的高字 }
stosw { 存到对象的域R的高字 }
mov ax,word ptr PA { 取形参PA的偏移量 }
stosw { 存到对象的域P的低字 }
mov ax,word ptr PA+2 { 取形参PA的段地址 }
stosw { 存到对象的域P的高字 }
push ds
lds si,ATempStr { 把形参ATempStr的地址装入DS:SI }
@2: lodsb { 装入一个字符 }
stosb { 存储一个字符 }
cmp al,0 { 是ATempstr的结束符吗? }
jz @3 { 是,转@3 }
jmp @2 { 不是,转@2 }
@3: pop ds
end;
end;
destructor TMyObj.done;
begin
end;
function TMyObj.GetWord: word;
begin
asm
lds si,self { 把对象的一个实例的地址装入DS:SI }
mov ax,[si].I { 取对象域I的值 }
mov @RESULT,ax { 返回结果 }
end;
end;
function TMyObj.GetPtr: Pointer;
begin
asm
lds si,self { 把对象的一个实例的地址装入DS:SI }
mov ax,word ptr [si].P { 取对象域P的低字 }
mov word ptr @RESULT,ax { 存入结果变量的低字 }
mov ax,word ptr [si].P+2 { 取对象域P的高字 }
mov word ptr @RESULT+2,ax { 存入结果变量的高字 }
end;
end;
function TMyObj.GetReal: real;
begin
asm
lds si,self { 把对象的一个实例的地址装入DS:SI }
mov ax,word ptr [si].R { 取对象域R的低字 }
mov word ptr @RESULT,ax { 存入结果变量的低字 }
mov ax,word ptr [si].R+2 { 取对象域R的中字 }
mov word ptr @RESULT+2,ax { 存入结果变量的中字 }
mov ax,word ptr [si].R+4 { 取对象域R的高字 }
mov word ptr @RESULT+4,ax { 存入结果变量的高字 }
end;
end;
function TMyObj.GetStr: PChar;
begin
asm
lds si,self { 把对象的一个实例的地址装入DS:SI }
add si,TempStr { 计算对象域TempStr的偏移地址 }
mov word ptr @RESULT,si { 将偏移地址存入结果变量的低字 }
mov word ptr @RESULT+2,ds { 将段地址存入结果变量的高字 }
end;
end;
function TMyObj.GetAChar;
begin
asm
lds si,self { 把对象的一个实例的地址装入DS:SI }
add si,TempStr { 计算对象域TempStr的偏移地址 }
xor ax,ax
mov al,order { 求字符在串中的位置 }
add si,ax
dec si { 达到字符的偏移地址 }
mov al,byte ptr [si] { 取字符 }
mov byte ptr @RESULT,al { 将字符存入结果变量 }
end;
end;
Procedure TMyObj.GetAllField(var IA:word;var RA:real;
var PA:Pointer;var ATempStr:PChar; var AChar : Char);
begin
asm
lds si,self { 把对象的一个实例的地址装入DS:SI }
push ds { 段地址入栈 }
push si { 偏移地址入栈 }
mov di,ds:[si+113] { 装入虚拟方法表(VMT)的偏移量 }
call dword ptr [di+12] { 调用虚拟方法GetWord }
cld
les di,IA { 把形参IA的地址装入ES:DI }
stosw
lds si,self
push ds
push si
call far ptr GetReal { 调用方法GetReal }
les di,RA { 把形参RA的地址装入ES:DI }
stosw { 存低字 }
mov ax,bx
stosw { 存中字 }
mov ax,dx
stosw { 存高字 }
lds si,self
push ds
push si
call far ptr GetPtr { 调用方法GetPtr }
les di,PA { 把形参PA的地址装入ES:DI }
stosw
mov ax,dx
stosw
lds si,self
push ds
push si
call far ptr GetStr { 调用方法GetStr }
les di,ATempStr { 把形参ATempStr的地址装入ES:DI }
stosw
mov ax,dx
stosw
mov al,2 { 取字符串的第二个字符 }
push ax
lds si,self
push ds
push si
call far ptr GetAChar { 调用方法GetAChar }
les di,AChar { 把形参AChar的地址装入ES:DI }
stosb
end;
end;
var
AMyObj : TMyObj;
MP : PMyPtr;
LI : LongInt;
I : word;
R : real;
P : Pointer;
MyStr : PChar;
TempStr : array[0..100] of char;
Ch : char;
begin
Writeln('-------------- input ---------------');
Write('Input a string : ');readln(TempStr);
Write('Input a Word integer : ');readln(I);
Write('Input a real : ');readln(R);
Write('Input a Longint : ');readln(LI);
Writeln;
Writeln('-------------- result ---------------');
new(MP);
MP^ := LI;
AMyObj.init(I,R,MP,TempStr);
AMyObj.GetAllField(I,R,P,MyStr,Ch);
Writeln('Word = ',I:10,^M^J,
'Real = ',R:10:4,^M^J,
'LongInt = ',longint(P^):15,^M^J,
'String = ',MyStr,^M^J,
'The second char of string = ',Ch);
Writeln('-------------- the end ---------------');
end.
©董占山Zhanshan Dong
|