Turbo PASCAL与汇编语言混合编程技术
董占山
TURBO PASCAL是80年代开始流行的优秀的PASCAL编译系统,&127;被广大编程人员所使用,它以编译速度快、生成的目标代码高速和紧凑而著称。在大多数情况下,只使用TURBO
PASCAL即可以完成各种各样的程序编制,但是,在硬件接口程序、&127;实时控制程序及大规模浮点运算时,读需要用汇编语言来编程。虽然TURBO
PASCAL&127;提供了INLINE语句和命令,以及内在汇编程序(TURBO PASCAL 6.00),但这是远远不够的。本文详细讨论了TURBO
PASCAL与汇编语言混合编程的技术,并列举了大量的实例。
一、TURBO PASCAL的调用协定
TURBO PASCAL程序与外部汇编子程序混合编程时,要涉及到子程序的调用方式、函数或过程的参数传递方法和函数如何返回值的问题,现分述如下。
1.调用子程序的方式和子程序的返回方式
TURBO PASCAL程序在调用汇编子程序时,可以是近调用也可以是远调用,因此,TURBO
PASCAL程序在对汇编子程序进行调用时,根据调用方式的不同,&127;有两种不同的保存返回地址的方法:①近调用时,因是段内调用,仅将偏移地址IP入栈,占2字节;②远调用时,因是段间调用,要将代码段值CS和偏移地址IP入栈,占4字节。
在主程序中直接调用汇编子程序时,一般采用近调用,&127;汇编子程序采用近返回方式,用RET指令;在TURBO
PASCAL的单元中使用汇编子程序时分两种情况:①在单元接口部分说明的子程序,在汇编子程序中要用远返回,用RETF指令;&127;②在单元解释部分说明的子程序,汇编子程序要用近返回方式,用RET指令。
汇编子程序在运行结束后,为了能正确地返回到调用程序,&127;栈顶指针必须指向正确的返回地址,它通过在返回指令RETF(或RET)中给出参数入栈时所占的字节数的方法实现的。
2.参数传递的方法
TURBO PASCAL是利用堆栈向过程和函数传递参数的,&127;参数按从左到右的顺序被压入堆栈中,例如调用过程PROC(A,B,C
: INTEGER; VAR D)时,其堆栈情况见图1。
┌────────────┐
│+0E│ 参数A的值
│ ↑
│ ├────────────┤ │
参│+0C│ 参数B的值
│ │参
│ ├────────────┤ │
数│+0A│ 参数C的值
│ │数
│ ├────────────┤ │
压│ +8│ 参数D的段地址 │
│出
│ ├────────────┤ │
栈│ +6│ 参数D的偏移地址 │ │栈
│ ├────────────┤ │
顺│ +4│ 过程返回的段地址 │ │顺
│ ├────────────┤ │
序│ +2│ 过程返回的偏移地址 │ │序
↓ ├────────────┤ │
│ BP寄存器的值
│
└────────────┘
图1.TURBO PASCAL远调用汇编程序PROC的堆栈情况
TURBO PASCAL在调用子程序时,有两种传递参数的方法,即传值和传地址。&127;下面分别说明这两种参数传递方法。各种类型参数入栈的方法见表1。
(1)传值方式
在TURBO PASCAL的过程或函数的形式参数表中,以值参形式定义的参数,&127;且类型是记录、数组、字符串、指针等复合类型以外的各种类型,如字节型(BYTE)、&127;短整型(SHORTINT)、整型(INTEGER)、字型(WORD)、长整型(LONGINT)、字符型(CHAR)、布尔型(BOOLEAN)、实数型(REAL)等,TURBO
PASCAL在调用子程序时,直接将实参值依次从左到右顺序压入堆栈中,汇编子程序可以直接从堆栈中取得实参的值。
(2)传地址方式
在TURBO PASCAL的过程或函数的形式参数表中,以变量形式定义的参数,&127;及以记录、字符串、数组、指针等复合类型定义的值参,TURBO
PASCAL在调用子程序时,是将调用程序的实参地址依次按从左到右的顺序压入堆栈的。&127;汇编子程序从堆栈中取得实参的地址,即可得到参数的值。&127;同样汇编子程序可以把运算结果存放到对应的变量中,以便传回调用程序。
表1.各种类型参数入栈的方法
┌───────┬────┬─────┐
│形参类型 │传递方式│栈中字节数│
├───────┼────┼─────┤
│char,boolean │ │
│
│byte,shortint,│ 传值 │ 2
│
│integer,word │ │
│
├───────┼────┼─────┤
│longint,single│ 传值 │ 4
│
├───────┼────┼─────┤
│real │ 传值 │ 6
│
├───────┼────┼─────┤
│double │ 传值 │ 8
│
├───────┼────┼─────┤
│string,pointer│ 传地址 │ 4 │
│变量 │
│
│
└───────┴────┴─────┘
3. 函数返回值的传递
TURBO PASCAL函数返回值的传递方式根据函数返回值类型的不同而异,&127;有采用传地址的方式进行,也有采用寄存器方式进行,如采用传地址的方式,其地址(4&127;字节)首先入栈,然后才压入函数参数,最后压入函数的返回地址。各种函数返回类型的传递方式见表2。
表2.各种函数返回类型的传递方式
┌───────┬──────────┬──────┐
│ 函数返回类型 │ 返 回 方 式 │
所占字节数 │
├───────┼──────────┼──────┤
│boolean,byte, │ 在寄存器AL中 │ 1
│
│char,shortint │
│
│
├───────┼──────────┼──────┤
│word,integer │ 在寄存器AX中 │ 2
│
├───────┼──────────┼──────┤
│longint │ 高位在DX,低位在AX │ 4
│
├───────┼──────────┼──────┤
│real │由高到低在DX,BX,AX中│
6 │
├───────┼──────────┼──────┤
│pointer │段地址在DX,偏移在AX │ 4
│
├───────┼──────────┼──────┤
│string │ 在DS:SI指的地址中 │
不限 │
└───────┴──────────┴──────┘
二、汇编子程序的编写格式
根据TURBO PASCAL的调用协定,外部汇编子程序的通用编写格式如下:
TITLE 程序名
DOSSEG
LOCAL @@
.MODEL TPASCAL
.CODE
ASSUME CS:@CODE
PUBLIC 过程或函数名
过程或函数名:
PUSH BP
MOV BP,SP
…
POP BP
RETF 参数占堆栈字节数
END
上述汇编子程序是TURBO ASSEMBLER的格式,本文汇编子程序均采用这种格式。对此汇编子程序格式说明如下:
1.汇编模块要采用TPASCAL模式;
2.在汇编模块中,必须把TURBO PASCAL调用的过程或函数说明为PUBLIC属性;
3.子程序返回指令视具体情况而定,近调用用RET,远调用用RETF;
4.&127;返回指令后的参数是指该子程序形式参数表中所有参数入栈后所占堆栈的字节数;
5.汇编模块结束要写END。
三、TURBO PASCAL程序的编写格式
在TURBO PASCAL中,声明外部子程序的格式如下:
procedure prc(a, b : integer; var c : real);external;
function func(a, b : integer) : real; external;
即在通常的TURBO PASCAL过程或函数的声明后加上external关键字。&127;在声明了外部过程或函数的主程序或程序单元中,要用编译指令{$L},&127;把汇编好的目标模块加载进来。
在&127;TURBO &127;PASCAL&127;程序中使用外部汇编过程或函数时,&127;方法和一般的&127;TURBO
PASCAL过程和函数没有两样。
四、主程序中使用外部汇编子程序的典型例子分析
在TURBO PASCAL主程序中直接使用外部汇编子程序时,&127;一般采用近调用方式,所以汇编子程序返回指令为RET,在特别指明采用远调用方式时,要用RETF返回指令。
1.无参数传递的过程
program prog1;
{$L prog1.obj}
procedure DisplayOk;external;
begin
DisplayOk;
end.
Title PROG1
LOCAL @@
DOSSEG
.MODEL TPASCAL
.CODE
ASSUME CS:@CODE
OkMsg db 'OK !',0dh,0ah,'$'
; Procedure DisplayOk
PUBLIC DisplayOk
DisplayOk:
push ds ;保存数据段
push cs ;代码段入栈
pop ds ;弹出数据段
mov ah,09 ;显示字符串
mov dx,offset OkMsg ;字符串地址
int 21h ;DOS功能调用
pop ds ;恢复数据段
ret ;近返回
end ;汇编子模块结束
2.传递字符型值参的过程
program prog2;
{$L prog2.obj}
procedure DisplayInt(ch : char);external;
begin
DisplayInt('a');
end.
Title PROG2
LOCAL @@
DOSSEG
.MODEL TPASCAL
.CODE
ASSUME CS:@CODE
; Procedure DisplayInt
PUBLIC DisplayInt
DisplayInt:
push bp
mov bp,sp
mov ah,02 ;显示字符
mov dl,[bp+4] ;从栈中取参数
int 21h ;DOS功能调用
pop bp
ret 2 ;形式参数在栈中占2字节
end
3.传递字符型值参和整型变参的过程
program prog3;
{$L prog3.obj}
procedure ProcArg(ch : char;var i : integer);external;
var i : integer;
begin
ProcArg('d',i);
writeln(i);
end.
Title PROG3
LOCAL @@
DOSSEG
.MODEL TPASCAL
.CODE
ASSUME CS:@CODE
; Procedure ProcArg
PUBLIC ProcArg
ProcArg:
push bp
mov bp,sp
xor ax,ax
mov al,byte ptr [bp+8] ;取字符参数
les si,[bp+4] ;取整数变量的地址
mov es:[si],al
pop bp
ret 6 ;形式参数在栈中占6字节
end
4.传递字符值参返回整型的函数
program prog4;
{$L prog4.obj}
function func(ch : char) : integer; external;
begin
writeln(func('a'));
end.
Title PROG4
LOCAL @@
DOSSEG
.MODEL TPASCAL
.CODE
ASSUME CS:@CODE
; Procedure func
PUBLIC func
func:
push bp
mov bp,sp
xor ax,ax
mov al,byte ptr [bp+4] ;取字符参数值
pop bp
ret 2 ;形式参数在栈中占2字节
end ;子程序返回值在寄存器AX中
5.传递字符串型值参和返回字符串型的函数
program prog5;
{$L prog5.obj}
function func(s : string) : string; external;
begin
writeln( func('abcd') );
end.
Title PROG5
LOCAL @@
DOSSEG
.MODEL TPASCAL
.CODE
ASSUME CS:@CODE
; Procedure func
PUBLIC func
func:
push bp
mov bp,sp
push ds
xor ch,ch
lds si,[bp+4] ;取字符串S的地址
les di,[bp+8] ;取返回值地址
mov cl,[si]
inc cl
cld
@@1:
lodsb
stosb
loop @@1
pop ds
pop bp
ret 4 ;字符串S的地址在栈中占4字节
end
6.传递长整型值参和返回长整型的函数
program prog6;
{$L prog6.obj}
function func(li : LongInt) : Longint; external;
var i : longint;
begin
i := func(111111110);
writeln(i);
end.
Title PROG6
LOCAL @@
DOSSEG
.MODEL TPASCAL
.CODE
ASSUME CS:@CODE
; Procedure func
PUBLIC func
func:
push bp
mov bp,sp
mov ax,[bp+4] ;取长整型数高位
mov dx,[bp+6] ;取长整型数低位
les si,[bp+8] ;取函数返回地址
mov es:[si],dx
mov es:[si+2],ax
pop bp
ret 4 ;长整型数LI在栈中占4字节
end
7.传递实型数值参和返回实型数的函数
program prog7;
{$L prog7.obj}
function func(r : real) : real; external;
var r : real;
begin
r := func(11111.1110);
writeln(r);
end.
Title PROG7
LOCAL @@
DOSSEG
.MODEL TPASCAL
.CODE
ASSUME CS:@CODE
; Procedure func
PUBLIC func
func:
push bp
mov bp,sp
mov ax,[bp+4] ;取实数R的值
mov bx,[bp+6] ;
mov dx,[bp+8] ;
les si,[bp+0ah] ;取函数的返回地址
mov es:[si],dx
mov es:[si+2],bx
mov es:[si+4],ax
pop bp
ret 6 ;实数R在栈中占6字节
end
五、单元中使用汇编模块的情况
在下面的演示单元DEMOU中声明了两个外部汇编函数,P1是在单元接口部分定义的,在汇编模块中采用远返回方式,P2是在单元的解释部分声明的,&127;在汇编模块中采用近返回方式。在单元DEMOU的过程P3中又调用了函数P1和P2,调用P2采用近调用,没有问题;当调用P1时,因其是在接口部分定义的,必须采用远调用方式,&127;这可以用编译指令{$F}来完成,在调用
P1之前,加上 {$F+},在调用之后,加上{$F-}&127;即可。
program prog8;
uses demou;
begin
if p1(1) then Writeln('Far call complete !');
p3;
end.
unit demou;
interface
function p1(a : integer) : boolean;
procedure p3;
implementation
{$L demou.obj}
function p1( a : integer) : boolean; external;
function p2( a : char ) : boolean; external;
procedure p3;
begin
if p2('a') then writeln('Near call complete !');
{$F+} ;打开远调用编译指令
if p1(1) then Writeln('Far call again !');
{$F-} ;关闭远调用编译指令
end;
end.
Title DEMOU
LOCAL @@
DOSSEG
.MODEL TPASCAL
.CODE
ASSUME CS:@CODE
; function p1
PUBLIC p1
p1:
push bp
mov bp,sp
xor ax,ax
cmp ax,[bp+4]
jnz @@1
mov al,0
jmp @@2
@@1: mov al,1
@@2: pop bp
retf 2 ;此函数在单元接口部分定义
; function p2
PUBLIC p2
p2:
push bp
mov bp,sp
mov ax,'a'
cmp ax,[bp+4]
jnz @@3
mov al,0
jmp @@4
@@3: mov al,1
@@4: pop bp
ret 2 ;此函数在单元解释部分定义
end
六、结束语
本文详细地介绍了TURBO PASCAL与汇编语言混合编程的技术,&127;并给出了许多典型实例,读者可以参照实例的格式进行混合语言编程,解决工作中的具体问题。&127;高级语言和汇编语言混合编程是一个比较复杂的事情,&127;只有在实践中不断细心体会,积累经验,&127;才能有所提高。&127;在对混合语言编写的程序进行调试时,&127;&127;不妨多使用TURBO
DEBUGGER,它可以帮助你发现许多不宜发现的汇编语言的错误,&127;有利于加快程序的开发进程。
©董占山Zhanshan Dong
|