Delphi異常處理總結
以前寫delphi程序一直不注意異常處理,對其異常處理機制總是一知半解,昨天程 序中的一個bug,讓我對異常有了更深入的認識,必須要對可能產生異常的地方進行異常處理,否則可能給程序造成災難,就像昨天,因為寫的filecopy 函數沒有做異常捕獲處理,導致復制文件出錯時整個程序崩潰,用戶只能通過殺進程的方式重啟程序再進行其它操作(汗~)。後來對程序進行異常處理,遇到意外 只是提示下用戶,然後可以繼續運行下去,表現很完美,才意識到異常處理的重要性,故要總結下Delphi異常處理相關的知識。
Delphi異常處理機制建立在保護塊(ProtectedBlocks)的概念上。所謂保護塊是用保留字try和end封裝的一段代碼。保護塊的作用是當應用程序發生錯誤時自動創建一個相應的異常類 (Exception)。程序可以捕獲並處理這個異常類,以確保程序的正常結束以及資源的釋放和數據不受破壞。如果程序不進行處理,則系統會自動提供一個消息框。每一段程序都有可能產生錯誤!這是軟件業的一個不容置疑的現象和規律。事實上,傳統的if…else…結構完全可以解決所有的錯誤,使用 Exception機制也沒能夠回避在最原始的層次,通過遍歷可能的情況來產生異常的做法,但異常提供了一種更加靈活和開放的方式,使得後來的編程者可以來根據實際的情況處理這種錯誤,而不是使用預先設定好的處理結果。
常的來源
在Delphi的應用程序中,下列的情況都比較有可能產生異常。
(1)文件處理
(2)內存分配
(3)Windows資源
(4)運行時創建對象和窗體
(5)硬件和操作系統沖突
一、異常的來源
在Delphi的應用程序中,下列的情況都比較有可能產生異常。
(1)文件處理
(2)內存分配
(3)Windows資源
(4)運行時創建對象和窗體
(5)硬件和操作系統沖突
二、異常處理
(1)try…except…end;
在try體內的代碼發生異常時,系統將轉向except部分進行異常的處理。這是 Delphi處理異常的最基本的方式之一。try語句塊指出了需要進行異常保護的代碼。如果在這部分有不正常的事件發生,則引發一個異常對象。 except是異常處理部分,被保護部分引發的異常對象將執行<異常處理語句>或由這部分代碼捕獲並進行處理。
try except語句的一般格式如下:
try //try保護代碼塊
被保護語句
except //異常處理塊
異常處理語句 //異常不發生,不處理
end;
或
try //try保護代碼塊
被保護語句
except //異常處理塊
on <異常對象類型1> do <語句1> //捕獲指定類型的異常對象,進行處理
on <異常對象類型n> do <語句n> //捕獲指定類型的異常對象,進行處理
else
<語句n+1> //缺省的異常處理代碼
end;
(2)try…finally…end;
這種異常處理結構一般用於保護Windows的資源分配等方面,它確保了無論try體內的代碼是否發生異常,都需要由系統進行最後的統一處理的一些Windows對象的正確處理。
和try…except…end不同,該結構的finally部分總被執行。在try-finally語句中,當try部分產生異常後,應用程序直接執行Finally部分的資源釋放語句。
try finally語句的一般格式如下:
try //try保護代碼塊
被保護語句
finally //異常處理塊
異常處理語句 //無論異常發生否,都必須處理
end;
若用作創建一個資源保護塊時,它的格式可寫成:
(分配系統資源)
try
(使用系統資源的語句)
finanlly
(釋放系統資源)
end;
(3)不存在try…except…finally…end結構來既處理異常,又保護資源分配的結構,但是,try…except…end結構允許嵌套到try…finally…end結構中,從而實現既處理異常,又保護資源的分配。
(4) raise:知道一些情況不合理,直接手工彈異常對話框。如:raise 異常類.Create('異常的缺省說明');
try...finally結構與try...except結構在用法上主要有以下區別:
(1) 對於try...finally結構來說,不管try部分的代碼是否觸發異常,finally部分總是執行的。如果發生異常,就提前跳到finally部分。而對於try...except結構來說,只有當觸發了異常後,才會執行except部分的代碼。
(2) 在try...except結構中,當異常被處理後異常對象就被釋放,除非重新觸發異常。而在try...finally結構中,即使finally部分對異常作了處理,異常對象仍然存在。
(3) finally部分不能處理特定的異常,因為它沒有try...except結構中的異常句柄,無法知道確切的異常類型。因此,在finally部分只能對異常作籠統的處理。
(4) 在try…finally結構中,如果在try部分調用標准命令Exit、Break或Continue,將導致程序的執行流程提前跳到finally部分。finally部分不允許調用上述三個命令。
三、Delphi中的異常類結構
Delphi 提供的所有異常類都是類Exception的子類。用戶也可以從Exception派生一個自定義的異常類。即Exception是所有異常類的基類,它 並不是以'T'開頭,而是以'E'開頭,它的派生類也是以'E'開頭的.Delphi提供了一個很龐大的異常類體系,從大的方面可以把異常類分為運行庫異 常、對象異常、組件異常三類。
3.2.1 運行庫異常類(RTL Exception)
運行庫異常可以分為七類,它們都定義在SysUtils庫單元中。
1.I/O異常
I/O異常類EInOutError是在程序運行中試圖對文件或外設進行操作失敗後產生的,它從Exception派生後增加了一個公有數據成員ErrorCode,用於保存所發生錯誤的代碼。這一成員可用於在發生I/0異常後針對不同情況采取不同的對策。
當設置編譯指示{$I-}時,不產生I/0異常類而是把錯誤代碼返回到預定義變量IOResult中。
2.堆異常
堆異常是在動態內存分配中產生的,包括兩個類EOutOfMemory和EInvalidPointer,如表3-1所示。
表3-1 堆異常類及其產生原因
異常類 引發條件
EOutOfMemory 沒有足夠的空間用於滿足所要求的內存分配
EInvalidPointer 非法指針。一般是由於程序試圖去釋放一個已釋放的指針而引起的
3.整數異常
整數異常都是從一個EIntError類派生的,但程序運行中引發的總是它的子類:EDivByZero,ERangeError,EIntOverFlow,如表3-2所示。
表3-2 整數異常及其產生原因
異常類 引發條件
EDivByZero 試圖被零除
ERangeError 整數表達式越界
EIntOverFlow 整數操作溢出
ERangeError當一個整數表達式的值超過為一個特定整數類型分配的范圍時引發。比如下面一段代碼將引發一個ERangeError異常。
var
SmallNumber:ShortInt;
X,Y:Integer;
begin
X:=100;
Y:=75;
SmallNumber:=X*Y;
end;
特定整數類型包括ShortInt、Byte以及與整數兼容的枚舉類型、布爾類型等。例如:
type
THazard=(Safety,Marginal,Critical,Catastrophic);
var
Haz:THazard;
Item:Integer;
begin
Item:=5;
Haz:=THazard(Item);
end;
由於枚舉類型越界而引發一個ERangeError異常。數組下標越界也會引發一個ERangeError異常,如:
var
Values:array[1..10]of Integer;
I:Integer;
begin
for I:=1 to 11 do
Values[I]:=I;
end;
ERangeError異常只有當范圍檢查打開時才會引發。這可以在代碼中包含{$R+}編譯指示或設置IDE Option|Project的Range_Checking Option選擇框。注意,Delphi不對長字符串做范圍檢查。
EIntOverFlow異常類在Integer、Word、Longint三種整數類型越界時引發。如下面的代碼將引發一個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異常類只有在編譯選擇框Option|Project|Over_Flow_Check Option選中時才產生。當關閉溢出檢查,則溢出後變量的值是丟棄溢出部分後的剩余值。
4.浮點異常
浮點異常是在進行實數操作時產生的,它們都從一個EMathError類派生,但與整數異常相同,程序運行中引發的總是它的子類EInvalidOp、EZeroDivide、EOverFlow、EunderFlow (表3-3)。
表3-3 浮點異常類及其引發條件
異常類 引發條件
EInvalidOp 處理器碰到一個未定義的指令
EZeroDivide 試圖被零除
EOverFlow 浮點上溢
EUnderFlow 浮點下溢
EInvalidOp最常見的引發條件是沒有協處理器的機器遇到一個協處理器指令。由於在缺省情況下Delphi總是把浮點運算編譯為協處理器指令,因而在386以下微機上常常會碰到這個錯誤。此時只需要在單元的接口部分設置全局編譯指示 {$N-},選擇利用運行庫進行浮點運算,問題就可以解決了。
各種類型的浮點數(Real、Single、Double、Extended)越界引起同樣的溢出異常。
5.類型匹配異常
類型匹配異常EInvalidCast當試圖用As操作符把一個對象與另一類對象匹配失敗後引發。
6.類型轉換異常
類型轉換異常EConvertError當試圖用轉換函數把數據從一種形式轉換為另一種形式時引發,特別是當把一個字符串轉換為數值時引發。下面程序中的兩條執行語句都將引發一個EConvertError異常。
var
r1:Real;
int:Integer;
begin
r1:=StrToFloat('$140.48');
int:=StrToInt('1,402');
end;
要注意,並不是所有的類型轉換函數都會引發EConvertError異常。比如函數Val當它無法完成字符串到數值的轉換時只把錯誤代碼返回。利用這一點我們實現了輸入的類型和范圍檢查。
7.硬件異常
硬件異常發生的情況有兩種:或者是處理器檢測到一個它不能處理的錯誤,或者是程序產生一個中斷試圖中止程序的執行。硬件異常不能編譯進動態鏈接庫(DLLs)中,而只能在標准的應用中使用(表3-4)。
硬件異常都是EProcessor異常類的子類。但運行時並不會引發一個EProcessor異常。
表3-4 硬件異常類及其產生原因
異常類 引發條件
EFault 基本異常類,是其它異常類的父類
EGPFault 一般保護錯,通常由一個未初始化的指針或對象引起
EStackFault 非法訪問處理器的棧段
EPageFault Windows內存管理器不能正確使用交換文件
EInvalidOpCode 處理器碰到一個未定義的指令,這通常意味著處理器試圖去操作非法數據或未初始化的內存
EBreakPoint 應用程序產生一個斷點中斷
ESingleStep 應用程序產生一個單步中斷
EFault、EGPFault往往意味著致命的錯誤。而EBreakPoint、ESingleStep被Delphi IDE的內置調試器處理。事實上前邊的五種硬件異常的響應和處理對開發者來說都是十分棘手的問題。
3.2.2 對象異常類
所謂對象異常是指非組件的對象引發的異常。Delphi定義的對象異常包括流異常、打印異常、圖形異常、字符串鏈表異常等。
1.流異常類
流異常類包括EStreamError、EFCreateError、EFOpenError、EFilerError、EReadError、EWriteError、EClassNotFound。
流異常在Classes庫單元中定義。
流異常引發的原因如表3-5所示。
表3-5 流異常類及其產生原因
異常類 引發條件
EStreamError 利用LoadFromStream方法讀一個流發生錯誤
EFCreateError 創建文件時發生錯誤
EFOpenError 打開文件時發生錯誤
EFilerError 試圖再次登錄一個存在的對象
EReadError ReadBuffer方法不能讀取特定數目的字節
EWriteErrorWriteBuffer方法不能寫特定數目的字節
EClassNotFound 窗口上的組件被從窗口的類型定義中刪除
2.打印異常類
打印異常類EPrinter當打印發生錯誤時引發。它在printers庫單元中定義。例如應用程序試圖向一個不存在的打印機打印或由於某種原因打印工作無法送到打印機時,就會產生一個打印異常。
3.圖形異常類
圖形異常類定義在Graphic庫單元中,包括EInvalidGraphic和EInvalidGraphicOperation兩類。
EInvalidGraphic當應用程序試圖從一個並不包含合法的位圖、圖標、元文件或用戶自定義圖形類型的文件中裝入圖形時引發。例如下面的代碼:
Image1.Picture.LoadFromFile('Readme.txt');
由於Readme.txt並不包含一個合法的圖形,因而將引發一個EInvalidGraphic異常。
EInvalidGraphicOperation當試圖對一個圖形進行非法操作時引發。例如試圖改變一個圖標的大小。
var
AnIcon:TIcon;
begin
AnIcon:=TIcon.Create;
AnIcon.LoadFromFile('C:/WINDOWS/DIRECTRY.ICO');
AnIcon.Width:=100; {引發一個圖形異常}
end;
4.字符串鏈表異常
字符串鏈表異常EStringListError、EListError在用戶對字符串鏈表進行非法操作時引發。
由於許多組件(如TListBox,TMemo,TTabSet,…)都有一個TStrings類的重要屬性,因而字符串鏈表異常在組件操作編程中非常有用。
EStringListError異常一般在字符串鏈表越界時產生。例如對如下初始化的列表框:
ListBox1.Items.Add('Firstitem');
ListBox1.Items.Add('Seconditem');
ListBox1.Items.Add('Thirditem');
則以下操作都會引起EStringListError異常:
ListBox1.Item[3]:='NotExist';
Str:=ListBox1.Item[3];
EListError異常一般在如下兩種情況下引發:
(1)當字符串鏈表的Duplicates屬性設置為dupError時,應用程序試圖加入一個重復的字符串。
(2)試圖往一個排序的字符串鏈表中插入一個字符串。
3.2.3 組件異常類
1.通用組件異常類
通用組件異常類常用的有三個:EInvalidOperation、 EComponentError、EOutOfResource。其中EInvalidOperation、EOutOfResource在 Controls單元中定義;EComponentError在Classes單元中定義。
(1)非法操作異常EInvalidOperation
EInvalidOperation引發的原因可能有:
a. 應用程序試圖對一個Parent屬性為nil的組件進行一些需要Windows句柄的操作
b. 試圖對一個窗口進行拖放操作
c. 操作違反了組件屬性間內置的相互關系等
例如,ScrollBar、Gauge等組件要求Max屬性大於等於Min屬性,因而下面的語句:
ScrollBar1.Max:=ScrollBar1.Min-1;
將引發一個EInvalidOperation異常。
(2)組件異常EComponentError
引發該異常的原因可能有:
a. 在Register過程之外試圖登錄一個組件(常用於自定義組件開發中)
b. 應用程序在運行中改變了一個組件的名稱並使該組件與另一個組件重名
c. 一個組件的名稱改變為一個Object Pascal非法的標識符
d. 動態生成一個組件與已存在的另一組件重名
(3)資源耗盡異常EOutOfResource
當應用程序試圖創建一個Windows句柄而Windows卻沒有多余的句柄分配時引發該異常。
2.專用組件異常類
許多組件都定義了相應的組件異常類。但並不是有關組件的任何錯誤都會引發相應的異常類。許多情況下它們將引發一個運行時異常或對象異常。
下面列出幾個典型的組件異常類。
(1)EMenuError
非法的菜單操作,例如試圖刪除一個不存在的菜單項。這一異常類在Menus庫單元中定義。
(2)EInvalidGridOpertion
非法的網格操作,比如試圖引用一個不存在的網格單元。這一異常類在Grids庫單元中定義。
(3)EDDEError
DDE異常。比如應用程序找不到特定的服務器或會話,或者一個連接意外中止。這一異常類在DDEMan庫單元中定義。
(4)EDatabaseError,EReportError
數據庫異常(EDatabaseError)和報表異常(EReportError)在進行數據庫和報表操作出現錯誤時引發。