Delphi異常處理與調(diào)試
3.1 Delphi異常處理
3.1.1 異常處理的意義
所謂異常,可以理解為一種特殊的事件。當(dāng)這種特殊的事件發(fā)生時(shí),程序正常的執(zhí)行流程將被打斷。異常處理機(jī)制能夠確保在發(fā)生異常的情況
下應(yīng)用程序不會(huì)中止運(yùn)行,也不會(huì)丟失數(shù)據(jù)或資源。
Object Pascal定義了大量的異常處理對(duì)象,使應(yīng)用程序幾乎能夠處理所有的異常情況,并且對(duì)異常處理的語(yǔ)法作了簡(jiǎn)化。異常處理不再僅僅是
高級(jí)程序員的“專(zhuān)利”。
沒(méi)有人能保證程序代碼絕對(duì)不出錯(cuò)。有時(shí)候,即使程序本身沒(méi)有錯(cuò),但與程序打交道的軟硬件設(shè)備出錯(cuò),也會(huì)使程序出現(xiàn)異常。引起異常的設(shè)
備稱(chēng)為“異常源”,包括操作系統(tǒng)、設(shè)備驅(qū)動(dòng)程序、數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序(諸如BDE、SQL Links、ODBC、ADO)、動(dòng)態(tài)鏈接庫(kù)、常駐內(nèi)存的防病毒軟件、Delphi自身的組件庫(kù)和運(yùn)行期庫(kù)等。
由此可見(jiàn),異常幾乎是不可避免的。過(guò)去,在沒(méi)有異常處理機(jī)制的情況下,程序員不得不小心翼翼地檢查每一次函數(shù)調(diào)用的返回值,或者通過(guò)
額外的代碼捕獲可能的錯(cuò)誤。
關(guān)鍵是,通過(guò)檢查函數(shù)的返回值或設(shè)置錯(cuò)誤陷阱只能捕獲可預(yù)見(jiàn)的錯(cuò)誤。如果發(fā)生沒(méi)有預(yù)見(jiàn)到的錯(cuò)誤,或者函數(shù)調(diào)用本身就已失敗,則程序正
常的流程將被打亂。
還有一個(gè)問(wèn)題就是,程序中過(guò)多的錯(cuò)誤檢查代碼也破壞了程序的可讀性。事實(shí)上,這些代碼大都是閑置的,因?yàn)槌绦蛑械教幇l(fā)生異常的可能性
畢竟不是很大。
使用Object Pascal語(yǔ)言的異常處理機(jī)制,就能解決上述弊端。既能使程序的安全性得到保證,又不至于使程序代碼過(guò)分瑣碎。我們先看一段
沒(méi)有采用異常處理機(jī)制的代碼:
var
AChar,AString:ShortString;
begin
AString:=’Welcome to Delphi’;
AChar:=Copy(AString,20,1);
if AChar<>#0 then // #0是空字符,不是空格
begin
if AChar<>’!’ then Insert(AChar,AString,20);
Exit;
end;
end;
在上述代碼中,雖然檢查了Copy()的返回值,但如果Copy()本身調(diào)用失敗,則程序就執(zhí)行不到Exit語(yǔ)句。下面改用異常處理機(jī)制來(lái)編寫(xiě)這段代
碼:
var
AChar,Astring:ShortString;
begin
try
AString:=’Welcome to Delphi’;
AChar:=Copy(Astring,20,1);
if AChar<>’!’ then Insert(AChar,AString,20);
except
Exit;
end;
End;
采用了異常處理機(jī)制后,可以先假設(shè)程序不可能出錯(cuò),按正常的順序?qū)懘a。當(dāng)代碼在運(yùn)行過(guò)程中出現(xiàn)異常時(shí),將跳轉(zhuǎn)到except部分,執(zhí)行
Exit。
異常處理機(jī)制有兩種結(jié)構(gòu):一種是try...except結(jié)構(gòu),另一種是try...finally結(jié)構(gòu),這兩種結(jié)構(gòu)在用法上有很大的區(qū)別。
3.1.2 錯(cuò)誤類(lèi)型
一般來(lái)說(shuō),無(wú)論在編程的時(shí)候如何仔細(xì),程序總會(huì)有錯(cuò)誤。錯(cuò)誤分為四種類(lèi)型:設(shè)計(jì)期錯(cuò)誤、編譯期錯(cuò)誤、運(yùn)行期錯(cuò)誤、邏輯錯(cuò)誤。
1.設(shè)計(jì)期錯(cuò)誤
這種錯(cuò)誤類(lèi)型發(fā)生在設(shè)計(jì)期,通常是因?yàn)榻o組件的某個(gè)屬性輸入了非法的值。例如,在設(shè)計(jì)數(shù)據(jù)庫(kù)應(yīng)用程序時(shí)指定了一個(gè)沒(méi)有定義的數(shù)據(jù)庫(kù)別
名。
這種類(lèi)型的錯(cuò)誤比較容易被發(fā)現(xiàn)和糾正,因?yàn)镈elphi能夠?qū)傩缘闹颠M(jìn)行合法性檢查。一旦發(fā)現(xiàn)這種錯(cuò)誤,Delphi將彈出一個(gè)警告窗口,提示
你糾正錯(cuò)誤。
2.編譯期錯(cuò)誤
編譯期錯(cuò)誤也叫語(yǔ)法錯(cuò)誤,當(dāng)程序代碼違反了Object Pascal的語(yǔ)法規(guī)則時(shí)將發(fā)生這種錯(cuò)誤。
如果程序代碼中有語(yǔ)法錯(cuò)誤,編譯就不能通過(guò),代碼編輯器的狀態(tài)欄將給出錯(cuò)誤信息提示,并在代碼編輯器中突出顯示有語(yǔ)法錯(cuò)誤的行。
比較常見(jiàn)的語(yǔ)法錯(cuò)誤是數(shù)據(jù)類(lèi)型不匹配,特別是調(diào)用RTL、VCL或Windows的API時(shí)容易發(fā)生參數(shù)不匹配的錯(cuò)誤。為了避免這類(lèi)錯(cuò)誤,建議使用
Delphi的在線幫助,找到RTL、VCL或API的聲明,仔細(xì)對(duì)照它們的參數(shù)。不小心輸錯(cuò)變量名或命令語(yǔ)句拼寫(xiě)錯(cuò)誤也是經(jīng)常造成編譯期錯(cuò)誤的原因
。
編譯器檢查語(yǔ)法錯(cuò)誤的功能是可以自定義的。你可以使用“Project”菜單上的“Options”命令打開(kāi)“Project Options”對(duì)話框,然后設(shè)置編
譯器的語(yǔ)法檢查選項(xiàng)。
3.運(yùn)行期錯(cuò)誤
程序雖然通過(guò)了編譯,但在運(yùn)行時(shí)失敗了,這種錯(cuò)誤稱(chēng)為運(yùn)行期錯(cuò)誤。例如,程序試圖打開(kāi)一個(gè)不存在的文件,或者在運(yùn)算時(shí)出現(xiàn)了被零除。
運(yùn)行期錯(cuò)誤分兩種情況,一種是運(yùn)行時(shí)隨運(yùn)行異常環(huán)境或用戶(hù)輸入的錯(cuò)誤參數(shù)而造成運(yùn)行錯(cuò)誤,這種情況的“錯(cuò)誤”往往無(wú)法在交付前通過(guò)調(diào)
試排除,而通常由“異常處理”來(lái)解決。另一種是不隨環(huán)境和參數(shù)變化都會(huì)產(chǎn)生的運(yùn)行錯(cuò)誤,這種情況下,如果不能確認(rèn)錯(cuò)誤發(fā)生在什么地方
,可以使用Delphi的內(nèi)部集成調(diào)試器幫助你找到錯(cuò)誤所在。例如,可以通過(guò)單步執(zhí)行命令讓程序一條語(yǔ)句一條語(yǔ)句地執(zhí)行,或者通過(guò)一個(gè)觀察
窗口來(lái)監(jiān)視某個(gè)變量的變化情況。
4.邏輯錯(cuò)誤
邏輯錯(cuò)誤是指程序通過(guò)了編譯,也能執(zhí)行,但執(zhí)行的結(jié)果與預(yù)期的不同。
邏輯錯(cuò)誤有時(shí)比較難找,因?yàn)榫幾g器無(wú)法識(shí)別這種錯(cuò)誤。此時(shí),需要用內(nèi)部集成調(diào)試器,通過(guò)控制程序的運(yùn)行以及監(jiān)視程序的輸出,來(lái)把錯(cuò)誤
逐步定位在一個(gè)較小的范圍內(nèi)。
5.怎樣盡可能地減少錯(cuò)誤
雖然錯(cuò)誤是很難避免的,但好的編程習(xí)慣能夠盡可能地減少錯(cuò)誤。下面是我們的建議:
(1) 程序應(yīng)盡可能地模塊化
程序分解為模塊后,由于每個(gè)模塊所要完成的任務(wù)相對(duì)簡(jiǎn)單了,所以發(fā)生錯(cuò)誤的可能也就減少了。模塊化還簡(jiǎn)化了程序的維護(hù)。
(2) 養(yǎng)成良好的代碼書(shū)寫(xiě)習(xí)慣
注釋能夠增加代碼的可讀性,方便維護(hù)和修改??s進(jìn)能夠使程序的語(yǔ)法結(jié)構(gòu)更加清晰。
(3) 不要忘記檢查參數(shù)的值
在函數(shù)內(nèi)部,首先要檢查傳遞過(guò)來(lái)的參數(shù)值是否合法,是否在一個(gè)可接受的范圍內(nèi)。
(4) 不要忘記檢查函數(shù)的返回值
函數(shù)的返回值往往表示函數(shù)調(diào)用是否成功,以此決定下面的程序流程。如果貿(mào)然執(zhí)行下一步,有可能會(huì)出現(xiàn)意想不到的結(jié)果。
3.2 Delphi異常類(lèi)
異常就是不正?;蛘呤遣豢深A(yù)料的情況,它干擾正常的程序流。在應(yīng)用程序運(yùn)行過(guò)程中有許多情況可能導(dǎo)致程序異常,如需要打開(kāi)的文件不存
在,不能向操作系統(tǒng)獲得申請(qǐng)的內(nèi)存,數(shù)組元素的索引超過(guò)數(shù)組的長(zhǎng)度,進(jìn)行除法運(yùn)算時(shí)除數(shù)為零,在進(jìn)行網(wǎng)絡(luò)訪問(wèn)時(shí),因?yàn)槟撤N原因不能和
通信對(duì)方建立連接等等,均會(huì)產(chǎn)生異?,F(xiàn)象。
異常類(lèi)是Delphi異常處理機(jī)制的核心,也是Delphi異常處理的主要特色。下面我們對(duì)異常類(lèi)的概念和體系進(jìn)行詳細(xì)的介紹。
Delphi提供的所有異常類(lèi)都是類(lèi)Exception的子類(lèi)。用戶(hù)也可以從Exception派生一個(gè)自定義的異常類(lèi)。
Exception的一系列構(gòu)造函數(shù)中最重要的參數(shù)是顯示的錯(cuò)誤信息。而數(shù)據(jù)成員中最重要的也是可被引用的消息字符串(message,messagePtr)。
這些信息分別對(duì)自定義一個(gè)異常類(lèi)和處理一個(gè)異常類(lèi)有重要作用。
Delphi提供了一個(gè)很龐大的異常類(lèi)體系,這些異常類(lèi)幾乎涉及到編程的各個(gè)方面。從大的方面我們可以把異常類(lèi)分為運(yùn)行庫(kù)異常、對(duì)象異常、
組件異常三類(lèi)。下面我們分別進(jìn)行介紹。
3.2.1 運(yùn)行庫(kù)異常類(lèi)(RTL Exception)
運(yùn)行庫(kù)異常可以分為七類(lèi),它們都定義在SysUtils庫(kù)單元中。
1.I/O異常
I/O異常類(lèi)EInOutError是在程序運(yùn)行中試圖對(duì)文件或外設(shè)進(jìn)行操作失敗后產(chǎn)生的,它從Exception派生后增加了一個(gè)公有數(shù)據(jù)成員ErrorCode,
用于保存所發(fā)生錯(cuò)誤的代碼。這一成員可用于在發(fā)生I/0異常后針對(duì)不同情況采取不同的對(duì)策。
當(dāng)設(shè)置編譯指示{$I-}時(shí),不產(chǎn)生I/0異常類(lèi)而是把錯(cuò)誤代碼返回到預(yù)定義變量IOResult中。
2.堆異常
堆異常是在動(dòng)態(tài)內(nèi)存分配中產(chǎn)生的,包括兩個(gè)類(lèi)EOutOfMemory和EInvalidPointer,如表3-1所示。
表3-1 堆異常類(lèi)及其產(chǎn)生原因
異常類(lèi) 引發(fā)條件
EOutOfMemory 沒(méi)有足夠的空間用于滿(mǎn)足所要求的內(nèi)存分配
EInvalidPointer 非法指針。一般是由于程序試圖去釋放一個(gè)已釋放的指針而引起的
3.整數(shù)異常
整數(shù)異常都是從一個(gè)EIntError類(lèi)派生的,但程序運(yùn)行中引發(fā)的總是它的子類(lèi):EDivByZero,ERangeError,EIntOverFlow,如表3-2所示。
表3-2 整數(shù)異常及其產(chǎn)生原因
異常類(lèi) 引發(fā)條件
EDivByZero 試圖被零除
ERangeError 整數(shù)表達(dá)式越界
EIntOverFlow 整數(shù)操作溢出
ERangeError當(dāng)一個(gè)整數(shù)表達(dá)式的值超過(guò)為一個(gè)特定整數(shù)類(lèi)型分配的范圍時(shí)引發(fā)。比如下面一段代碼將引發(fā)一個(gè)ERangeError異常。
var
SmallNumber:ShortInt;
X,Y:Integer;
begin
X:=100;
Y:=75;
SmallNumber:=X*Y;
end;
特定整數(shù)類(lèi)型包括ShortInt、Byte以及與整數(shù)兼容的枚舉類(lèi)型、布爾類(lèi)型等。例如:
type
THazard=(Safety,Marginal,Critical,Catastrophic);
var
Haz:THazard;
Item:Integer;
begin
Item:=5;
Haz:=THazard(Item);
end;
由于枚舉類(lèi)型越界而引發(fā)一個(gè)ERangeError異常。數(shù)組下標(biāo)越界也會(huì)引發(fā)一個(gè)ERangeError異常,如:
var
Values:array[1..10] of Integer;
I:Integer;
begin
for I:=1 to 11 do
Values[I]:=I;
end;
ERangeError異常只有當(dāng)范圍檢查打開(kāi)時(shí)才會(huì)引發(fā)。這可以在代碼中包含{$R+}編譯指示或設(shè)置IDE Option|Project的Range_Checking Option選
擇框。注意,Delphi不對(duì)長(zhǎng)字符串做范圍檢查。
EIntOverFlow異常類(lèi)在Integer、Word、Longint三種整數(shù)類(lèi)型越界時(shí)引發(fā)。如下面的代碼將引發(fā)一個(gè)EIntOverFlow異常:
var
I:Integer;
a,b,c:Word;
begin
a:=10;
b:=20;
c:=1;
for I:=0 to 100 do
c:=a*b*c;
end;
EIntOverFlow異常類(lèi)只有在編譯選擇框Option|Project|Over_Flow_Check Option選中時(shí)才產(chǎn)生。當(dāng)關(guān)閉溢出檢查,則溢出后變量的值是丟棄溢
出部分后的剩余值。
4.浮點(diǎn)異常
浮點(diǎn)異常是在進(jìn)行實(shí)數(shù)操作時(shí)產(chǎn)生的,它們都從一個(gè)EMathError類(lèi)派生,但與整數(shù)異常相同,程序運(yùn)行中引發(fā)的總是它的子類(lèi)EInvalidOp、
EZeroDivide、EOverFlow、EunderFlow (表3-3)。
表3-3 浮點(diǎn)異常類(lèi)及其引發(fā)條件
異常類(lèi) 引發(fā)條件
EInvalidOp 處理器碰到一個(gè)未定義的指令
EZeroDivide 試圖被零除
EOverFlow 浮點(diǎn)上溢
EUnderFlow 浮點(diǎn)下溢
EInvalidOp最常見(jiàn)的引發(fā)條件是沒(méi)有協(xié)處理器的機(jī)器遇到一個(gè)協(xié)處理器指令。由于在缺省情況下Delphi總是把浮點(diǎn)運(yùn)算編譯為協(xié)處理器指令,
因而在386以下微機(jī)上常常會(huì)碰到這個(gè)錯(cuò)誤。此時(shí)只需要在單元的接口部分設(shè)置全局編譯指示{$N-},選擇利用運(yùn)行庫(kù)進(jìn)行浮點(diǎn)運(yùn)算,問(wèn)題就可
以解決了。
各種類(lèi)型的浮點(diǎn)數(shù)(Real、Single、Double、Extended)越界引起同樣的溢出異常。
5.類(lèi)型匹配異常
類(lèi)型匹配異常EInvalidCast當(dāng)試圖用As操作符把一個(gè)對(duì)象與另一類(lèi)對(duì)象匹配失敗后引發(fā)。
6.類(lèi)型轉(zhuǎn)換異常
類(lèi)型轉(zhuǎn)換異常EConvertError當(dāng)試圖用轉(zhuǎn)換函數(shù)把數(shù)據(jù)從一種形式轉(zhuǎn)換為另一種形式時(shí)引發(fā),特別是當(dāng)把一個(gè)字符串轉(zhuǎn)換為數(shù)值時(shí)引發(fā)。下面程
序中的兩條執(zhí)行語(yǔ)句都將引發(fā)一個(gè)EConvertError異常。
var
r1:Real;
int:Integer;
begin
r1:=StrToFloat(’$140.48’);
int:=StrToInt(’1,402’);
end;
要注意,并不是所有的類(lèi)型轉(zhuǎn)換函數(shù)都會(huì)引發(fā)EConvertError異常。比如函數(shù)Val當(dāng)它無(wú)法完成字符串到數(shù)值的轉(zhuǎn)換時(shí)只把錯(cuò)誤代碼返回。利用
這一點(diǎn)我們實(shí)現(xiàn)了輸入的類(lèi)型和范圍檢查。
7.硬件異常
硬件異常發(fā)生的情況有兩種:或者是處理器檢測(cè)到一個(gè)它不能處理的錯(cuò)誤,或者是程序產(chǎn)生一個(gè)中斷試圖中止程序的執(zhí)行。硬件異常不能編譯
進(jìn)動(dòng)態(tài)鏈接庫(kù)(DLLs)中,而只能在標(biāo)準(zhǔn)的應(yīng)用中使用(表3-4)。
硬件異常都是EProcessor異常類(lèi)的子類(lèi)。但運(yùn)行時(shí)并不會(huì)引發(fā)一個(gè)EProcessor異常。
表3-4 硬件異常類(lèi)及其產(chǎn)生原因
異常類(lèi) 引發(fā)條件
EFault 基本異常類(lèi),是其它異常類(lèi)的父類(lèi)
EGPFault 一般保護(hù)錯(cuò),通常由一個(gè)未初始化的指針或?qū)ο笠?br>EStackFault 非法訪問(wèn)處理器的棧段
EPageFault Windows內(nèi)存管理器不能正確使用交換文件
EInvalidOpCode 處理器碰到一個(gè)未定義的指令,這通常意味著處理器試圖去操作非法數(shù)據(jù)或未初始化的內(nèi)存
EBreakPoint 應(yīng)用程序產(chǎn)生一個(gè)斷點(diǎn)中斷
ESingleStep 應(yīng)用程序產(chǎn)生一個(gè)單步中斷
EFault、EGPFault往往意味著致命的錯(cuò)誤。而EBreakPoint、ESingleStep被Delphi IDE的內(nèi)置調(diào)試器處理。事實(shí)上前邊的五種硬件異常的響應(yīng)
和處理對(duì)開(kāi)發(fā)者來(lái)說(shuō)都是十分棘手的問(wèn)題。
3.2.2 對(duì)象異常類(lèi)
所謂對(duì)象異常是指非組件的對(duì)象引發(fā)的異常。Delphi定義的對(duì)象異常包括流異常、打印異常、圖形異常、字符串鏈表異常等。
1.流異常類(lèi)
流異常類(lèi)包括EStreamError、EFCreateError、EFOpenError、EFilerError、EReadError、EWriteError、EClassNotFound。
流異常在Classes庫(kù)單元中定義。
流異常引發(fā)的原因如表3-5所示。
表3-5 流異常類(lèi)及其產(chǎn)生原因
異常類(lèi) 引發(fā)條件
EStreamError 利用LoadFromStream方法讀一個(gè)流發(fā)生錯(cuò)誤
EFCreateError 創(chuàng)建文件時(shí)發(fā)生錯(cuò)誤
EFOpenError 打開(kāi)文件時(shí)發(fā)生錯(cuò)誤
EFilerError 試圖再次登錄一個(gè)存在的對(duì)象
EReadError ReadBuffer方法不能讀取特定數(shù)目的字節(jié)
EWriteError WriteBuffer方法不能寫(xiě)特定數(shù)目的字節(jié)
EClassNotFound 窗口上的組件被從窗口的類(lèi)型定義中刪除
2.打印異常類(lèi)
打印異常類(lèi)EPrinter當(dāng)打印發(fā)生錯(cuò)誤時(shí)引發(fā)。它在printers庫(kù)單元中定義。例如應(yīng)用程序試圖向一個(gè)不存在的打印機(jī)打印或由于某種原因打印
工作無(wú)法送到打印機(jī)時(shí),就會(huì)產(chǎn)生一個(gè)打印異常。
3.圖形異常類(lèi)
圖形異常類(lèi)定義在Graphic庫(kù)單元中,包括EInvalidGraphic和EInvalidGraphicOperation兩類(lèi)。
EInvalidGraphic當(dāng)應(yīng)用程序試圖從一個(gè)并不包含合法的位圖、圖標(biāo)、元文件或用戶(hù)自定義圖形類(lèi)型的文件中裝入圖形時(shí)引發(fā)。例如下面的代碼
:
Image1.Picture.LoadFromFile('Readme.txt');
由于Readme.txt并不包含一個(gè)合法的圖形,因而將引發(fā)一個(gè)EInvalidGraphic異常。
EInvalidGraphicOperation當(dāng)試圖對(duì)一個(gè)圖形進(jìn)行非法操作時(shí)引發(fā)。例如試圖改變一個(gè)圖標(biāo)的大小。
var
AnIcon:TIcon;
begin
AnIcon:=TIcon.Create;
AnIcon.LoadFromFile('C:/WINDOWS/DIRECTRY.ICO');
AnIcon.Width:=100; {引發(fā)一個(gè)圖形異常}
end;
4.字符串鏈表異常
字符串鏈表異常EStringListError、EListError在用戶(hù)對(duì)字符串鏈表進(jìn)行非法操作時(shí)引發(fā)。
由于許多組件(如TListBox,TMemo,TTabSet,…)都有一個(gè)TStrings類(lèi)的重要屬性,因而字符串鏈表異常在組件操作編程中非常有用。
EStringListError異常一般在字符串鏈表越界時(shí)產(chǎn)生。例如對(duì)如下初始化的列表框:
ListBox1.Items.Add('Firstitem');
ListBox1.Items.Add('Seconditem');
ListBox1.Items.Add('Thirditem');
則以下操作都會(huì)引起EStringListError異常:
ListBox1.Item[3]:=’NotExist’;
Str:=ListBox1.Item[3];
EListError異常一般在如下兩種情況下引發(fā):
(1)當(dāng)字符串鏈表的Duplicates屬性設(shè)置為dupError時(shí),應(yīng)用程序試圖加入一個(gè)重復(fù)的字符串。
(2)試圖往一個(gè)排序的字符串鏈表中插入一個(gè)字符串。
3.2.3 組件異常類(lèi)
1.通用組件異常類(lèi)
通用組件異常類(lèi)常用的有三個(gè):EInvalidOperation、EComponentError、EOutOfResource。其中EInvalidOperation、EOutOfResource在
Controls單元中定義;EComponentError在Classes單元中定義。
(1)非法操作異常EInvalidOperation
EInvalidOperation引發(fā)的原因可能有:
a. 應(yīng)用程序試圖對(duì)一個(gè)Parent屬性為nil的組件進(jìn)行一些需要Windows句柄的操作
b. 試圖對(duì)一個(gè)窗口進(jìn)行拖放操作
c. 操作違反了組件屬性間內(nèi)置的相互關(guān)系等
例如,ScrollBar、Gauge等組件要求Max屬性大于等于Min屬性,因而下面的語(yǔ)句:
ScrollBar1.Max:=ScrollBar1.Min-1;
將引發(fā)一個(gè)EInvalidOperation異常。
(2)組件異常EComponentError
引發(fā)該異常的原因可能有:
a. 在Register過(guò)程之外試圖登錄一個(gè)組件(常用于自定義組件開(kāi)發(fā)中)
b. 應(yīng)用程序在運(yùn)行中改變了一個(gè)組件的名稱(chēng)并使該組件與另一個(gè)組件重名
c. 一個(gè)組件的名稱(chēng)改變?yōu)橐粋€(gè)Object Pascal非法的標(biāo)識(shí)符
d. 動(dòng)態(tài)生成一個(gè)組件與已存在的另一組件重名
(3)資源耗盡異常EOutOfResource
當(dāng)應(yīng)用程序試圖創(chuàng)建一個(gè)Windows句柄而Windows卻沒(méi)有多余的句柄分配時(shí)引發(fā)該異常。
2.專(zhuān)用組件異常類(lèi)
許多組件都定義了相應(yīng)的組件異常類(lèi)。但并不是有關(guān)組件的任何錯(cuò)誤都會(huì)引發(fā)相應(yīng)的異常類(lèi)。許多情況下它們將引發(fā)一個(gè)運(yùn)行時(shí)異常或?qū)ο螽?/font>
常。
下面列出幾個(gè)典型的組件異常類(lèi)。
(1)EMenuError
非法的菜單操作,例如試圖刪除一個(gè)不存在的菜單項(xiàng)。這一異常類(lèi)在Menus庫(kù)單元中定義。
(2)EInvalidGridOpertion
非法的網(wǎng)格操作,比如試圖引用一個(gè)不存在的網(wǎng)格單元。這一異常類(lèi)在Grids庫(kù)單元中定義。
(3)EDDEError
DDE異常。比如應(yīng)用程序找不到特定的服務(wù)器或會(huì)話,或者一個(gè)連接意外中止。這一異常類(lèi)在DDEMan庫(kù)單元中定義。
(4)EDatabaseError,EReportError
數(shù)據(jù)庫(kù)異常(EDatabaseError)和報(bào)表異常(EReportError)在進(jìn)行數(shù)據(jù)庫(kù)和報(bào)表操作出現(xiàn)錯(cuò)誤時(shí)引發(fā)。
3.3 Delphi異常處理機(jī)制
Delphi異常處理機(jī)制建立在保護(hù)塊(Protected Blocks)的概念上。所謂保護(hù)塊是用保留字try和end封裝的一段代碼。保護(hù)塊的作用是當(dāng)應(yīng)用程
序發(fā)生錯(cuò)誤時(shí)自動(dòng)創(chuàng)建一個(gè)相應(yīng)的異常類(lèi)(Exception)。程序可以捕獲并處理這個(gè)異常類(lèi),以確保程序的正常結(jié)束以及資源的釋放和數(shù)據(jù)不受破
壞。如果程序不進(jìn)行處理,則系統(tǒng)會(huì)自動(dòng)提供一個(gè)消息框。
3.3.1 異常響應(yīng)與try-except語(yǔ)句
異常響應(yīng)為開(kāi)發(fā)者提供了一個(gè)按自己的需要進(jìn)行異常處理的機(jī)制。try…except…end形成了一個(gè)異常響應(yīng)保護(hù)塊。正常情況下except后面的語(yǔ)
句并不被執(zhí)行,而當(dāng)異常發(fā)生時(shí)程序自動(dòng)跳到except,進(jìn)入異常響應(yīng)處理模塊。當(dāng)異常被響應(yīng)后異常類(lèi)自動(dòng)清除。
try except語(yǔ)句的一般格式如下:
try //try保護(hù)代碼塊
被保護(hù)語(yǔ)句
except //異常處理塊
異常處理語(yǔ)句 //異常不發(fā)生,不處理
end;
或
try //try保護(hù)代碼塊
被保護(hù)語(yǔ)句
except //異常處理塊
on <異常對(duì)象類(lèi)型1> do <語(yǔ)句1> //捕獲指定類(lèi)型的異常對(duì)象,進(jìn)行處理
on <異常對(duì)象類(lèi)型n> do <語(yǔ)句n> //捕獲指定類(lèi)型的異常對(duì)象,進(jìn)行處理
else
<語(yǔ)句n+1> //缺省的異常處理代碼
end;
try語(yǔ)句塊指出了需要進(jìn)行異常保護(hù)的代碼。如果在這部分有不正常的事件發(fā)生,則引發(fā)一個(gè)異常對(duì)象。except是異常處理部分,被保護(hù)部分引
發(fā)的異常對(duì)象將執(zhí)行<異常處理語(yǔ)句>或由這部分代碼捕獲并進(jìn)行處理。
實(shí)例3-1 用try-except語(yǔ)句處理被0除情況:
procedure TForm1.Button1Click(Sender: TObject);
var a,b,c:real;
begin
b:=strtofloat(edit1.Text);
c:=strtofloat(edit2.Text);
try
a:=b /c;
edit3.Text:=floattostr(a);
except
edit3.Text:='不能用0除';
end;
end;
在正常情況下(即由try保護(hù)的部分沒(méi)有異常事件發(fā)生時(shí)),應(yīng)用程序執(zhí)行完畢try部分代碼后,跳過(guò)except部分代碼,繼續(xù)運(yùn)行應(yīng)用程序。在
執(zhí)行時(shí)內(nèi)代碼出現(xiàn)異常情況時(shí),引發(fā)一個(gè)異常對(duì)象后,應(yīng)用程序?qū)⑻^(guò)剩余的被保護(hù)部分未執(zhí)行的代碼,直接進(jìn)入except部分進(jìn)行異常處理,
在except異常處理塊中,由try保護(hù)塊引發(fā)的異常對(duì)象也可以由以下語(yǔ)句來(lái)捕獲:
on <異常對(duì)象類(lèi)型> do <語(yǔ)句>
實(shí)例3-2 兩數(shù)相除過(guò)程中發(fā)生異常時(shí)的處理情況:
procedure TForm1.Button1Click(Sender: TObject);
var a,b,c:real;
begin
try
b:=strtofloat(edit1.Text);
c:=strtofloat(edit2.Text);
a:=b /c;
edit3.Text:=floattostr(a);
except
on Ezerodivide do edit3.Text:='不能用0除';
on EMathError do edit3.Text:='計(jì)算錯(cuò)誤';
else
edit3.Text:='發(fā)生異常';
end;
end;
保留字on...do用于判斷異常類(lèi)型。這樣,根據(jù)不同的異常類(lèi)型,執(zhí)行不同的異常處理命令。如上例中,edit2中輸入0,則edit3中將顯示'
不能用0除',若edit1和edit2同時(shí)輸入0,則edit3中將顯示'計(jì)算錯(cuò)誤',edit1或edit2組建中輸入非數(shù)字字符,則edit3中將顯示'發(fā)生異常'。
注意:運(yùn)行時(shí)必須先編譯生成exe文件,然后在Windows中運(yùn)行,否則在發(fā)生異常時(shí)仍將出現(xiàn)系統(tǒng)的錯(cuò)誤信息。
3.3.2 異常保護(hù)與try-finally語(yǔ)句
確?;厥辗峙涞馁Y源是程序健壯性的一個(gè)關(guān)鍵。但缺省情況下異常發(fā)生時(shí)程序會(huì)在出錯(cuò)點(diǎn)自動(dòng)退出當(dāng)前模塊,因此需要一種特殊的機(jī)制來(lái)確保
即使在異常發(fā)生的情況下釋放資源的語(yǔ)句仍能被執(zhí)行。而Delphi的異常處理正提供了這種機(jī)制。
1.需要保護(hù)的資源
一般說(shuō)來(lái)需要保護(hù)的資源包括:文件、內(nèi)存、Windows資源和對(duì)象。
比如下面一段程序就會(huì)造成1K內(nèi)存資源的丟失。
var
pPointer:Pointer;
iInt,iDiv:Integer;
begin
iDiv:=0;
GetMem(pPointer,1024); //分配1K的內(nèi)存
iInt:=10 div iDiv; //這里將觸發(fā)被零除的異常
FreeMem(pPointer,1024); //永遠(yuǎn)執(zhí)行不到這兒
end;
由于程序從異常發(fā)生點(diǎn)退出,從而FreeMem永遠(yuǎn)沒(méi)有執(zhí)行的機(jī)會(huì)。
2.創(chuàng)建一個(gè)資源保護(hù)塊
一個(gè)設(shè)計(jì)良好的應(yīng)用程序必須關(guān)閉或釋放操作系統(tǒng)為應(yīng)用程序分配的各種資源。即使在應(yīng)用程序運(yùn)行中產(chǎn)生了不正常的事件,也要保證系統(tǒng)資
源的釋放和關(guān)閉。如應(yīng)用程序運(yùn)行過(guò)程中申請(qǐng)的內(nèi)存資源,在應(yīng)用程序結(jié)束前必須釋放它,否則將會(huì)使內(nèi)存丟失。特別需要注意的是,不僅用
戶(hù)代碼可能產(chǎn)生異常,許多運(yùn)行時(shí)函數(shù)也可能產(chǎn)生異常。一旦產(chǎn)生異常應(yīng)用程序?qū)⒈唤K止,這時(shí)可能出現(xiàn)分配的系統(tǒng)資源未被釋放。使用try-
finally語(yǔ)句可以有效地避免這種情況。
try finally語(yǔ)句的一般格式如下:
try //try保護(hù)代碼塊
被保護(hù)語(yǔ)句
finally //異常處理塊
異常處理語(yǔ)句 //無(wú)論異常發(fā)生否,都必須處理
end;
若用作創(chuàng)建一個(gè)資源保護(hù)塊時(shí),它的格式可寫(xiě)成:
(分配系統(tǒng)資源)
try
(使用系統(tǒng)資源的語(yǔ)句)
finanlly
(釋放系統(tǒng)資源)
end;
try前面的語(yǔ)句中包含了申請(qǐng)或創(chuàng)建系統(tǒng)資源的語(yǔ)句。try部分包含了使用系統(tǒng)資源的代碼語(yǔ)句。finally部分包含資源釋放語(yǔ)句。在應(yīng)用程序運(yùn)
行過(guò)程中,如果在try部分產(chǎn)生異常情況,將直接跳過(guò)隨后的代碼,直接執(zhí)行finally部分的資源釋放語(yǔ)句。如果不產(chǎn)生異常,則應(yīng)用程序正常
運(yùn)行,最后執(zhí)行finally部分中的資源釋放語(yǔ)句。
注意:在try-finally語(yǔ)句中,當(dāng)try部分產(chǎn)生異常后,應(yīng)用程序直接執(zhí)行Finally部分的資源釋放語(yǔ)句。
實(shí)例3-3 用try-finally語(yǔ)句確保所分配內(nèi)存資源的在異常情況下仍被釋放:
var
pPointer: Pointer;
iInt,iDiv: Integer;
begin
iDiv:=0;
GetMem(pPointer,1024); //分配1K的內(nèi)存
try
iInt:=10 div iDiv; //這將觸發(fā)異常
finally
FreeMem(pPointer,1024); //釋放先前分配的內(nèi)存
end;
end;
try...finally結(jié)構(gòu)與try...except結(jié)構(gòu)在用法上主要有以下區(qū)別:
(1) 對(duì)于try...finally結(jié)構(gòu)來(lái)說(shuō),不管try部分的代碼是否觸發(fā)異常,finally部分總是執(zhí)行的。如果發(fā)生異常,就提前跳到finally部分。
而對(duì)于try...except結(jié)構(gòu)來(lái)說(shuō),只有當(dāng)觸發(fā)了異常后,才會(huì)執(zhí)行except部分的代碼。
(2) 在try...except結(jié)構(gòu)中,當(dāng)異常被處理后異常對(duì)象就被釋放,除非重新觸發(fā)異常。而在try...finally結(jié)構(gòu)中,即使finally部分對(duì)
異常作了處理,異常對(duì)象仍然存在。
(3) finally部分不能處理特定的異常,因?yàn)樗鼪](méi)有try...except結(jié)構(gòu)中的異常句柄,無(wú)法知道確切的異常類(lèi)型。因此,在finally部分只能
對(duì)異常作籠統(tǒng)的處理。
(4) 在try…finally結(jié)構(gòu)中,如果在try部分調(diào)用標(biāo)準(zhǔn)命令Exit、Break或Continue,將導(dǎo)致程序的執(zhí)行流程提前跳到finally部分。finally部
分不允許調(diào)用上述三個(gè)命令。
3.3.3 異常的重引發(fā)和處理嵌套
由于異常在處理后即被清除,因而當(dāng)希望對(duì)異常進(jìn)行多次處理時(shí)就需要使用保留字raise 來(lái)重引發(fā)一個(gè)當(dāng)前異常。
實(shí)例3-4 同時(shí)使用異常響應(yīng)和異常保護(hù)來(lái)處理被0除和釋放資源,當(dāng)異常響應(yīng)結(jié)束時(shí)利用raise重引發(fā)一個(gè)當(dāng)前異常。
var
pPointer:Pointer;
iInt,iDiv:Integer;
begin
iDiv:=0;
GetMem(pPointer,1024);
try
try
iInt:=10 div iDiv;
except
On EDivByZero do
begin
iInt:=0;
raise;
end;
end;
finally
FreeMem(pPointer,1024);
End;
End;
上面一段代碼體現(xiàn)了異常處理的嵌套。異常保護(hù)、異常響應(yīng)可以單獨(dú)嵌套也可以如上例所示的那樣相互嵌套。
3.3.4 定義自己的異常
利用Delphi的異常類(lèi)機(jī)制我們可以定義自己的異常類(lèi)來(lái)處理程序執(zhí)行中的異常情況。同標(biāo)準(zhǔn)異常不同的是:這種異常情況并不是相對(duì)于系統(tǒng)的
正常運(yùn)行,而是應(yīng)用程序的預(yù)設(shè)定狀態(tài)。比如輸入一個(gè)非法的口令、輸入數(shù)據(jù)值超出設(shè)定范圍、計(jì)算結(jié)果偏離預(yù)計(jì)值等等。
使用自定義異常需要:
(1)自己定義一個(gè)異常對(duì)象類(lèi);
(2)自己引發(fā)一個(gè)異常。
1.定義異常對(duì)象類(lèi)
異常是對(duì)象,所以定義一類(lèi)新的異常同定義一個(gè)新的對(duì)象類(lèi)型并無(wú)太大區(qū)別。由于缺省異常處理只處理從Exception或Exception子類(lèi)繼承的對(duì)
象,因而自定義異常類(lèi)應(yīng)該作為Exception或其它標(biāo)準(zhǔn)異常類(lèi)的子類(lèi)。這樣,假如在一個(gè)模塊中引發(fā)了一個(gè)新定義的異常,而這個(gè)模塊并沒(méi)有包
含對(duì)應(yīng)的異常響應(yīng),則缺省異常處理機(jī)制將響應(yīng)該異常,顯示一個(gè)包含異常類(lèi)名稱(chēng)和錯(cuò)誤信息的消息框。
下面是一個(gè)異常類(lèi)的定義:
type
EMyException=Class(Exception);
2.自引發(fā)異常
引發(fā)一個(gè)異常,調(diào)用保留字raise,后邊跟一個(gè)異常類(lèi)的實(shí)例。假如定義:
type
EPasswordInvalid=Class(Exception);
則在程序中如下的語(yǔ)句將引發(fā)一個(gè)EPasswordInvalid異常:
If Password<>CorrectPassword then
raise EPasswordInvalid.Create('Incorrect Password entered');
異常產(chǎn)生時(shí)把System庫(kù)單元中定義的變量ErrorAddr的值置為應(yīng)用程序產(chǎn)生異常處的地址。在你的異常處理過(guò)程中可以引用ErrorAddr的值。
在自己引發(fā)一個(gè)異常時(shí),同樣可以為ErrorAddr分配一個(gè)值。
為異常分配一個(gè)錯(cuò)誤地址需要使用保留字at,使用格式如下:
raise EInstance at Address_Expession;
下面通過(guò)例程來(lái)說(shuō)明類(lèi)型文件的使用。
實(shí)例3-5 編寫(xiě)一個(gè)利用自定義異常編程的完整實(shí)例。程序設(shè)計(jì)界面如圖3-1所示。
(1).新建應(yīng)用程序
啟動(dòng)Delphi,從[File]菜單中選取[New Application]命令,開(kāi)始一個(gè)新的應(yīng)用文件,應(yīng)用程序中將有一個(gè)缺省窗體Form1。
(2).設(shè)置窗體屬性
將窗體Form1的Caption屬性設(shè)置為“自定義異常”;Font屬性設(shè)置為宋體、五號(hào);Height和Width屬性分別設(shè)置為200、290。
圖3-1 自定義異常實(shí)例
(3).添加組件
往窗體Form1中添加兩個(gè)Label組件-Label1和Label2,兩個(gè)Edit組件-name屬性分別設(shè)為PassWord和InputEdit,編輯框用于輸入口令和代碼,
和兩個(gè)Button組件-Button1和Button2。
(4).設(shè)置組件屬性
表3-6 實(shí)例3-5組件屬性
組件 Caption Visible PassWordChar
Label1 輸入密碼:
Label2 輸入數(shù)字(0,1): false
PassWord *
InputEdit false
窗體的布置如圖3-1所示。
程序啟動(dòng)時(shí)Label2、InputEdit不可見(jiàn)。當(dāng)在PassWord中輸入正確的口令時(shí),Label2、InputBox出現(xiàn)在屏幕上。此時(shí)Labell、PassWord隱藏。通
過(guò)設(shè)置PassWord的PassWordChar為“*”,可以使輸入口令時(shí)回顯在屏幕上的字符為“*”。
(5).自定義異常
自定義異常EInvalidPassWord和EInvalidInput分別用于表示輸入的口令非法和數(shù)字非法。它們都是自定義異常EInValidation的子類(lèi)。而
EInValidation直接從Exception異常類(lèi)派生。
下面是三個(gè)異常類(lèi)的定義:
type
TForm1 = class(TForm)
……
private
{ Private declarations }
public
{ Public declarations }
end;
EInValidation=class(Exception)
public
ErrorCode:Integer;
constructor Create(Const Msg:String;ErrorNum:Integer);
end;
EInvalidPassWord=class(EInValidation)
public
constructor Create;
end;
EInvalidInput=class(EInValidation)
public
constructor Create(ErrorNum:Integer);
end;
EInValidation增加了一個(gè)公有成員ErrorCode來(lái)保存錯(cuò)誤代碼。錯(cuò)誤代碼的增加提供了很大的編程靈活性。對(duì)于異常類(lèi),可以根據(jù)錯(cuò)誤代碼提
供不同的錯(cuò)誤信息。對(duì)于使用者可以通過(guò)截取錯(cuò)誤代碼,在try...except模塊之外來(lái)處理異常。
從以上定義可以發(fā)現(xiàn):EInvalidPassWord和EInvalidInput的構(gòu)造函數(shù)參數(shù)表中沒(méi)有表示錯(cuò)誤信息的參數(shù)。事實(shí)上,它們保存在構(gòu)造函數(shù)內(nèi)部。
下面是三個(gè)自定義異常類(lèi)構(gòu)造函數(shù)的實(shí)現(xiàn)代碼。
constructor EInValidation.Create(Const Msg:String;ErrorNum:Integer);
begin
inherited Create(Msg);
ErrorCode:=ErrorNum;
end;
constructor EInValidPassWord.Create;
begin
inherited Create('輸入的密碼不正確',0);
end;
constructor EInValidInput.Create(ErrorNum:Integer);
var
Msg:String;
begin
case ErrorNum of
1:
Msg:='無(wú)法把字符轉(zhuǎn)換成數(shù)字';
2:
Msg:='數(shù)值超出范圍';
else
Msg:='輸入有錯(cuò)';
end;
inherited Create(Msg,ErrorNum);
end;
對(duì)于EInvalidInput,ErrorCode=1表示輸入的不是純數(shù)字序列,而ErrorCode=2表示輸入數(shù)值越界。
(6).添加事件
為窗體Form1中的PassWord和InputEdit編輯框添加OnKeyPress事件,事件響應(yīng)過(guò)程分別為PassWordKeyPress和InputEditKeyPress。
(7).編寫(xiě)事件代碼
口令檢查是用戶(hù)在PassWord中輸入口令并按下回車(chē)鍵后開(kāi)始的。即在PassWordKeyPress中,要對(duì)口令進(jìn)行核對(duì)操作,若不正確觸發(fā)異常
EInvalidPassWord。
procedure TForm1.PassWordKeyPress(Sender: TObject; var Key: Char);
const
CurrentPassWord='Delphi';
begin
if Key=#13 then
begin
try
if PassWord.text<>CurrentPassWord then
raise EInvalidPassWord.Create;
Label2.Visible:=True;
InputEdit.Visible:=True;
InputEdit.SetFocus;
PassWord.Visible:=False;
Label1.Visible:=False;
except
on EInvalidPassWord do
begin
PassWord.text:='';
raise;
end;
end;
Key:=#0;
end;
end;
同樣,在InputEdit的OnKeyPress事件處理過(guò)程中實(shí)現(xiàn)了輸入數(shù)字的合法性檢查:
procedure TForm1.InputEditKeyPress(Sender: TObject; var Key: Char);
var
Res:Real;
Code:Integer;
begin
if Key=#13 then
begin
try
val(InputEdit.text,Res,Code);
if Code<>0 then
raise EInValidInput.create(1);
if (Res>1) or (Res<0) then
raise EInValidInput.create(2);
MessageDlg('輸入正確',mtInformation,[mbOk],0);
Key:=#0;
except
on E:EInValidInput do
begin
InputEdit.text:='';
MessageDlg(E.Message,mtWarning,[mbOk],0);
end;
end;
end;
end;
(8).保存程序
將窗體Form1的單元文件保存為“Unit03_5.pas”,將項(xiàng)目文件保存為“Project03_5.dpr”。
(9).運(yùn)行程序
必須先編譯生成Project03_3.exe文件,然后在Windows資源管理器中運(yùn)行Project03_3.exe。運(yùn)行時(shí),不同的輸入會(huì)觸發(fā)不同的異常。
由于異常響應(yīng)后即被清除,所以要顯示異常信息,需要另外的手段。在以上兩段程序中我們采用了兩種不同的方法:在口令合法性檢查中,利用
異常重引發(fā)由系統(tǒng)進(jìn)行缺省響應(yīng);在輸入數(shù)字合法性檢查中,通過(guò)異常實(shí)例來(lái)獲取異常信息并由自己來(lái)顯示它。
以上所舉的是一個(gè)非常簡(jiǎn)單的例子,但從中已可以發(fā)現(xiàn):使用自定義異常編程,為程序設(shè)計(jì)帶來(lái)了很大的靈活性。
3.3.5 利用異常響應(yīng)編程
利用異常處理機(jī)制不僅能使程序更加健壯,而且也提供了一種使程序更加簡(jiǎn)捷、明了的途徑。事實(shí)上,使用自定義異常類(lèi)就是一種利用異常響
應(yīng)編程的方式。這里我們?cè)儆懻搸讉€(gè)利用標(biāo)準(zhǔn)異常類(lèi)編程的例子。
比如為了防止零作除數(shù),可以在進(jìn)行除法運(yùn)算前使用if…then…else語(yǔ)句。但如果有一系列這樣的語(yǔ)句,則繁瑣程度是令人難以忍受的。這時(shí)
候我們可能傾向于使用EDivByZero異常。例如如下一段程序就遠(yuǎn)比用if…then…else實(shí)現(xiàn)簡(jiǎn)捷明了。
function Calcu(x,y,z,a,b,c:Integer):Real;
begin
try
Result:=x/a+y/b+z/c;
except
On EdivByZerod do
Result:=0;
end;
end;
實(shí)例3-6 在文件的打開(kāi)與創(chuàng)建中可以利用異常響應(yīng)來(lái)實(shí)現(xiàn)文件的打開(kāi)或創(chuàng)建。
Procedure TRecFileForm.OpenButtonClick(Sender:TObject);
begin
if OpenDialog1.Execute then
FileName:=OpenDialog1.FileName
else
exit;
AssignFile(MethodFile,Filename);
try
Reset(MethodFile);
FileOpened:=True;
except
OnEInOutError do
begin
try
if FileExists(FileName)=False then
begin
ReWrite(MethodFile);
FileOpened:=True;
end
else
begin
FileOpened:=False;
MessageDlg(‘文件不能打開(kāi)’,mtWarning,[mbOK],0);
end;
except
On EInOutError do
begin
FileOpen:=False;
MessageDlg(‘文件不能創(chuàng)建’,mtWarning,[mbOK],0);
end;
end;
end;
end;
if FileOpened=False then exit;
Count:=FileSize(MethodFile);
if Count>0 then
ChangeGrid;
RecFileForm.Caption:=FormCaption+’-’+FileName;
NewButton.Enabled:=False;
OpenButtomEnabled:=False;
CloseButton.Enabled:=True;
end;
總之,利用異常響應(yīng)編程的中心思想是雖然存在預(yù)防異常發(fā)生的確定方法,但卻對(duì)異常的產(chǎn)生并不進(jìn)行事先預(yù)防,而是進(jìn)行事后處理,并以此
來(lái)簡(jiǎn)化程序的邏輯結(jié)構(gòu)。
3.4 Delphi調(diào)試器
Delphi 在IDE內(nèi)部集成了一個(gè)調(diào)試器,因而對(duì)程序的調(diào)試不用離開(kāi)集成開(kāi)發(fā)環(huán)境(IDE)就可以進(jìn)行。該調(diào)試器能夠控制程序的運(yùn)行、監(jiān)視程序的
輸出、檢查和修改變量的值。
3.4.1 準(zhǔn)備調(diào)試
在調(diào)試程序之前,必須保證程序代碼已經(jīng)沒(méi)有語(yǔ)法錯(cuò)誤,還要正確設(shè)置一些選項(xiàng)。為此,需要使用“Project”菜單上的“Options”命令打開(kāi)
“Project Options”對(duì)話框。翻到“Compiler”頁(yè),選中“Debug information”復(fù)選框(缺省就是選中)。這樣,編譯器將把所有的調(diào)試信
息加到.DCU文件和.EXE文件中。
VCL的代碼都是仔細(xì)調(diào)試過(guò)的,一般不會(huì)有錯(cuò)誤。如果仍然不放心,想跟蹤進(jìn)入VCL的內(nèi)部,則需要選中“Use Debug DCUs”復(fù)選框。
要說(shuō)明的是,調(diào)試信息將使可執(zhí)行文件增大,但不影響程序的性能和對(duì)內(nèi)存的需求。盡管如此,調(diào)試結(jié)束后,最好要打開(kāi)"Project Options”
對(duì)話框,翻到“Compiler”頁(yè),清除“Debug infomation”復(fù)選框,再重新編譯程序。這樣,程序中就不包含任何調(diào)試信息。
要使用內(nèi)部集成調(diào)試器來(lái)調(diào)試程序,還必須使用“Tools”菜單上的“Debugger Options”。命令打開(kāi)“Debugger Options”對(duì)話框,選中
“Integrated debugging”復(fù)選框(缺省就是選中)。否則,“Run”菜單上的調(diào)試命令將變灰。但要說(shuō)明的是,內(nèi)部集成的調(diào)試器可能會(huì)與某
些軟件沖突,從而引起應(yīng)用程序運(yùn)行異常。因此,在調(diào)試程序時(shí)最好把可能引起沖突的軟件退出運(yùn)行。
使用“Tools”菜單上的“Environment Options”命令打開(kāi)“Environment Options”對(duì)話框,翻到“Preferences”頁(yè)。如果選中“Hide
designers on run”復(fù)選框,當(dāng)程序運(yùn)行時(shí),Object Inspector和Form設(shè)計(jì)器將關(guān)閉,以騰出屏幕上的空間。如果選中“Minimize on run”
復(fù)選框,當(dāng)程序運(yùn)行時(shí),IDE將最小化,以避免屏幕上內(nèi)容太多太亂。不過(guò),當(dāng)程序暫停運(yùn)行時(shí),IDE的窗口會(huì)重新恢復(fù)成原先大小。
3.4.2 設(shè)置調(diào)試器的選項(xiàng)
1.設(shè)置調(diào)試器選項(xiàng)
要設(shè)置調(diào)試器的選項(xiàng),可以使用“Tools”菜單上的“Debugger Options”命令,Delphi將打開(kāi)“Debugger Options”對(duì)話框,如圖3-2所示。
有General頁(yè)、Event Log頁(yè)、Language Exceptions頁(yè)和OS Exceptions頁(yè),用來(lái)設(shè)置調(diào)試的一些環(huán)境、配置及方法等
圖3-2 “Debugger Options”對(duì)話框
2.編譯指令
默認(rèn)情況下,上述設(shè)置對(duì)整個(gè)項(xiàng)目的所有單元都有效。不過(guò),也可以讓這些設(shè)置只對(duì)部分單元有效,因?yàn)橛行﹩卧赡軟](méi)有問(wèn)題,不希望參加
調(diào)試。
要使某個(gè)單元不包含調(diào)試信息,就在這些單元中加入適當(dāng)?shù)木幾g指令,程序示例如下:
unit Unit1;
{$DEBUGINFO OFF}
interface
…
3.自定義調(diào)試器的顏色
在上冊(cè)中已提到,代碼編輯器可以用不同的顏色顯示不同的語(yǔ)法成分,在使用調(diào)試器調(diào)試程序時(shí)也有這個(gè)功能。例如,通常斷點(diǎn)用白底紅色表
示,當(dāng)前執(zhí)行點(diǎn)用白底藍(lán)色表示。
要自定義調(diào)試器的顏色,可以使用“Tools”菜單上的“Editor Options”命令打開(kāi)“Editor Properties”對(duì)話框,翻到“Colors”頁(yè),在“
Element”框中選擇某種語(yǔ)法元素,然后設(shè)置它的前景顏色和背景顏色。
3.5 控制程序的運(yùn)行
帶調(diào)試信息編譯了程序后,就可以調(diào)試程序了,調(diào)試器將接管對(duì)程序運(yùn)行的控制,但程序的運(yùn)行結(jié)果與在非調(diào)試狀態(tài)下運(yùn)行沒(méi)有什么兩樣,包
括建立窗口、接受用戶(hù)輸入、計(jì)算數(shù)值、響應(yīng)事件、訪問(wèn)數(shù)據(jù)庫(kù)等均照常進(jìn)行。
可以在單步執(zhí)行或跟蹤執(zhí)行時(shí)同步觀察應(yīng)用程序的輸出。為了避免出現(xiàn)令人討厭的閃爍,最好把應(yīng)用程序的窗口和代碼編輯器的窗口恰當(dāng)布局
,不要使它們相互覆蓋。
3.5.1 單步執(zhí)行
通過(guò)[Run]菜單上的[Step Over]命令,可以單步執(zhí)行程序。所謂單步執(zhí)行,就是一次只執(zhí)行一行(一個(gè)指令),這樣就可以知道哪一行或指令引
起了運(yùn)行期錯(cuò)誤或邏輯錯(cuò)誤。
[Step Over]命令將把整個(gè)過(guò)程或函數(shù)當(dāng)作一行。如果把幾條語(yǔ)句寫(xiě)在一行上,調(diào)試器將把這幾條語(yǔ)句當(dāng)作一條語(yǔ)句。這樣,就無(wú)法單獨(dú)調(diào)試其
中的某一個(gè)語(yǔ)句。如果把一條長(zhǎng)語(yǔ)句分成幾行寫(xiě),調(diào)試器仍然把這幾行當(dāng)作一行。
調(diào)試器每執(zhí)行一行,當(dāng)前執(zhí)行點(diǎn)就自動(dòng)移到下一個(gè)要執(zhí)行的行上,但不一定是源代碼的下一行。例如,若正在執(zhí)行的是Goto語(yǔ)句,當(dāng)前執(zhí)行點(diǎn)
將移到Goto語(yǔ)句跳轉(zhuǎn)到的行上。
另外還有一種情況就是,如果開(kāi)啟了優(yōu)化編譯的選項(xiàng),某些源代碼行將被合并或越過(guò),這時(shí)候,當(dāng)前執(zhí)行點(diǎn)不會(huì)移到這些行上。
為了清晰地看出當(dāng)前執(zhí)行點(diǎn)在哪兒,代碼編輯器將用白底藍(lán)色顯示當(dāng)前執(zhí)行點(diǎn),同時(shí),在“裝訂區(qū)”顯示一個(gè)綠色的箭頭指向當(dāng)前執(zhí)行點(diǎn),如
圖3-3所示。
圖3-3 當(dāng)前執(zhí)行點(diǎn)
3.5.2 跟蹤執(zhí)行
[Run]菜單上的[Trace Into]命令用于跟蹤程序。與單步執(zhí)行相似,這條命令一次也只執(zhí)行一行。不同的是,執(zhí)行到有函數(shù)調(diào)用的行時(shí),這條命
令將進(jìn)入函數(shù)的內(nèi)部。
如果程序鏈接了外部代碼諸如動(dòng)態(tài)鏈接庫(kù),只要?jiǎng)討B(tài)鏈接庫(kù)包含了符號(hào)調(diào)試信息,就可以跟蹤進(jìn)入這些外部代碼。否則,調(diào)試器將把動(dòng)態(tài)鏈接
庫(kù)當(dāng)作一行處理。
在調(diào)試過(guò)程中,可以根據(jù)需要交替使用單步執(zhí)行和跟蹤執(zhí)行。例如,對(duì)有疑問(wèn)的調(diào)用命令使用跟蹤執(zhí)行,使控制進(jìn)入被調(diào)用部分內(nèi)部,調(diào)試該
被調(diào)用部分。而對(duì)有把握的調(diào)用命令使用單步執(zhí)行,從而直接跳過(guò)被調(diào)用部分的調(diào)試,這樣能提高調(diào)試效率。
“Trace Into”命令也能夠進(jìn)入事件句柄的內(nèi)部,就像進(jìn)入一般的函數(shù)內(nèi)部一樣。要注意的是,OnPaint事件是當(dāng)應(yīng)用程序的窗口需要重畫(huà)的時(shí)
候觸發(fā)的,當(dāng)進(jìn)入處理該事件的句柄內(nèi)部時(shí),代碼編輯器的窗口將推到前端。也就是說(shuō),此時(shí)窗口需要重畫(huà)了,這樣又將觸發(fā)OnPaint事件。而
一旦進(jìn)入處理OnPaint事件的句柄內(nèi)部,代碼編輯器的窗口又將被推到前端。如此反復(fù),構(gòu)成無(wú)限循環(huán)。要解決上述問(wèn)題,必須把代碼編輯器與
應(yīng)用程序的窗口在屏幕上重新排列,不要相互覆蓋。
程序往往大量調(diào)用了VCL的方法,一般情況下,不要跟蹤進(jìn)入VCL的內(nèi)部,因?yàn)閂CL的源代碼一般是不會(huì)出錯(cuò)的。如果懷疑VCL中可能出錯(cuò),或者
想進(jìn)入VCL方法的內(nèi)部看看方法是怎樣實(shí)現(xiàn)的,你也可以進(jìn)入VCL方法的內(nèi)部。Delphi Enterprise和Delphi Professional附帶了VCL的源代碼,
而且還提供了帶調(diào)試信息的VCL庫(kù)。
3.5.3 跳過(guò)一段代碼
為了節(jié)省時(shí)間和提高工作效率,不必每次都從頭開(kāi)始單步或跟蹤執(zhí)行程序,可以一下子跳到有疑問(wèn)的地方,然后再一行一行地執(zhí)行程序。
[Run]菜單上的[Run to Cursor]命令可以實(shí)現(xiàn)這個(gè)功能。它能夠先以非調(diào)試方式執(zhí)行到光標(biāo)所在的行,再接管對(duì)程序的控制,單步或跟蹤執(zhí)行
以后的代碼。
如果光標(biāo)所在的行不包含調(diào)試信息,調(diào)試器將推出一個(gè)錯(cuò)誤框顯示“No code was generated for the current Line”。
如果不小心進(jìn)入了例程的內(nèi)部,想馬上退出來(lái),可以把光標(biāo)移到該例程的最后一行,使用[Run to Cursor]命令,再使用[Step Over]命令,就
可以返回到調(diào)用該例程的地方。
3.5.4 全速執(zhí)行剩余的代碼
如果不小心進(jìn)入了一個(gè)例程,但又不想調(diào)試這個(gè)例程,或者確信該例程的代碼沒(méi)有問(wèn)題,從而想盡快退出這個(gè)例程,你可以使用[Run]菜單上的
[Run Until Return]命令。這個(gè)命令將全速執(zhí)行該例程的代碼,直到返回為止。
3.5.5 返回到執(zhí)行點(diǎn)
在調(diào)試過(guò)程中,隨時(shí)可以切換到IDE或其他程序中,可以進(jìn)行各種操作。
如果要重新回到調(diào)試器的當(dāng)前執(zhí)行點(diǎn),可以使用[Run]菜單上的[Show Execution Point]命令,光標(biāo)將自動(dòng)回到先前的執(zhí)行點(diǎn)上。
如果包含執(zhí)行點(diǎn)的源文件已關(guān)閉,調(diào)試器將重新打開(kāi)這個(gè)源文件。如果執(zhí)行點(diǎn)沒(méi)有對(duì)應(yīng)的源代碼,Delphi將打開(kāi)CPU窗口,顯示相應(yīng)的機(jī)器指令
。
3.5.6 暫停運(yùn)行
使用[Run]菜單上的[Program Pause]命令將使程序運(yùn)行暫時(shí)停止,這樣就可以檢查程序在此狀態(tài)下的輸出或變量的值是否正確,檢查完以后,
可以繼續(xù)對(duì)程序進(jìn)行調(diào)試,或者修改變量的值再讓程序繼續(xù)執(zhí)行,以便看程序?qū)π碌闹禃?huì)作出什么反應(yīng)。
有時(shí)候,程序暫停后無(wú)法回到調(diào)試器中繼續(xù)運(yùn)行,這時(shí)候可以同時(shí)按—下Ctrl+Alt+SysRq鍵終止程序的運(yùn)行,如果按一次不行,就多按幾次。
3.5.7 重新開(kāi)始運(yùn)行
在調(diào)試過(guò)程中,可以使用[Run]菜單上的[Program Reset]命令中止程序的運(yùn)行并釋放所有占用的內(nèi)存和資源,關(guān)閉所有打開(kāi)的文件,清除所有
的變量設(shè)置,然后重新運(yùn)行程序。
這通常用于在調(diào)試過(guò)程中發(fā)現(xiàn)了錯(cuò)誤并更改了源代碼后需要重新編譯和運(yùn)行的情況。
“Program Reset”命令并不刪除先前設(shè)置的斷點(diǎn)和觀察表達(dá)式,因?yàn)橹匦麻_(kāi)始調(diào)試程序時(shí)可能還要用到這些設(shè)置。
“Program Reset”命令可能不能很“干凈”地釋放應(yīng)用程序占用的所有資源,這樣可能導(dǎo)致其他程序運(yùn)行失敗,碰到這種情形應(yīng)當(dāng)退出Delphi
或者重新啟動(dòng)Windows。
3.5.8 命令行參數(shù)
如果要調(diào)試的程序需要傳遞參數(shù),可以使用[Run]菜單上的[Parameters]命令打開(kāi)[Run Parameters]對(duì)話框,如圖3-4所示。
圖3-4 命令行參數(shù)
在“Parameters”框內(nèi)鍵入要傳遞的參數(shù),也可以從以前鍵入過(guò)的參數(shù)中選擇一個(gè)。
3.6 斷點(diǎn)
所謂斷點(diǎn),就是在程序代碼的某一行上設(shè)一個(gè)標(biāo)記,程序執(zhí)行到這里將暫停,由調(diào)試器接管對(duì)程序的控制。使用斷點(diǎn)與使用[Run to Cursor]命
令有點(diǎn)相似,都是執(zhí)行到某一行后暫停。不同的是,程序中可以設(shè)多個(gè)斷點(diǎn)并且能夠給斷點(diǎn)設(shè)置條件。
斷點(diǎn)通常設(shè)在懷疑有問(wèn)題的區(qū)域。在遇到斷點(diǎn)之前,程序以全速運(yùn)行。遇到斷點(diǎn)之后,程序暫時(shí)停止運(yùn)行,以后就可以單步或跟蹤執(zhí)行程序。
3.6.1 源代碼斷點(diǎn)
要在代碼編輯器中設(shè)置源代碼斷點(diǎn),有以下4種操作方式:
(1) 把光標(biāo)移到要設(shè)為斷點(diǎn)的行上,按下F5鍵;
(2) 用鼠標(biāo)左鍵單擊要設(shè)為斷點(diǎn)的行的最左端;
(3) 用鼠標(biāo)右鍵單擊要設(shè)為斷點(diǎn)的行,在彈出的菜單中選擇“Debug”命令,再選擇“Toggle Breakpoint”;
(4) 使用“Run”菜單上的“Add Breakpoint”命令,再選擇“Source Breakpoint”,Delphi將打開(kāi)“Add Source Breakpoint”對(duì)話框,如圖
3-5所示。
“File name”框用于輸入斷點(diǎn)所在的源文件名(包含路徑)。
“Line number”框用于輸入斷點(diǎn)所在的行號(hào)。
“Condition”框用于設(shè)置斷點(diǎn)有效的條件,通常是一個(gè)布爾表達(dá)式。布爾表達(dá)式中可以包含函數(shù)調(diào)用,只要該函數(shù)返回布爾值即可。
圖3-5 “Add Source Breakpoint”對(duì)話框
當(dāng)程序執(zhí)行到這個(gè)斷點(diǎn)時(shí),首先計(jì)算該布爾表達(dá)式的值。如果值為T(mén)rue,則斷點(diǎn)有效,程序?qū)和_\(yùn)行。如果值為False,則斷點(diǎn)無(wú)效,程序?qū)?/font>
繼續(xù)執(zhí)行。
“Pass count”框用于指定經(jīng)過(guò)斷點(diǎn)多少次后斷點(diǎn)有效。例如,在一個(gè)For循環(huán)中設(shè)一個(gè)斷點(diǎn),每次循環(huán)時(shí)都會(huì)遇到這個(gè)斷點(diǎn)。但并非每次遇到
斷點(diǎn)時(shí)程序都會(huì)暫停,因?yàn)檫€需要經(jīng)過(guò)一定次數(shù)后斷點(diǎn)才有效。
“Group”框用于把斷點(diǎn)分組。你可以在這個(gè)框內(nèi)輸入一個(gè)新的組名,也可以選擇一個(gè)已有的組名。一旦若干個(gè)斷點(diǎn)編成組,就可以分別使用“
Disable Group”命令和“Enable Group”命令成組地被禁止或允許它們,還可以給一組斷點(diǎn)指定一系列動(dòng)作。
注意:設(shè)為斷點(diǎn)的行必須是可執(zhí)行的代碼行。如果把斷點(diǎn)設(shè)在注釋行、空行、變量聲明的行上,調(diào)試器將認(rèn)為斷點(diǎn)無(wú)效。
默認(rèn)情況下,斷點(diǎn)所在的行用白底紅字顯示,并且在裝訂區(qū)有一個(gè)填為紅色的小圓圈。如圖3-6所示。
當(dāng)鼠標(biāo)指向這個(gè)小圓圈時(shí),將彈出一個(gè)提示窗口,顯示斷點(diǎn)的條件和經(jīng)過(guò)次數(shù)。
圖3-6 斷點(diǎn)
3.6.2 機(jī)器指令斷點(diǎn)
Delphi允許針對(duì)某個(gè)機(jī)器指令設(shè)斷點(diǎn)。當(dāng)程序執(zhí)行到這個(gè)指令時(shí),就會(huì)暫停(必須執(zhí)行到斷點(diǎn)處設(shè)置)。要設(shè)置機(jī)器指令斷點(diǎn),有下列幾種方式
:
(1) 在CPU窗口中用鼠標(biāo)左鍵單擊某個(gè)指令的裝訂區(qū);
(2) 在CPU窗口中選擇一個(gè)指令,然后按F5鍵;
(3) 在CPU窗口中用鼠標(biāo)右鍵單擊某個(gè)指令,在彈出的菜單中選擇“Toggle breakpoint”命令;
(4) 使用“Run”菜單上的“Add Breakpoint”命令,再選擇“Address Breakpoint”,Delphi將打開(kāi)“Add Address Breakpoint”對(duì)話框,如
圖3-7所示。
圖3-7 “Add Address Breakpoint”對(duì)話框
“Address”框用于指定斷點(diǎn)(在這兒是機(jī)器指令)的地址。
“Condition”框用于設(shè)置斷點(diǎn)有效的條件。
“Pass count”框用于指定經(jīng)過(guò)斷點(diǎn)多少次后斷點(diǎn)有效。
“Group”框用于把斷點(diǎn)分組。
當(dāng)程序執(zhí)行到這條指令時(shí),就會(huì)暫停(如果滿(mǎn)足有關(guān)條件的話)。
3.6.3 數(shù)據(jù)斷點(diǎn)
Delphi能夠監(jiān)視指針錯(cuò)誤。如果內(nèi)存的某個(gè)地址被改寫(xiě),程序就會(huì)暫停,由調(diào)試器接管控制權(quán),就好像遇到斷點(diǎn)一樣。
要設(shè)置這樣的斷點(diǎn),可以使用“Run”菜單上的“Add Breakpoint”命令,再選擇“Data Breakpoint”,Delphi將顯示“Add Data
Breakpoint”對(duì)話框,如圖3-8所示。
圖3-8 “Add Data Breakpoint”對(duì)話框
“Address”框用于指定要監(jiān)視的內(nèi)存地址??梢枣I入一個(gè)變量名。
“Length”框用于指定數(shù)據(jù)的長(zhǎng)度(字節(jié)數(shù))。如果“Address”框鍵入的是一個(gè)變量名的話,則“Length”框可以空著,因?yàn)镈elphi會(huì)自動(dòng)計(jì)
算出該變量的長(zhǎng)度。
“Condition”框用于設(shè)置斷點(diǎn)有效的條件,通常是一個(gè)布爾表達(dá)式。
“Pass count”框用于指定經(jīng)過(guò)斷點(diǎn)多少次后斷點(diǎn)有效。
“Group”框用于把斷點(diǎn)分組。
要說(shuō)明的是,當(dāng)本次調(diào)試結(jié)束時(shí),所有的數(shù)據(jù)斷點(diǎn)都會(huì)被禁止。下次調(diào)試時(shí),如果還要用到這些數(shù)據(jù)斷點(diǎn)的話,需要使它們有效。
3.6.4 模塊斷點(diǎn)
要監(jiān)視模塊的第一次加載,可以使用“Run”菜單上的“Add Breakpoint”命令,再選擇“Module Load Breakpoint”,Delphi將打開(kāi)“Add
Module”對(duì)話框,如圖3-9所示。
圖3-9“Add Module”對(duì)話框
在“Module Name”框內(nèi)指定一個(gè)要監(jiān)視的模塊,通常是DLL或BPL。也可以單擊“Browser”按鈕定位一個(gè)模塊。以后,當(dāng)這個(gè)模塊第一次調(diào)入
內(nèi)存時(shí),程序就會(huì)暫停,由調(diào)試器接管控制權(quán),就好像遇到斷點(diǎn)一樣。
3.6.5 指定遇到斷點(diǎn)時(shí)的行為
一般來(lái)說(shuō),當(dāng)遇到斷點(diǎn)時(shí),程序?qū)和?。不過(guò),在Delphi中,除了使程序暫停外,還可以指定其他行為。
要指定遇到斷點(diǎn)時(shí)的行為,可以在設(shè)置斷點(diǎn)時(shí)進(jìn)行。使用“Run”菜單上的“Add Breakpoint”命令,再選擇一種斷點(diǎn)類(lèi)型,Delphi將打開(kāi)
“Add Breakpoint”對(duì)話框。然后,單擊“Advanced”按鈕,“Add Breakpoint”對(duì)話框?qū)⒈徽归_(kāi),如圖3-10所示。
圖3-10 指定遇到斷點(diǎn)時(shí)的行為
如果選中“Break”復(fù)選框,當(dāng)遇到斷點(diǎn)時(shí),程序?qū)和!_@是默認(rèn)的行為。
如果選中“Ignore subsequent exceptions”復(fù)選框,以后將忽略當(dāng)前進(jìn)程觸發(fā)的異常,也就是說(shuō),遇到異常時(shí)調(diào)試器不會(huì)停止。這個(gè)復(fù)選框
與下面的“Handle subsequent exceptions”復(fù)選框成對(duì)使用,可以使一段代碼忽略異常。
如果選中“Handle subsequent exceptions”復(fù)選框,以后將處理當(dāng)前進(jìn)程觸發(fā)的異常,也就是說(shuō),當(dāng)調(diào)試器遇到“Debugger Options”對(duì)話
框中指定的異常時(shí)將停止。
“Log message”框讓你指定一個(gè)消息。當(dāng)遇到斷點(diǎn)時(shí),將記載這個(gè)消息。
“Eval expression”框讓你指定一個(gè)表達(dá)式。如果下面的“Log result”復(fù)選框被選中的話,當(dāng)遇到斷點(diǎn)時(shí),將計(jì)算這個(gè)表達(dá)式并記載計(jì)算結(jié)
果。
“Enable group”框可以使一組斷點(diǎn)有效。
“Disable group”框可以禁止—組斷點(diǎn)。
如果—個(gè)斷點(diǎn)被指定了多個(gè)行為,則當(dāng)遇到斷點(diǎn)時(shí),將依次執(zhí)行這些行為。
3.6.6 斷點(diǎn)列表窗口
如果定義了很多斷點(diǎn),或者斷點(diǎn)不在當(dāng)前的編輯窗口中,可以通過(guò)斷點(diǎn)列表窗口來(lái)查找斷點(diǎn)并且在源代碼中定位它。
使用“View”菜單上的“Debug Windows”命令,再選擇“Breakpoints”,Delphi將打開(kāi)斷點(diǎn)列表窗口,如圖3-11所示。
圖3-11 斷點(diǎn)列表
對(duì)于源代碼斷點(diǎn)來(lái)說(shuō),斷點(diǎn)列表窗口將顯示斷點(diǎn)所在的源文件名稱(chēng)、行號(hào)、條件和經(jīng)過(guò)次數(shù)。
對(duì)于機(jī)器指令斷點(diǎn)來(lái)說(shuō),斷點(diǎn)列表窗口將顯示斷點(diǎn)所在的源文件名稱(chēng)、行號(hào)加一個(gè)16進(jìn)制的偏移量,這個(gè)偏移量就是該指令距離源代碼行機(jī)器
指令起始點(diǎn)的字節(jié)數(shù)。不過(guò),有時(shí)候,—條機(jī)器指令并不—定對(duì)應(yīng)著一條源代碼行,此時(shí),就不顯示行號(hào)。
對(duì)于數(shù)據(jù)斷點(diǎn),斷點(diǎn)列表窗口將顯示數(shù)據(jù)的名稱(chēng)或地址以及長(zhǎng)度。
Delphi的斷點(diǎn)列表窗口還有兩欄,分別用于顯示斷點(diǎn)的行為和所屬的組名。
要在源代碼中定位斷點(diǎn)所在的行,或者在CPU窗口中定位斷點(diǎn)所在的機(jī)器指令,可以在斷點(diǎn)列表窗口中用鼠標(biāo)右鍵單擊某個(gè)斷點(diǎn),在彈出的菜單
中選擇“View Source”或“Edit Source”命令。如果選擇“View Source”命令,光標(biāo)將定位于要找的斷點(diǎn)上,但斷點(diǎn)列表窗口仍然是當(dāng)前
活動(dòng)的窗口,以便繼續(xù)在斷點(diǎn)列表窗口中查找其他斷點(diǎn)。如果選擇的是“Edit Source”命令,代碼編輯器將成為當(dāng)前活動(dòng)的窗口,這樣就可
以編輯源代碼。
3.6.7 刪除斷點(diǎn)
刪除斷點(diǎn)并不是刪除斷點(diǎn)所在的行或指令,只是取消斷點(diǎn)的定義,程序執(zhí)行到這兒不會(huì)暫停。
如果僅僅要?jiǎng)h除一個(gè)斷點(diǎn),有下列幾個(gè)操作方式:
(1) 在斷點(diǎn)列表窗口中用鼠標(biāo)右鍵單擊斷點(diǎn),在彈出的菜單中選擇“Delete”命令;
(2) 在斷點(diǎn)列表窗口中選擇一個(gè)斷點(diǎn),然后按Delete鍵或按Ctrl+D鍵;
(3) 在代碼編輯器或CPU窗口中用鼠標(biāo)右鍵單擊斷點(diǎn),在彈出的菜單中選擇“Debug”命令,再選擇“Toggle Breakpoint”;
(4) 在代碼編輯器或CPU窗口中把光標(biāo)移到斷點(diǎn)上,按下F5鍵;
(5) 在代碼編輯器或CPU窗口中用鼠標(biāo)左鍵單擊斷點(diǎn)的最左端。
如果要?jiǎng)h除所有斷點(diǎn),可以在斷點(diǎn)列表窗口中單擊鼠標(biāo)右鍵,在彈出的菜單中選擇“Delete All”命令。
3.6.8 設(shè)置斷點(diǎn)的屬性
要設(shè)置斷點(diǎn)的屬性,需要打開(kāi)“Breakpoint Properties”對(duì)話框。為此,你可以在斷點(diǎn)列表窗口中用鼠標(biāo)右鍵單擊斷點(diǎn),在彈出的菜單中選擇
“Properties”命令;或者在代碼編輯器或CPU窗口中用鼠標(biāo)右鍵單擊小圓圈,在彈出的菜單中選擇“Breakpoint Properties”命令。
以源代碼斷點(diǎn)為例,“Source Breakpoint Properties”對(duì)話框如圖3-12所示。
圖3-12 修改斷點(diǎn)的屬性
“Filename”框用于指定包含斷點(diǎn)的文件名及其路徑。
“Line number”框用于指定斷點(diǎn)所在的行號(hào)。
“Condition”框用于設(shè)置斷點(diǎn)有效的條件,通常是一個(gè)布爾表達(dá)式。
“Pass count”框用于指定經(jīng)過(guò)斷點(diǎn)多少次后斷點(diǎn)有效。
“Group”框用于把斷點(diǎn)分組。
3.6.9 禁止和允許斷點(diǎn)
設(shè)置了斷點(diǎn)以后,也可以暫時(shí)禁止這個(gè)斷點(diǎn),使這個(gè)斷點(diǎn)暫時(shí)無(wú)效。程序執(zhí)行到這里將不會(huì)停下來(lái),但斷點(diǎn)的屬性繼續(xù)保留,以后還可以用它
。
要暫時(shí)禁止斷點(diǎn),可以在斷點(diǎn)列表窗口中用鼠標(biāo)右鍵單擊斷點(diǎn),在彈出的菜單中不要選中“Enabled”命令。要重新允許這個(gè)斷點(diǎn),可以重新選
中“Enabled”命令。
3.7 監(jiān)視表達(dá)式的值
在單步執(zhí)行或跟蹤執(zhí)行程序的時(shí)候,可以同步監(jiān)視某個(gè)表達(dá)式的值。通過(guò)監(jiān)視表達(dá)式的值,可以分析程序是執(zhí)行到哪里才開(kāi)始不正常的。
要監(jiān)視表達(dá)式的值,使用[Run]菜單上的[Add Watch]命令或[Evaluate/Modify]命令,前者可以同時(shí)觀察多個(gè)表達(dá)式的值,后者只能觀察一個(gè)表
達(dá)式的值但可以修改。也可以使用[Inspect]命令,這個(gè)命令適合于監(jiān)視結(jié)構(gòu)型數(shù)據(jù)的值。
3.7.1 觀察窗口
要用觀察窗口監(jiān)視表達(dá)式的值,有下列3種操作方式:
(1) 直接使用[Run]菜單上的[Add Watch]命令;
(2) 在代碼編輯器中把光標(biāo)移到某個(gè)表達(dá)式上,然后按Ctrl+F5鍵,或者用鼠標(biāo)右鍵單擊某個(gè)表達(dá)式,在彈出的菜單中選擇“Debug”下的“Add
Watch At Cursor”命令;
(3) 使用“View”菜單上的“Debug Windows”命令,再選擇“Watch”,然后在觀察窗口中單擊鼠標(biāo)右鍵,在彈出的菜單中選擇“Add Watch”
命令。
實(shí)際上,上述三種操作都會(huì)打開(kāi)“Watch Properties”對(duì)話框,如圖3-13所示。
圖3-13 設(shè)置表達(dá)式的觀察屬性
如果“Watch Properties”對(duì)話框是用第一或第三種操作方式打開(kāi)的,首先要在“Expression”框內(nèi)輸入要監(jiān)視的表達(dá)式;如果對(duì)話框是用第
二種方式打開(kāi)的,光標(biāo)所在的表達(dá)式已經(jīng)自動(dòng)出現(xiàn)在“Expression”框內(nèi)。當(dāng)然,不管哪種方式,都可以鍵入一個(gè)新的表達(dá)式或者從下拉列表
中選擇一個(gè)過(guò)去曾經(jīng)鍵入過(guò)的表達(dá)式。
要注意的是,在“Expression”框內(nèi)鍵入的表達(dá)式必須符合Object Pascal的語(yǔ)法規(guī)則。表達(dá)式中可以包含函數(shù)調(diào)用。
通過(guò)觀察窗口可以監(jiān)視某個(gè)對(duì)象的屬性。從表面上看,屬性就好像一個(gè)變量一樣。實(shí)際上,對(duì)象的屬性是通過(guò)對(duì)象內(nèi)部的函數(shù)調(diào)用訪問(wèn)的,因
此,直接監(jiān)視對(duì)象的屬性可能會(huì)產(chǎn)生意想不到的結(jié)果。解決上述問(wèn)題的辦法很簡(jiǎn)單,就是把屬性的值賦給一個(gè)變量,通過(guò)監(jiān)視這個(gè)變量間接地
監(jiān)視這個(gè)屬性。不過(guò),這又帶來(lái)一個(gè)新的問(wèn)題,就是編譯器可能會(huì)認(rèn)為這個(gè)賦值是沒(méi)意義的,從而把它優(yōu)化掉。事實(shí)上,這個(gè)變量的賦值確是
沒(méi)意義的,因?yàn)榇a中沒(méi)有任何地方再引用這個(gè)變量。要解決這個(gè)問(wèn)題也有辦法,就是把這個(gè)變量聲明為全局變量,使編譯器難以簡(jiǎn)單地判斷
出這個(gè)賦值沒(méi)意義。
假設(shè)要監(jiān)視的數(shù)組是這樣聲明的:
Var
MyArray:array [1..1000] of char;
可以看出,這個(gè)數(shù)組很大。如果在“Expression”框內(nèi)鍵入MyArray,這意味著要監(jiān)視它的每一個(gè)元素,而實(shí)際上關(guān)心的往往只是其中一部分元
素,例如第900到909個(gè)元素。這時(shí),應(yīng)當(dāng)在“Expression”框內(nèi)鍵入MyArray[900],同時(shí)在“Repeat count”框內(nèi)鍵入10,表示讓觀察窗口顯
示從MyArray[900]開(kāi)始的10個(gè)元素。
如果要監(jiān)視的表達(dá)式是浮點(diǎn)數(shù),可以在“Digits”框內(nèi)指定數(shù)據(jù)的有效位。
如果選中“Enabled”復(fù)選框,表示該表達(dá)式將加到觀察窗口。如果不選中“Enabled”復(fù)選框,表示該表達(dá)式不加到觀察窗口,但其觀察屬性
不會(huì)丟失。
默認(rèn)情況下,調(diào)試器根據(jù)表達(dá)式本來(lái)的數(shù)據(jù)類(lèi)型選擇一種最合適的格式來(lái)顯示表達(dá)式的值,但也可以讓表達(dá)式以另一種格式輸出。例如,一個(gè)
整型變量本來(lái)以十進(jìn)制顯示,你可以讓它以十六進(jìn)制顯示。
設(shè)置好觀察屬性后,單擊[OK]按鈕,就把表達(dá)式加到觀察窗口中,如圖3-14所示。
圖3-14 觀察窗口
在觀察窗口中,如果要修改某個(gè)表達(dá)式的觀察屬性,可以用鼠標(biāo)右鍵單擊該表達(dá)式,在彈出的菜單中選擇“Edit Watch”命令,Delphi將打開(kāi)
“Watch Properties”對(duì)話框。
如果要暫時(shí)禁止某個(gè)觀察表達(dá)式,可以在觀察窗口中用鼠標(biāo)右鍵單擊該表達(dá)式,在彈出的菜單中選擇“Disable Watch”命令。如果要禁止所有
的觀察表達(dá)式,可以在觀察窗口中單擊鼠標(biāo)右鍵,在彈出的菜單中選擇“Disable All Watches”命令。
如果要重新允許某個(gè)觀察表達(dá)式,可以在觀察窗口中用鼠標(biāo)右鍵單擊該表達(dá)式,在彈出的菜單中選擇“Enable Watch”命令。如果要允許所有
的觀察表達(dá)式,可以在觀察窗口中單擊鼠標(biāo)右鍵,在彈出的菜單中選擇“Enable All Watches”命令。
如果要永久刪除某個(gè)觀察表達(dá)式,可以在觀察窗口中用鼠標(biāo)右鍵單擊該表達(dá)式,在彈出的菜單中選擇“Delete Watch”命令;如果要?jiǎng)h除所有
的觀察表達(dá)式,可以在觀察窗口中單擊鼠標(biāo)右鍵,在彈出的菜單中選擇“Delete All Watches”命令。
3.7.2 計(jì)算和修改表達(dá)式的值
要計(jì)算和修改一個(gè)表達(dá)式的值,可以使用“Run”菜單上的“Evaluate/Modify”命令,或在代碼編輯器中用鼠標(biāo)右鍵單擊某個(gè)表達(dá)式,在彈出
的菜單中選擇“Debug”命令,再選擇“Evaluate/Modify”,Delphi將打開(kāi)“Evaluate/Modify”對(duì)話框,如圖3-15所示。
圖3-15 Evaluate/Modify對(duì)話框
在“Expression”框內(nèi)鍵入一個(gè)表達(dá)式或從下拉列表框中選擇一個(gè)已有的表達(dá)式,它可以是對(duì)象的屬性,例如Buttonl.Height。
要計(jì)算表達(dá)式的值,可以單擊“Evaluate”按鈕,表達(dá)式的值將出現(xiàn)在“Result”框內(nèi)。
要計(jì)算的表達(dá)式可以是任何符合Object Pascal語(yǔ)法規(guī)則的表達(dá)式,但不能包含函數(shù)調(diào)用或者當(dāng)前執(zhí)行點(diǎn)上沒(méi)有定義的局部變量。
“Evaluate/Modify”對(duì)話框最重要的功能就是能修改表達(dá)式的值。這樣,在調(diào)試過(guò)程中,可以用不同的值去觀察程序的反應(yīng)。
要修改表達(dá)式的值,可以在“New Value”框內(nèi)鍵入一個(gè)新的值,然后單擊“Modify”按鈕。此時(shí),表達(dá)式在內(nèi)存中的值就被新的值替換。要注
意的是,在“New Value”框內(nèi)鍵入的值必須與表達(dá)式的類(lèi)型兼容,否則有可能引起錯(cuò)誤。
“Evaluate/Modify”對(duì)話框是非模態(tài)的,所以即使沒(méi)有關(guān)閉該對(duì)話框,也可以把輸入焦點(diǎn)移走,以便使用“Run”菜單上的“Step Over”命
令繼續(xù)執(zhí)行程序,看看新的值對(duì)程序有什么影響。如果在執(zhí)行過(guò)程中表達(dá)式的值又發(fā)生了變化,“Evaluate/Modify”對(duì)話框的“Result”框
不會(huì)自動(dòng)更新,需要單擊“Evaluate”按鈕手動(dòng)更新表達(dá)式的值。
“Evaluate/Modify”對(duì)話框的“Result”框是一個(gè)多行的編輯框,也就是說(shuō),“Evaluate/Modify”對(duì)話框比觀察窗口更適合于顯示大型的
數(shù)據(jù)如記錄和對(duì)象。
3.7.3 計(jì)算提示
當(dāng)處于調(diào)試暫停的狀態(tài)時(shí),在代碼編輯器中只要把鼠標(biāo)指向某個(gè)變量,在該變量的附近將彈出一個(gè)小窗口,窗口內(nèi)顯示該變量的當(dāng)前值。不過(guò)
,這個(gè)功能并不能完全替代觀察窗口和“Evaluate/Modify”對(duì)話框。觀察窗口的優(yōu)勢(shì)在于能同時(shí)顯示多個(gè)表達(dá)式的值,而“Evaluate/
Modify”對(duì)話框的優(yōu)勢(shì)在于能夠修改表達(dá)式的值。另外,對(duì)于在開(kāi)域語(yǔ)句中簡(jiǎn)寫(xiě)了的變量,或在退出當(dāng)前例程前不再引用的變量,小窗口也不
會(huì)彈出。
3.7.4 Inspector窗口
Inspector窗口非常適合于查看復(fù)合的數(shù)據(jù),諸如對(duì)象、結(jié)構(gòu)等。
要打開(kāi)Inspector窗口,可以使用“Run”菜單上的“Inspect”命令,Delphi將打開(kāi)“Inspect”對(duì)話框。鍵入一個(gè)要查看的表達(dá)式,然后單擊
[OK]按鈕,Delphi將打開(kāi)“Debug Inspector”窗口,顯示該表達(dá)式當(dāng)前的值,如圖3-16所示。
圖3-16 Inspector窗口 圖3-17 顯示局部變量的值
Inspector窗口的最大特點(diǎn)是,它能夠根據(jù)所要查看的表達(dá)式的數(shù)據(jù)類(lèi)型自動(dòng)按最適合的格式顯示。例如,在圖3-16中,要查看的表達(dá)式是
Forml.Button1。由于Button1是TButton類(lèi)型的對(duì)象,所以Inspector窗口就把該對(duì)象的私有數(shù)據(jù)、方法和公開(kāi)的屬性分別列出來(lái)。如果要查看
的是一個(gè)數(shù)組,Inspector窗口就會(huì)列出該數(shù)組的每一個(gè)元素的值。
你可能已經(jīng)發(fā)現(xiàn),Inspector窗口非常類(lèi)似于IDE中的Object Inspector,事實(shí)上的確如此。不同的是,Inspector窗口不能列出對(duì)象所能響應(yīng)的
事件。
使用Inspector窗口要特別注意表達(dá)式的作用域問(wèn)題。如果在代碼編輯器中單擊鼠標(biāo)右鍵,在彈出的菜單中選擇“Inspect”命令,而當(dāng)前執(zhí)行
點(diǎn)并不在要查看的表達(dá)式的作用域范圍內(nèi)。這時(shí)候,表達(dá)式無(wú)定義,Inspector窗口就是空白的。
在Inspector窗口上可以修改元素的值。首先,單擊該元素,如果出現(xiàn)一個(gè)省略號(hào)按鈕,表示該元素的值是可以修改的。單擊省略號(hào)按鈕,鍵入
一個(gè)新的值即可。
在Inspector窗口上單擊鼠標(biāo)右鍵,將彈出一個(gè)快捷菜單,菜單上的命令有:
“Range”命令用于在查看指針或數(shù)組的時(shí)候指定要顯示的范圍。其中,“Start”用于指定起始序號(hào),“Count”用于指定要顯示的元素個(gè)數(shù)。
“Change”命令用于修改元素的值,相當(dāng)于單擊省略號(hào)按鈕。
“Show Inherited”命令如果被選中的話,Inspector窗口將顯示基類(lèi)的成員。
“Inspect”命令將再打開(kāi)一個(gè)Inspector窗口,顯示所選元素的值。
“Descend”命令類(lèi)似于“Inspect”命令,但不打開(kāi)一個(gè)新的Inspector窗口。
“New Expression”命令用于查看一個(gè)新的表達(dá)式。
“Type Cast”命令用于強(qiáng)制轉(zhuǎn)換表達(dá)式的數(shù)據(jù)類(lèi)型。
3.7.5 查看局部變量的值
如果要查看過(guò)程或函數(shù)的局部變量,可以使用“View”菜單上的“Debug Windows”命令,再選擇“Local Variables”。你不必指定要查看哪
個(gè)局部變量,因?yàn)镈elphi會(huì)自動(dòng)把當(dāng)前函數(shù)的所有局部變量全部列出來(lái)。圖3-17顯示了一個(gè)事件中局部變量的值。
如果有必要的話,建議使這個(gè)窗口保持在打開(kāi)狀態(tài)。這樣,調(diào)試程序時(shí),就能看到局部變量是怎樣變化的。
3.8 調(diào)試的有關(guān)窗口
這一節(jié)介紹幾個(gè)與調(diào)試有關(guān)的窗口,這些窗口可以幫助你了解當(dāng)前CPU內(nèi)部的狀態(tài)、線程的狀態(tài)、映射到應(yīng)用程序地址空間的模塊、例程的調(diào)用
順序以及調(diào)試期間的事件。
3.8.1 CPU窗口
要打開(kāi)CPU窗口,可以使用“View”菜單上的“Debug Windows”命令,再選擇“CPU”,也可以直接按Alt+F2鍵。要說(shuō)明的是,只有在調(diào)試暫停
狀態(tài)才能打開(kāi)CPU窗口。
CPU窗口能夠顯示CPU內(nèi)部當(dāng)前的狀態(tài),包括寄存器、標(biāo)志、棧和全局?jǐn)?shù)據(jù)段等,如圖3-18所示。
圖3-18 CPU窗口
要完全看懂CPU窗口,需要對(duì)CPU內(nèi)部的結(jié)構(gòu)有一定的了解,最好還要有80x86匯編語(yǔ)言的編程經(jīng)驗(yàn)。
CPU窗口分成5個(gè)窗格,每個(gè)窗格都可以獨(dú)立滾動(dòng),這5個(gè)窗格的相對(duì)尺寸是可以改變的。在不同的窗格中單擊鼠標(biāo)右鍵,將彈出不同的快捷菜單
。
CPU窗口的左上角是“Disassembly”窗格。“Disassembly”窗格把編譯器生成的機(jī)器代碼反匯編成可讀性較好的匯編指令。為了便于對(duì)照,“
Disassembly”窗格能夠同時(shí)顯示Object Pascal源代碼和匯編指令。一行Object Pascal代碼可能對(duì)應(yīng)著幾條匯編指令。
“Disassembly”窗格分為兩欄,右邊一欄顯示匯編指令及其操作數(shù),左邊一欄顯示匯編指令的地址。如果單擊某個(gè)地址,“Disassembly”窗
格的左上角將顯示有效地址和該地址存儲(chǔ)的值。“Disassembly”窗格的右上角將顯示當(dāng)前線程的ID。
在“Disassembly”窗格中也可以單步執(zhí)行程序。“Disassembly”窗格用一個(gè)綠色小箭頭指向當(dāng)前執(zhí)行點(diǎn)的位置。隨著當(dāng)前執(zhí)行點(diǎn)的移動(dòng),綠
色小箭頭也相應(yīng)地移動(dòng)。
CPU窗口的左下角是“Memory Dump”窗格,用于顯示應(yīng)用程序的全局?jǐn)?shù)據(jù)段中的數(shù)據(jù)。“Memo Dump”窗格分成三欄,顯示內(nèi)容很象以前DOS下
的Debug工具的顯示內(nèi)容,左邊的欄顯示內(nèi)存地址,中間的欄顯示該地址當(dāng)前的值,右邊的欄是相應(yīng)的ASCII值,不可打印的字符用小圓點(diǎn)表示
。
要改變中間那一欄的顯示格式,可以在“Memory Dump”窗格上單擊鼠標(biāo)右鍵,在彈出的菜單中選擇“Display As”命令,然后再選擇一種顯示
格式。
“Machine Stack”窗格位于CPU窗口的右下角,用于顯示應(yīng)用程序棧中的數(shù)據(jù)。這個(gè)窗格也分成3欄,左邊的欄顯示內(nèi)存地址,中間的欄顯示該
地址當(dāng)前的值,右邊的欄是相應(yīng)的ASCII值,不可打印的字符用小圓點(diǎn)表示。綠色小箭頭指示棧頂?shù)奈恢谩?br>“Registers”窗格位于“Disassembly”窗格的右邊,用于顯示CPU中寄存器的值,包括8個(gè)32位的通用寄存器、6個(gè)16位的段寄存器、1個(gè)32位
的程序計(jì)數(shù)器和1個(gè)32位的標(biāo)志寄存器。如果最近一次單步或跟蹤執(zhí)行后有寄存器的值被改變,就用紅色突出顯示。
“Registers”窗格的右邊是“Flags”窗格,用于顯示CPU中15個(gè)標(biāo)志位的狀態(tài),值為1表示標(biāo)志被置位,值為0表示標(biāo)志被清除。如果最近一次
單步或跟蹤執(zhí)行后有標(biāo)志位的狀態(tài)被改變,就用紅色突出顯示該標(biāo)志位的狀態(tài)。
3.8.2 FPU窗口
FPU窗口用于顯示CPU中浮點(diǎn)單元的內(nèi)容或MMX信息。要打開(kāi)FPU窗口,可以使用“View”菜單上的“Debug Windows”命令,再選擇“FPU”。FPU
窗口如圖3-19所示。
圖3-19 FPU窗口
FPU窗口分為三個(gè)窗格:最左邊的是寄存器窗格,顯示了浮點(diǎn)寄存器棧的內(nèi)容;中間是控制標(biāo)志窗格,列出了所有的控制字;最右邊是狀態(tài)標(biāo)志
窗格,列出了所有的狀態(tài)字。
寄存器窗格的上方是指令指針(IPTR)的地址、操作碼以及上次執(zhí)行的浮點(diǎn)指令的地址。
3.8.3 線程狀態(tài)窗口
使用“View”菜單上的“Debug Windows”命令,再選擇“Threads”,Delphi將打開(kāi)“Thread Status”窗口,以顯示當(dāng)前進(jìn)程中所有活動(dòng)的線
程,如圖3-20所示。
圖3-20 線程狀態(tài)窗口
除非正在調(diào)試多線程的應(yīng)用程序,否則,線程狀態(tài)窗口一般只顯示一個(gè)線程。線程狀態(tài)窗口分為以下4欄:
(1) ThreadID:顯示線程的識(shí)別號(hào),它是由操作系統(tǒng)分配的。
(2) State:顯示線程的狀態(tài),可能是Running,也可能是Stopped。當(dāng)應(yīng)用程序正在等待用戶(hù)輸入時(shí),線程的狀態(tài)就是Runnable。
(3) Status:顯示線程處于Stopped狀態(tài)的原因,BreakPoint表示遇到斷點(diǎn),Stepped表示單步或跟蹤執(zhí)行,F(xiàn)aulted表示出現(xiàn)異常,Unknown表
示這個(gè)線程不是當(dāng)前線程。
(4) Location:顯示本線程的當(dāng)前執(zhí)行點(diǎn)所在的源代碼文件名和行號(hào)。
程序一般執(zhí)行很快,可能來(lái)不及看到線程的執(zhí)行情況,除非事先在程序中設(shè)置了斷點(diǎn)或者自己中止了程序運(yùn)行或者程序異常中止。
在調(diào)試多線程應(yīng)用程序時(shí),線程狀態(tài)窗口將顯示所有活動(dòng)的線程,但其中只有一個(gè)是當(dāng)前線程,通常它就是應(yīng)用程序的主線程。要把其他線程
變?yōu)楫?dāng)前線程,可以用鼠標(biāo)右鍵單擊這個(gè)線程的“ThreadID”欄,在彈出的菜單中選擇“Make Current”命令,此時(shí),代碼編輯器將用一個(gè)綠
色小箭頭指向當(dāng)前線程的當(dāng)前執(zhí)行點(diǎn),這樣就可以調(diào)試當(dāng)前線程了。
3.8.4 Call Stack窗口
使用“View”菜單上的“Debug Windows”命令,再選擇“Call Stack”,Delphi將打開(kāi)“Call Stack”窗口,如圖3-21所示。
圖3-21 “Call Stack”窗口
“Call Stack”窗口非常有用,它能顯示例程的調(diào)用順序以及傳遞給例程的參數(shù)值。其中,顯示在最上面的是最近調(diào)用的例程,圖3-21中就是
TForm1.Button12Click()。
“Call Stack”窗口主要用于定位例程調(diào)用。例如,如果不小心跟蹤進(jìn)入了一個(gè)本不想跟蹤的例程內(nèi)部,要想返回到調(diào)用這個(gè)例程的地方,只
要在“Call Stack”窗口用鼠標(biāo)右鍵單擊調(diào)用這個(gè)例程的例程(通常是“Call Stack”窗口中第二個(gè)例程),在彈出的菜單中選擇“Edit
Source”命令,代碼編輯器將被推到前端,光標(biāo)定位在調(diào)用該例程的地方(如果有必要的話將首先打開(kāi)包含此例程的源代碼窗口)。然后,移動(dòng)
光標(biāo)跳過(guò)這個(gè)調(diào)用,再使用“Run”菜單上的“Run to Cursor”命令就可以繼續(xù)調(diào)試了。如果在彈出的菜單中選擇“View Source”命令,光標(biāo)
將定位在要找的例程上,但“Call Stack”窗口仍然是當(dāng)前活動(dòng)的窗口,這樣就可以繼續(xù)在“Call Stack”窗口中查找其他例程。
3.8.5 模塊窗口
使用“View”菜單上的“Debug Windows”命令,再選擇“Modules”,Delphi將打開(kāi)模塊窗口。該窗口顯示當(dāng)前所有調(diào)入到內(nèi)存中的模塊,包
括應(yīng)用程序本身、應(yīng)用程序顯式或隱式調(diào)用的DLL以及運(yùn)行期包、操作系統(tǒng)調(diào)用的DLL,如圖3-22所示。
圖3-22 模塊窗口
模塊窗口分為三個(gè)窗格:Module(左上)、Source(左下)、Entry Point(右),其中:
“Module”窗格列出了所有模塊的名稱(chēng)、入口地址以及路徑。
如果在“Module”窗格中所選的模塊包含調(diào)試信息,“Source”窗格將顯示該模塊中調(diào)用了哪些單元。如果所選的模塊不包含調(diào)試信息,
“Source”窗格就是空的。
如果在“Module”窗格中所選的模塊包含調(diào)試信息,“Entry Point”窗格將列出該模塊中所有的全局符號(hào),否則,“Entry Point”窗格只列
出進(jìn)入該模塊的入口。
3.8.6 事件記錄窗口
使用“View”菜單上的“Debug Windows”命令,再選擇“Event Log”,Delphi將打開(kāi)“Event Log”窗口,如圖3-23所示。
圖3-23 事件記錄窗口
事件記錄窗口能夠顯示調(diào)試過(guò)程中遇到的各種事件,其中包括斷點(diǎn)和異常、進(jìn)程裝載或終止、調(diào)用Output Debug String和Windows的消息。
在事件記錄窗口上單擊鼠標(biāo)右鍵,將彈出一個(gè)快捷菜單,其中,“Clear Events”命令將把所有的事件記錄清空,“Save Events to File”命
令能夠把事件記錄保存到一個(gè)文件中,“Add Comments”命令能夠給事件加上注解,“Properties”命令將打開(kāi)“Debugger Event Log
Properties”對(duì)話框,讓你設(shè)置有關(guān)選項(xiàng)。
3.9 特殊程序調(diào)試*
3.9.1 調(diào)試動(dòng)態(tài)鏈接庫(kù)
Delphi可以用內(nèi)部集成的調(diào)試器來(lái)調(diào)試動(dòng)態(tài)鏈接庫(kù)(DLL)。我們將在下一章學(xué)習(xí)DLL,DLL是不能單獨(dú)執(zhí)行的,只能由一個(gè)可執(zhí)行程序調(diào)用它。
因此,要調(diào)試DLL,首先要指定一個(gè)可執(zhí)行程序,方法是使用“Run”菜單上的“Parameters”命令打開(kāi)“Run Parameters”對(duì)話框,如圖3-24
所示。
圖3-24 指定—個(gè)可執(zhí)行程序
在“Host Application”框內(nèi)鍵入一個(gè)可執(zhí)行程序的路徑,也可以單擊“Browse”按鈕定位一個(gè)可執(zhí)行程序,然后單擊“打開(kāi)”按鈕把這個(gè)可
執(zhí)行程序調(diào)入內(nèi)存運(yùn)行。接下來(lái),就可以像調(diào)試一般的應(yīng)用程序那樣調(diào)試DLL,包括單步執(zhí)行、設(shè)斷點(diǎn)、開(kāi)觀察窗口等。
也可以通過(guò)模塊斷點(diǎn)來(lái)調(diào)試DLL。首先,要設(shè)置一個(gè)模塊斷點(diǎn)。為此,可以使用“Run”菜單上的“Add Breakpoint”命令再選擇“Module Load
Breakpoint”;也可以使用“View”菜單上的“Debug Windows”命令再選擇“Modules”打開(kāi)“Modules”窗口,在左上角的窗格中單擊鼠標(biāo)右
鍵,在彈出的菜單中選擇“Add Module”命令。此時(shí),Delphi將打開(kāi)“Add Module”對(duì)話框。輸入要調(diào)試的DLL的路徑,也可以按“Browse”按
鈕瀏覽。
以后,當(dāng)應(yīng)用程序調(diào)用這個(gè)DLL時(shí),程序?qū)和!?br>Delphi能夠調(diào)試其他語(yǔ)言如C/C++、匯編語(yǔ)言編寫(xiě)的DLL,只要DLL中含有Borland符號(hào)調(diào)試信息。如果其他語(yǔ)言寫(xiě)的DLL中不包含Borland符號(hào)調(diào)
試信息,就不能在代碼編輯器中看到DLL的源代碼,而只能用CPU窗口來(lái)調(diào)試。
除了DLL外,Delphi的內(nèi)部集成調(diào)試器還可以調(diào)試Active X控件和OLE自動(dòng)化對(duì)象。與調(diào)試DLL一樣,也要首先指定一個(gè)可執(zhí)行程序。
3.9.2 遠(yuǎn)程調(diào)試
Delphi支持遠(yuǎn)程調(diào)試。所謂遠(yuǎn)程調(diào)試,是指要調(diào)試的EXE、DLL或包在另一臺(tái)機(jī)器上。當(dāng)然,要調(diào)試的EXE、DLL或包必須含有遠(yuǎn)程符號(hào)調(diào)試信息
。
要進(jìn)行遠(yuǎn)程調(diào)試,本地需要有Delphi的IDE(DELPHI32.EXE),當(dāng)然,需要適當(dāng)配置。遠(yuǎn)程機(jī)器上不需要安裝Delphi,但需要安裝遠(yuǎn)程調(diào)試服務(wù)器
來(lái)支持遠(yuǎn)程調(diào)試。
本地和遠(yuǎn)程機(jī)器之間必須通過(guò)TCP/IP協(xié)議連接,端口是8000。
1.本地的配置
本地的IDE需要適當(dāng)配置才能調(diào)試位于遠(yuǎn)程的EXE、DLL或包。
首先,要打開(kāi)“Project Options”對(duì)話框,翻到“Linker”頁(yè),選中“Include remote debug symbols”復(fù)選框。這樣,EXE或DLL將包含遠(yuǎn)
程調(diào)試信息。
接著,使用“Run”菜單上的“Parameters”命令打開(kāi)“Run Parameters”對(duì)話框,翻到“Remote”頁(yè),如圖3-25所示。
圖3-25 設(shè)置遠(yuǎn)程調(diào)試的參數(shù)
在“Remote Path”框內(nèi),輸入一個(gè)要調(diào)試的EXE的名稱(chēng)和路徑。如果要調(diào)試的是DLL或包,在“Remote Path”框內(nèi)輸入一個(gè)調(diào)用該DLL或包的可
執(zhí)行程序的名稱(chēng)和路徑,而不是DLL或包的名稱(chēng)和路徑。
注意:遠(yuǎn)程調(diào)試服務(wù)器應(yīng)當(dāng)能夠找到你指定的EXE及其符號(hào)。如果遠(yuǎn)程調(diào)試服務(wù)器是作為服務(wù)運(yùn)行的(在Windows NT環(huán)境中),則無(wú)法訪問(wèn)網(wǎng)絡(luò)共
享驅(qū)動(dòng)器。
這種情況下,你要么把EXE及其符號(hào)復(fù)制到服務(wù)器的機(jī)器上,要么在編譯時(shí)把輸出路徑設(shè)為服務(wù)器所在機(jī)器的某個(gè)目錄。
在“Remote Host”框內(nèi)輸入遠(yuǎn)程機(jī)器的主機(jī)名或IP地址。如果輸入的是主機(jī)名,本地機(jī)器必須通過(guò)標(biāo)準(zhǔn)的Internet域名解析來(lái)獲得遠(yuǎn)程機(jī)器的
IP地址。
在“Parameters”框輸入要傳遞給“Remote Path”框指定的EXE的參數(shù)。
如果選中“Debug project on remote machine”復(fù)選框,則以后所有的IDE命令將直接針對(duì)遠(yuǎn)程的項(xiàng)目。如果沒(méi)有選中這個(gè)復(fù)選框,則必須單
擊“Load”按鈕啟動(dòng)遠(yuǎn)程的EXE,但不打開(kāi)相關(guān)的項(xiàng)目,這樣,“Project”菜單上的命令就不會(huì)影響到遠(yuǎn)程的項(xiàng)目。
2.遠(yuǎn)程機(jī)器的配置
遠(yuǎn)程機(jī)器上雖然不需要安裝Delphi,但需要安裝一個(gè)特殊的軟件即遠(yuǎn)程調(diào)試服務(wù)器,以支持遠(yuǎn)程調(diào)試。
在Delphi的光盤(pán)中有一個(gè)RDEBUG目錄,運(yùn)行其中的SETUP程序,將把BORDBG60.EXE安裝到C:/Program Files\Common Files/Borland
Shared/Debugger目錄下。這個(gè)程序的作用就是通過(guò)TCP/IP協(xié)議來(lái)連接和解釋IDE發(fā)來(lái)的調(diào)試命令。
如果遠(yuǎn)程機(jī)器的操作系統(tǒng)是Windows 2000 Server,則BORDBG60.EXE既可以作為一個(gè)程序運(yùn)行,也可以作為一個(gè)服務(wù)運(yùn)行。如果遠(yuǎn)程機(jī)器的操作
系統(tǒng)是Windows 95/98,則 BORDBG60.EXE只能作為程序運(yùn)行,并且只能人工啟動(dòng)。
要把BORDBG60.EXE作為程序運(yùn)行并進(jìn)入監(jiān)聽(tīng)狀態(tài)(Windows 95/98),可以單擊 Windows的“開(kāi)始”按鈕,選擇“運(yùn)行”命令,然后鍵入
“BORDBG60.EXE-listen”。
要把BORDBG60.EXE作為一個(gè)服務(wù)安裝(只適用于Windows 2000 Server),可以單擊Windows 2000 Server的“開(kāi)始”按鈕,選擇“運(yùn)行”命令,
然后鍵入“BORDBG60.EXE-install”。
要移走BORDBG60.EXE這個(gè)服務(wù),可以單擊Windows 2000 Server的“開(kāi)始”按鈕,選擇“運(yùn)行”命令,然后鍵入“BORDBG60.EXE-remove”。
在Windows 2000 Server環(huán)境下,如果BORDBG60.EXE是作為服務(wù)(Service)安裝的(這可以從Windows 2000 Server的控制面板中看出來(lái)),
BORDBG60.EXE就能夠自動(dòng)啟動(dòng)。當(dāng)然,正如前面所講的,此時(shí)的BORDBG60.EXE無(wú)法訪問(wèn)網(wǎng)絡(luò)共享驅(qū)動(dòng)器。因此,必須把要調(diào)試的EXE及其符號(hào)信
息(擴(kuò)展名是.RSM)復(fù)制到運(yùn)行BORDBG60.EXE的機(jī)器上。
3.9.3 多進(jìn)程調(diào)試
Delphi支持在Windows 2000下進(jìn)行多進(jìn)程調(diào)試。要調(diào)試的進(jìn)程可以在本地機(jī)器上,也可以在遠(yuǎn)程機(jī)器上。對(duì)于后者,需要按上一節(jié)講的那樣進(jìn)
行配置。
要選擇一個(gè)進(jìn)程并調(diào)試它,有下列幾種方式:
一是借助于項(xiàng)目管理器把要調(diào)試的項(xiàng)目加到一個(gè)項(xiàng)目組中,然后使用“Project”菜單上的“Build All Projects”命令編譯這些項(xiàng)目。這樣,
就可以選擇一個(gè)進(jìn)程并調(diào)試它。
二是使用“Run”菜單上的“Parameters”命令,啟動(dòng)一個(gè)新的進(jìn)程并調(diào)試它。
三是使用“Run”菜單上的“Attach to Process”命令,列出當(dāng)前正在IDE之外運(yùn)行的進(jìn)程。選擇一個(gè)進(jìn)程,調(diào)試器將轉(zhuǎn)而調(diào)試該進(jìn)程。
四是單擊“Debug”工具欄上“Run”按鈕邊上的箭頭,選擇一個(gè)要調(diào)試的進(jìn)程。
使用“View”菜單上的“Debug Windows”命令,再選擇“Threads”,將打開(kāi)線程狀態(tài)窗口,顯示內(nèi)存中的所有進(jìn)程以及每個(gè)進(jìn)程中的所有線
程,如圖3-20所示。
綠色的箭頭指示當(dāng)前的進(jìn)程,淺綠色的箭頭指示非當(dāng)前的進(jìn)程。在同一個(gè)時(shí)刻,調(diào)試器的調(diào)試命令都是針對(duì)當(dāng)前進(jìn)程的。要使一個(gè)非當(dāng)前的進(jìn)
程變成當(dāng)前進(jìn)程,可以在這個(gè)非當(dāng)前的進(jìn)程上單擊鼠標(biāo)右鍵,在彈出的菜單中選擇“Make Current”命令。采用類(lèi)似的辦法可以使一個(gè)非當(dāng)前
的線程變成當(dāng)前線程,同時(shí),這個(gè)線程所屬的進(jìn)程也變成了當(dāng)前進(jìn)程。
模塊窗口能夠顯示內(nèi)存中的所有進(jìn)程以及每個(gè)進(jìn)程所調(diào)用的模塊,如圖3-22所示。
與線程狀態(tài)窗口一樣,模塊窗口也用一個(gè)綠色的小箭頭指示當(dāng)前進(jìn)程,非當(dāng)前的進(jìn)程沒(méi)有任何指示。
3.9.4 分布式調(diào)試
Delphi支持在Windows 2000下進(jìn)行分布式調(diào)試,包括COM跨進(jìn)程調(diào)試和CORBA跨進(jìn)程調(diào)試。Windows 95/98不支持分布式調(diào)試。
1.COM跨進(jìn)程調(diào)試
如果在“Debugger Options”對(duì)話框的“Distributed Debugging”頁(yè)選中“Enable COM cross-process support”復(fù)選框,則下列3個(gè)功能將
被激活:
一是跨進(jìn)程依附。當(dāng)進(jìn)行一個(gè)遠(yuǎn)程調(diào)用時(shí),調(diào)試器將試圖接管對(duì)RPC目標(biāo)的控制。如果目標(biāo)進(jìn)程在另一臺(tái)機(jī)器上,該機(jī)器上必須配置并運(yùn)行了
BORDBG60.EXE服務(wù)。從“Event Log”窗口和“Thread Status”窗口可以看出調(diào)試器是否成功地接管了對(duì)遠(yuǎn)程進(jìn)程的控制。如果該進(jìn)程原先是
作為客戶(hù)被調(diào)試的,則跨進(jìn)程RPC的目標(biāo)就是服務(wù)器。反之,如果該進(jìn)程原先是作為服務(wù)器被調(diào)試的,則跨進(jìn)程RPC的目標(biāo)就是客戶(hù)。
二是調(diào)用跟蹤。“Event Log”窗口將跟蹤所有的跨進(jìn)程RPC調(diào)用。對(duì)于每個(gè)調(diào)用,記載以下事件:客戶(hù)開(kāi)始調(diào)用服務(wù)器、服務(wù)器被調(diào)用、調(diào)用
結(jié)束。每個(gè)事件包含接口標(biāo)識(shí)符(IID)和被調(diào)用的方法的零基準(zhǔn)序號(hào)。
三是跨進(jìn)程單步。單步操作的流程將沿著“分布式”線程而不是實(shí)際的線程。為了避免這種情況,你可以使用“Run”菜單上的“Trace to
Next Source Line”命令(Shift+F7)而不要使用一般的“Trace Into”命令(F7)。當(dāng)然,進(jìn)行跨進(jìn)程跟蹤的前提是調(diào)試器已經(jīng)成功地依附了目
標(biāo)進(jìn)程,調(diào)用的方法必須含有調(diào)試信息。
從“Call Stack”窗口可以看出,源線程被阻塞,直到RPC方法在目標(biāo)線程中執(zhí)行完畢。
2.CORBA跨進(jìn)程調(diào)試
進(jìn)行CORBA跨進(jìn)程調(diào)試必然涉及到VisiBroker ORB。如果在“Debugger Options”對(duì)話框的“Distributed Debugging”頁(yè)選中“Enable CORBA
cross-process support”復(fù)選框,則除了上述三個(gè)功能被激活外,還有兩個(gè)功能被支持。
一是ORB事件斷點(diǎn)。“Event Log”窗口將記載更詳細(xì)的ORB事件,包括相關(guān)的信息,諸如操作名、事務(wù)編號(hào)、接口和主機(jī)名等。這些ORB事件可
以作為斷點(diǎn),使程序暫停。當(dāng)然,可以附加一些條件。例如,當(dāng)操作名是“X”或者主機(jī)名是“Y”時(shí)程序才暫停。
二是只跟蹤的進(jìn)程。有時(shí)候,調(diào)試器無(wú)法接管對(duì)RPC目標(biāo)的控制。例如,RPC目標(biāo)所在的操作系統(tǒng)不是Windows,或者該主機(jī)不支持遠(yuǎn)程調(diào)試,或
者目標(biāo)進(jìn)程有訪問(wèn)限制。對(duì)于COM來(lái)說(shuō),將不會(huì)返回任何關(guān)于RPC目標(biāo)的信息。對(duì)于CORBA來(lái)說(shuō),即使調(diào)試器無(wú)法接管控制,RPC目標(biāo)仍然會(huì)生成
一個(gè)事件流。這種無(wú)法被調(diào)試但能夠生成事件流的目標(biāo)進(jìn)程稱(chēng)為“只跟蹤的進(jìn)程”。對(duì)于只跟蹤的進(jìn)程來(lái)說(shuō),當(dāng)遇到一個(gè)ORB事件斷點(diǎn)導(dǎo)致該進(jìn)
程停止時(shí),它的當(dāng)前線程可以被單步執(zhí)行。不過(guò),“Call Stack”窗口不跟蹤它的棧。
3.9.5 其他調(diào)試手段
在程序開(kāi)發(fā)過(guò)程中,隨時(shí)可能想運(yùn)行一下程序,看看代碼修改后是否能達(dá)到原先設(shè)想的效果,但未必一定要用前面介紹的調(diào)試器來(lái)調(diào)試程序,
因?yàn)檫€有更簡(jiǎn)單的調(diào)試手段。
不妨回憶一下過(guò)去開(kāi)發(fā)DOS程序時(shí)的做法。要知道程序是否能執(zhí)行到某個(gè)地方,可以在這個(gè)地方插入發(fā)聲語(yǔ)句,然后編譯和運(yùn)行程序,如果聽(tīng)到
PC機(jī)的揚(yáng)聲器“嘟”一聲,表示程序能執(zhí)行到這兒。要知道某個(gè)變量的值,可以插入Write語(yǔ)句直接在屏幕上輸出。
在Windows環(huán)境下,上述調(diào)試手段仍然可以采用,只不過(guò)具體實(shí)施辦法有所改變。
下面就介紹幾種調(diào)試手段,要說(shuō)明的是,用這些調(diào)試手段雖然簡(jiǎn)單方便,但也在程序中留下了“垃圾”,應(yīng)當(dāng)及時(shí)清除掉。
調(diào)用Windows的MessageBeep()是—種常用的調(diào)試手段,它的作用就是讓PC機(jī)的揚(yáng)聲器“嘟”—聲。
可以在事件句柄中調(diào)用MessageBeep(),從而可以判斷是否觸發(fā)了事件。
在同一個(gè)時(shí)刻,程序中最好只有一個(gè)地方調(diào)用了MessageBeep(),如果有多個(gè)地方調(diào)用了MessageBeep(),就很難區(qū)分到底是哪個(gè)地方使PC機(jī)的
揚(yáng)聲器“嘟”一聲。
還有一種比較簡(jiǎn)單的調(diào)試手段就是直接在Form上輸出。要在Form上輸出文字,有3種方法:—是在Form的畫(huà)布上輸出,這就要用到TForm的
Canvas屬性。程序示例如下:
Form1.Canvas.Pen.Color:=clRed;
Form1.Canvas.TextOut(100,100,’要輸出的文字’);
這種方法的缺陷在于編程稍嫌復(fù)雜,可能有的讀者對(duì)Canvas屬性不熟悉。
第二種方法是在Form的標(biāo)題欄輸出,程序示例如下:
Form1.Caption:=’要輸出的文字’;
第三種方法是在Form上臨時(shí)放—個(gè)TLabel或TEdit組件,用標(biāo)簽或編輯框輸出文字。
不管采用上述哪種方法,F(xiàn)orm上只能輸出字符串,如果要輸出其他類(lèi)型的數(shù)值,首先要調(diào)用轉(zhuǎn)換例程把數(shù)值轉(zhuǎn)換為字符串。程序示例如下:
Label1.Caption:=IntToStr(Form1.Height);
如果要輸出一個(gè)數(shù)組,要把一個(gè)TLis
聯(lián)系客服