圖書信息
作者:董占山
特別是1992年推出的TURBO PASCAL 6.0的升級產品BORLAND PASCAL 7.0,它提供了更方便、更廣泛的編程環境,如同時提供了DOS實模式軟體、DOS保護模式軟體和WINDOWS軟體的開發環境,套用BORLAND PASCAL不僅可以開發DOS程式,同時也可以開發WINDOWS的應用程式。BORLAND PASCAL是唯一可以和BORLAND C++相媲美的軟體開發環境。
----------------------------------------------
第一章
TURBO PASCAL高級編程技術
TURBO PASCAL是美國BORLAND國際公司的產品,在微機PASCAL市場上占有絕對優勢。它克服了往常PASCAL編譯系統占用大量記憶體的缺陷,並對標準PASCAL作了許多有益的擴充,如它具有與低層軟體和硬體打交道的能力、具有強大的圖形圖象功能、支持面向對象的程式設計方法、支持WINDOWS程式設計等等。它是一個名副其實的通用系統程式設計語言,十分適合開發一些高級套用軟體、資料庫管理系統、編譯程式等。另外,TURBO PASCAL還配備有一個高性能的集成軟體開發環境,包括編輯、編譯、調試、檔案管理等一系列功能。
本章就使用TURBO PASCAL開發高級軟體的實用技術進行闡述,介紹如何使用一些工具和技術,為TURBO PASCAL程式設計師提供方便。本章將講述在程式設計時使用單元的技術、TURBO PASCAL與彙編語言和C語言混合編程技術、實現和使用動態數組的技術、編寫中斷例程的方法、在程式中使用擴展記憶體(EMS)和擴充記憶體(XMS)的方法以及將程式的標準數據作代碼處理的方法等。
1.1 單元及其使用
單元是能與TURBO PASCAL程式分開編譯的一組TURBO PASCAL過程和函式。因為單元是單獨編譯的,所以使用單元的程式編譯速度快。而且,一個獨立的單元可以為多個程式使用。充分利用單元的優點,不僅可以加快軟體的開發速度,而且可以提高程式可維護性。
§1.1.1 單元的結構
一個單元有兩部分組成──接口部分和實現部分。如:
unit <標識符>; {單元頭}
interface {接口部分開始}
uses <單元列表> {可選項}
{公共說明部分}
implementation {實現部分開始}
{私有說明部分}
{過程或函式的定義}
begin {初始化部分開始}
{初始化代碼}
end.
1.接口部分 單元的接口部分由保留字interface開始,在單元頭和實現部分之間。在此部分,說明公用的常量、類型、變數與過程和函式的頭部。一個程式如果使用了一個單元,那么它就能訪問該單元的接口部分所定義的所有變數、數據類型、過程和函式。
接口部分僅包含過程和函式的頭部。過程和函式的實現部分在單元的實現部分定義。在程式中使用一個單元只需要知道怎樣調用單元中的過程,而不需要知道過程是怎樣實現的。
2.實現部分
實現部分是由保留字implementation開始。實現部分定義所有在接口部分聲明的過程和函式的程式體。另外實現部分可以有自己的說明,這些說明是局部的,外部程式是不知道它們的存在的,也不能調用它們。
因為在實現部分中聲明的一切對象在作用域上是局部的,所以實現部分的改變對其它單元和程式來講是不可見的。因此,修改一個單元的實現部分,並不需要重新編譯使用該單元的單元,只需要編譯這個修改單元和使用此單元的程式。然而,如果接口部分做了修改,所有使用該單元的單元和程式,均需要重新編譯,甚至需要修改。
在實現部分,如果有uses子句,則必須緊跟在保留字implementation之後。
如果過程說明為external類型,則需用{$L 檔案名稱.OBJ}編譯指令將其連入程式。
在接口部分說明的函式或過程,除了inline類型之外,都必須在實現部分再現,它們的頭部必須和接口部分一致或用簡寫格式。
3.初始化部分
單元的整個實現部分通常包括在保留字implementation和end之間。然而,如果把保留字begin放在end之前,在它們中間寫一些語句,這些語句就是單元的初始化部分。
在初始化部分可以初始化任何變數,這些變數可由單元使用,也可通過接口部分由程式使用。可以在這部分打開檔案供程式使用。例如,標準單元Printer用它的初始化部分使所有輸出調用都指向文本檔案Lst,這樣在write語句中就可以使用它。
當使用單元的程式執行時,在程式的主體執行之前,它所使用的所有單元的初始化部分按uses子句中說明的先後依次被調用。
§1.1.2 單元的使用
當使用單元時,需在uses語句中將使用的所有單元的名字列出來,單元與單元之間用逗號(,)隔開。如:
uses dos,crt;
當編譯器掃描到uses子句時,它把每個單元的接口信息加到符號表中,同時又把實現部分的機器碼與程式代碼連線起來。
1.單元的直接引用
一個模組(程式或單元)的uses子句只須列出該模組直接使用的單元名。例如:
program prog;
uses unit2;
const
a = b;
begin
writeln('a=",a);
end.
unit unit2;
interface
uses unit1;
const
b = c;
implementaion
end.
unit unit1;
interface
const
c = 1;
implementation
const
b = 2;
end.
unit2用了unit1,主程式用了unit2,間接地使用了unit1。
單元的接口部分如果有改動,則所有使用該單元的單元或程式必須重新編譯。但如果改動了單元的實現部分,則用到它的單元不必重新編譯。在上例中,如果unit1的接口部分改動了(如C=2),unit2就必須重新編譯;如果只改動實現部分(b=1),則unit2不必重新編譯。
編譯一個單元時,TURBO PASCAL計算出該單元的版本數,這個數是單元的接口部分的校驗和。上例中,在編譯unit2時,unit1的當前版本數存入unit2的編譯版本中,編譯主程式時,unit1的版本數就和存在unit2中的版本數比較,若二者不同,說明unit2編譯後,unit1的接口部分改動過,編譯器給出錯誤信息並重新編譯unit2。
2.單元的循環引用
由於在實現部分使用的單元對用戶是不可見的,因此把uses子句放在單元的實現部分,進一步隱藏了單元的內部細節,而且有可能構造出相互依賴的單元。
下面的例子說明兩個單元如何相互引用。主程式Circular使用Display單元,而Display單元在接口部分說明了Writexy過程,它有3個參數:坐標值x和y和要顯示的文本信息,若(x,y)在螢幕內,Writexy移動游標到(x,y)並顯示信息,否則,調用簡單的錯誤處理過程ShowError,而ShowError過程反過來又調用Writexy來顯示錯誤信息,這樣就產生了單元的循環引用問題。
主程式:
program circular;
uses
crt,display;
begin
writexy(1,1,"Upper left corner of screen');
writexy(100,100,'Way of the screen');
writexy(81-length('Back to reality'),15,'Back to reality');
end.
display單元:
unit display;
interface
procedure Writexy(x,y:integer;Message:string);
implementation
uses CRT,Error;
procedure Writexy;
begin
if (x in [1..80]) and (y in [1..25]) then
begin
gotoxy(x,y);
writeln(message);
end
else
ShowError('Invalid Writexy coordinates');
end;
end.
Error單元:
unit Error;
interface
procedure ShowError(ErrMessage);
implementation
uses display;
procedure ShowError;
begin
Writexy(1,25,'Error: '+ErrMessage);
end;
end.
Display和Error單元的實現部分的uses子句互相引用,TURBO PASCAL能完整編譯兩個單元的接口部分,只要在接口部分不相互依賴,在實現部分可以相互調用。
§1.1.3 單元與大程式
單元是TURBO PASCAL模組化編程的基礎,它用來創建能夠為許多程式使用但不需要源程式的過程和函式館,它是把大程式劃分為多個相關的模組基礎。
通常,一個大程式可以劃分為多個單元,這些單元按過程的功能將其分組。例如,一個編輯程式可以劃分成初始化、列印、讀寫檔案、格式化等若干個部分。另外,也可以有一個定義全局常量、數據類型、變數、過程及函式的“全局”單元,它能被所有單元和主程式使用。
一個大程式的框架如下:
program Editor;
uses
dos,crt,printer,
EditGlobal;
EditInit;
EditPrint;
EditFile;
EditFormat;
......
begin
...
end.
在大程式開發中使用單元的另一個原因是與代碼段的限制有關。8086處理器要求代碼段長度最大為64K。這意味著主程式及任何單元都不能超過64K。TURBO PASCAL將每個單元放在一個單獨的段中來解決這個問題。
§1.2 與彙編語言混合編程
TURBO PASCAL以編譯速度快、生成的目標代碼高速和緊湊而著稱。在大多數情況下,只使用TURBO PASCAL即可以完成各種各樣的程式編制,但是,在硬體接口程式、實時控制程式及大規模浮點運算時,都需要用彙編語言來編程。雖然TURBO PASCAL提供了INLINE語句和命令,以及內嵌式彙編語言(TURBO PASCAL 6.00),但這是遠遠不夠的。本節詳細討論TURBO PASCAL與彙編語言混合編程的技術,並列舉了大量的實例。
§1.2.1 TURBO PASCAL的調用協定
TURBO PASCAL程式與外部彙編子程式混合編程時,要涉及到子程式的調用方式、函式或過程的參數傳遞方法和函式如何返回值的問題,現分述如下。
§1.2.1.1 調用子程式的方式和子程式的返回方式
TURBO PASCAL程式在調用彙編子程式時,可以是近調用也可以是遠調用,因此,TURBO PASCAL程式在對彙編子程式進行調用時,根據調用方式的不同,有兩種不同的保存返回地址的方法:①近調用時,因是段內調用,僅將偏移地址IP入棧,占2位元組;②遠調用時,因是段間調用,要將代碼段值CS和偏移地址IP入棧,占4位元組。
在主程式中直接調用彙編子程式時,一般採用近調用,彙編子程式採用近返回方式,用RET指令;在TURBO PASCAL的單元中使用彙編子程式時分兩種情況:①在單元接口部分說明的子程式,在彙編子程式中要用遠返回,用RETF指令;②在單元解釋部分說明的子程式,彙編子程式要用近返回方式,用RET指令。
彙編子程式在運行結束後,為了能正確地返回到調用程式,棧頂指針必須指向正確的返回地址,它通過在返回指令RETF(或RET)中給出參數入棧時所占的位元組數的方法實現的。
§1.2.1.2 參數傳遞的方法
TURBO PASCAL是利用堆疊向過程和函式傳遞參數的,參數按從左到右的順序(說明順序)被壓入堆疊中,例如調用過程PROC(A,B,C : INTEGER; VAR D)時,其堆疊情況見圖1-1。
殌 ┌────────────┐
│+0E│ 參數A的值 │ ↑
│ ├────────────┤ │
參│+0C│ 參數B的值 │ │參
│ ├────────────┤ │
數│+0A│ 參數C的值 │ │數
│ ├────────────┤ │
壓│ +8│ 參數D的段地址 │ │出
│ ├────────────┤ │
棧│ +6│ 參數D的偏移地址 │ │棧
│ ├────────────┤ │
順│ +4│ 過程返回的段地址 │ │順
│ ├────────────┤ │
序│ +2│ 過程返回的偏移地址 │ │序
↓ ├────────────┤ │
│ BP暫存器的值 │
└────────────┘
殣 圖1-1.TURBO PASCAL遠調用彙編程式PROC的堆疊情況
TURBO PASCAL在調用子程式時,有兩種傳遞參數的方法,即傳值和傳地址的方法。下面分別說明這兩種參數傳遞方法。各種類型參數入棧的方法見表1-1。
(1)傳值方式
在TURBO PASCAL的過程或函式的形式參數表中,以值參形式定義的參數,且類型是記錄、數組、字元串、指針等複合類型以外的各種類型,如位元組型(BYTE)、短整型(SHORTINT)、整型(INTEGER)、字型(WORD)、長整型(LONGINT)、字元型(CHAR)、布爾型(BOOLEAN)、實數型(REAL)等,TURBO PASCAL在調用子程式時,直接將實參值依次從左到右順序壓入堆疊中,彙編子程式可以直接從堆疊中取得實參的值。
(2)傳地址方式
在TURBO PASCAL的過程或函式的形式參數表中,以變數形式定義的參數,及以記錄、字元串、數組、指針等複合類型定義的值參,TURBO PASCAL在調用子程式時,是將調用程式的實參地址依次按從左到右的順序壓入堆疊的。彙編子程式從堆疊中取得實參的地址,即可得到參數的值。同樣彙編子程式可以把運算結果存放到對應的變數中,以便傳回調用程式。
表1-1.各種類型參數入棧的方法
殌┌───────┬────┬─────┐
│形參類型 │傳遞方式│棧中位元組數│
├───────┼────┼─────┤
│char,boolean │ │ │
│byte,shortint,│ 傳值 │ 2 │
│integer,word │ │ │
├───────┼────┼─────┤
│longint,single│ 傳值 │ 4 │
├───────┼────┼─────┤
│real │ 傳值 │ 6 │
├───────┼────┼─────┤
│double │ 傳值 │ 8 │
├───────┼────┼─────┤
│string,pointer│ 傳地址 │ 4 │
│變數 │ │ │
└───────┴────┴─────┘殣
§1.2.1.3 函式返回值的傳遞
TURBO PASCAL函式返回值的傳遞方式根據函式返回值類型的不同而異,有採用傳地址的方式進行,也有採用暫存器方式進行,如採用傳地址的方式,其地址(4位元組)首先入棧,然後才壓入函式參數,最後壓入函式的返回地址。各種函式返回類型的傳遞方式見表1-2。
表1-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指的地址中 │ 不限 │
└───────┴──────────┴──────┘
殣
§1.2.2 彙編子程式的編寫格式
根據TURBO PASCAL的調用協定,外部彙編子程式的通用編寫格式如下:
TITLE 程式名
DOSSEG
LOCALS @@
.MODEL TPASCAL
.CODE
ASSUME CS:@CODE
PUBLIC 過程或函式名
過程或函式名:
PUSH BP
MOV BP,SP
…
POP BP
RETF 參數占堆疊位元組數
END
上述彙編子程式是TURBO ASSEMBLER的格式,本文彙編子程式均採用這種格式。對此彙編子程式格式說明如下:
. 彙編模組要採用TPASCAL模式;
. 在彙編模組中,必須把TURBO PASCAL調用的過程或函式說明為PUBLIC屬性;
. 子程式返回指令視具體情況而定,近調用用RET,遠調用用RETF;
. 返回指令後的參數是指該子程式形式參數表中所有參數入棧後所占堆疊的位元組數;
. 彙編模組結束要寫END。
§1.2.3 TURBO PASCAL程式的編寫格式
在TURBO PASCAL中,聲明外部子程式的格式如下:
procedure prc(a, b : integer; var c : real);external;
function func(a, b : integer) : real; external;
即在通常的TURBO PASCAL過程或函式的聲明後加上external關鍵字。在聲明了外部過程或函式的主程式或程式單元中,要用編譯指令{$L},把彙編好的目標模組載入進來。
在TURBO PASCAL程式中使用外部彙編過程或函式時,方法和一般的TURBO PASCAL過程和函式沒有兩樣。
§1.2.4 主程式中使用外部彙編子程式的典型例子分析
在TURBO PASCAL主程式中直接使用外部彙編子程式時,一般採用近調用方式,所以彙編子程式返回指令為RET,在特別指明採用遠調用方式時,要用RETF返回指令。
1.無參數傳遞的過程
program prog1;
{$L prog1.obj}
procedure DisplayOk;external;
begin
DisplayOk;
end.
Title PROG1
LOCALS @@
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
LOCALS @@
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
LOCALS @@
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
LOCALS @@
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
LOCALS @@
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
LOCALS @@
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
LOCALS @@
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
----------------------------------------------