後一頁
前一頁
回目錄
回首頁
第十二章 異常處理與程式偵錯(一)

  在應用程式開發中如何偵測、處理程式的執行錯誤是一個很重要的問題。在 Delphi 的整合開發環境( IDE )中提供了一個完善的內置偵錯器,可以輔助敘述你發現大部分程式錯誤。但並不是所有的錯誤都可以被發現,而且當程式涉及到與外設的數據交換或操作外設,如要求用戶輸入、讀寫磁碟等時,錯誤的發生是程式無法控制的,如輸入非法字元、磁碟不能讀寫等。這些情況不僅會導致應用程式異常中止而且可能引起系統的崩潰。針對這些問題,Delphi同時提供了一套強大的異常處理機制。巧妙地利用它,可以使你的程式更為強健,使用更為友好。

  雖然Delphi為應用程式提供了一套缺省的自動異常處理機制,即目前模群組發生錯誤後結束目前模群組並給出錯誤資訊,而並不立即引起應用程式的中止。但當應用程式執行的過程性很強時,僅僅利用這種方法是不夠的,而且很容易導致程式執行的不可預測性。 

12.1 Delphi異常處理機制與異常類 

  Delphi異常處理機制建立在保護塊(Protected Blocks)的概念上。所謂保護塊是用保留字tryend壓縮的一段代碼。保護塊的作用是當應用程式發生錯誤時自動建立一個相應的異常類(Exception)。程式可以捕獲並處理這個異常類,以確保程式的正常結束以及資源的釋放和數據不受破壞。如果程式不進行處理,則系統會自動提供一個訊息框。

  異常類是Delphi異常處理機制的核心,也是Delphi異常處理的主要特色。下面我們對異常類的概念和體系進行詳細的介紹。

  Delphi提供的所有異常類都是類Exception的子類。用戶也可以從Exception派生一個自定義的異常類。

  Exception類的定義如下,對於不常用的成員沒有列出。  

{SysUtils 單元中}

Exception = class(TObject)

private

FMessage: PString;

FHelpContext: Longint;

function GetMessage: String;

procedure SetMessage(const Value: String);

public

constructor Create(const Msg: String);

constructor CreateFmt(const Msg: String; const Args: array of const);. . .

destructor Destroy; override;

property HelpContext: Longint

property Message: String;

property MessagePtr: PString;

end; 

Exception的一系列構造函數中最重要的參數是顯示的錯誤資訊。而數據成員中最重要的也是可被引用的訊息字元串(messagemessagePtr) 這些資訊分別對自定義一個異常類和處理一個異常類有重要作用。

  Delphi提供了一個很龐大的異常類體系,這些異常類幾乎涉及到程式設計的各個方面。從大的方面我們可以把異常類分為執行時間庫異常、物件異常、部件異常三類。下面我們分別進行介紹。 

12.1.1 執行時間庫異常類(RTL Exception) 

  執行時間庫異常可以分為七類,它們都定義在SysUtils庫單元中。 

12.1.1.1 I/O異常 

  I/O異常類EInOutError是在程式執行中試圖對文件或外設進行操作失敗後產生的,它從Exception派生後增加了一個公有數據成員ErrorCode,用於存檔所發生錯誤的代碼。這一成員可用於在發生I/O異常後針對不同情況採取不同的對策。

  當設定編譯指示{$I- } 時,不產生I/O異常類而是把錯誤代碼返回到預定義變數IOResult中。 

12.1.1.2 堆異常 

  堆異常是在動態記憶體分配中產生的,包括兩個類EOutOfMemoryEInvalidPointer

12.1  堆異常類及其產生原因

─────────────────────────────────

異常類 引發原因

—————————————————————————————————

EOutOfMemory 沒有足夠的空間用於滿足所要求的記憶體分配

EInvalidPointer 非法指標。一般是由於程式試圖去釋放一個業已釋 放的指標而引起的

───────────────────────────────── 

12.1.1.3  整數異常 

  整數異常都是從一個EIntError類派生的,但程式執行中引發的總是它的子類:EDivByZeroERangeErrorEIntOverFlow。 

   表12.2  整數異常及其產生原因

─────────────────────

異常類 引發原因

—————————————————————

EDivByZero 試圖被零除

ERangeError 整數表達式越界

EIntOverFlow 整數操作溢出

────────────────────── 

  ERangeError當一個整數表達式的值超過為一個特定整數型式分配的範圍時引發。比如下面一段代碼將引發一個ERangeError異常。 

var

SmallNumber: ShortInt;

X , Y: Integer;

begin

X := 100;

Y := 75;

SmallNumber := X * Y;

end;

  特定整數型式包括ShortIntByte以及與整數相容的枚舉型式、布爾型式等。例如:  

type

THazard = ( Safety , Marginal , Critical , Catastrophic );

var

Haz: THazard;

Item: Integer;

begin

Item:= 4;

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|ProjectRange_Checking Option選擇框。

  EIntOverFlow異常類在IntegerWordLongint三種整數型式越界時引發。如:

var

I : Integer;

a,b,c : Word;

begin

a := 10;

b := 20;

c := 1;

for I := 0 to 100 do

begin

c := a*b*c;

end;

end;

引發一個EIntOverFlow異常。

EIntOverFlow異常類只有在編譯選擇框Option|Project|Over_Flow_Check Option選中時才產生。當關閉溢出檢查,則溢出後變數保留該類整數的最大範圍值。

整數型式的範圍如下表。 

   表12.3 整數型式的範圍

───────────────────────────

型式 範圍 格式

  ———————————————————————————

Shortint -128 .. 127 有符號8

Integer -32768 .. 32767 有符號16

Longint -2147483648 .. 2147483647 有符號32

Byte 0 .. 255 無符號8

Word 0 .. 65535 無符號16

───────────────────────────  

12.1.1.4 浮點異常 

  浮點異常是在進行實數操作時產生的,它們都從一個EMathError類派生,但與整數異常相同,程式執行中引發的總是它的子類EInvalidOpEZeroDivideEOverFlowEUnderFlow。 

   表12.4 浮點異常類及其引發原因

────────────────────────

異常類 引發原因

————————————————————————

EInvalidOp 處理器碰到一個未定義的指令

EZeroDivide 試圖被零除

EOverFlow 浮點上溢

EUnderFlow 浮點下溢

──────────────────────── 

  EInvalidOp最常見的引發原因是沒有協處理器的機器遇到一個協處理器指令。由於在缺省情況下Delphi總是把浮點運算編譯為協處理器指令,因而在386以下微電腦上常常會碰到這個錯誤。此時只需要在單元的接口部分設定全局編譯指示{$N-},選擇利用執行時間庫進行浮點運算,問題就可以解決了。  

  各種型式的浮點數(RealSingleDoubleExtended)越界引起同樣的溢出異常。這同整數異常類是不同的。 

12.1.1.5 型式匹配異常

  型式匹配異常EInvalidCast當試圖用As 操作符把一個物件與另一類物件匹配失敗後引發。 

12.1.1.6 型式轉換異常

  型式轉換異常EConvertError當試圖用轉換函數把數據從一種形式轉換為另一種形式時引發,特別是當把一個字元串轉換為數值時引發。下面程式中的兩條執行語句都將引發一個EConvertError異常。

var

rl : Real;

int: Integer;

begin

rl := StrToFloat(' $140.48');

int := StrToInt(' 1,402 ');

end; 

要注意並不是所有的型式轉換函數都會引發EConvertError異常。比如函數Val當它無法完成字元串到數值的轉換時只把錯誤代碼返回。利用這一點我們在(6.2)節中實現了輸入的型式和範圍檢查。 

12.1.1.7 硬體異常

  硬體異常發生的情況有兩種:或者是處理器偵測到一個它不能處理的錯誤,或者是程式產生一個中斷試圖中止程式的執行。硬體異常不能編譯進動態鍊結庫(DLLs)中,而只能在標準的應用中使用。

  硬體異常都是EProcessor異常類的子類。但執行時間並不會引發一個EProcessor 異常。 

   表12.5  硬體異常類及其產生原因

─────────────────────────────────

異常類 引發原因

—————————————————————————————————

Efault 基本異常類。是其它異常類的父類

EGPFault 一般保護錯。通常由一個未 初始化的指標或物件引起

EStackFault 非法存取處理器的棧段

EPageFault Windows記憶體管理器不能正確使用交換文件

EInvalidOpCode 處理器碰到一個未定義的指令。這通常意味著處理器

試圖去操作非法數據或未初始化的記憶體

EBreakPoint 應用程式產生一個斷點中斷

ESingleStep 應用程式產生一個單步中斷

───────────────────────────────── 

  EFaultEGPFault 往往意味著致命的錯誤。而EBreakPointESingleStepDelphi IDE的內置偵錯器處理。事實上前邊的五種硬體異常的響應和處理對開發者來說都是十分棘手的問題。 

12.1.2 物件異常類 

  所謂物件異常是指非部件的物件引發的異常。Delphi定義的物件異常包括流異常、列印異常、圖形異常、字元串鍊表異常等。 

12.1.2.1 流異常類 

  流異常類包括EStreamErrorEFCreateError EFOpenErrorEFilerErrorEReadErrorEWriteErrorEClassNotFound。它們的結構關係如下: 

EStreamError

|---------- EFCreateError

|---------- EFOpenError

|---------- EFilerError

|--------- EReadError

|--------- EWriteError

|--------- EClassNotFound

    圖12.1 流異常結構圖 

流異常在Classes庫單元中定義。

  流異常引發的原因如表12.6

12.6  流異常類及其產生原因

─────────────────────────────────

異常類 引發原因

—————————————————————————————————

EStreamError 利用LoadFromStream方法讀一個流發生錯誤

EFCreateError 建立文件時發生錯誤

EFOpenError 打開文件時發生錯誤

EFilerError 試圖再次登入一個存在的物件

EReadError ReadBuffer方法不能讀取特定數目的位元群組

EWriteError WriteBuffer方法不能寫特定數目的位元群組

EClassNotFound 視窗上的部件被從視窗的型式定義中解除

───────────────────────────────── 

12.1.2.2 列印異常類 

  列印異常類EPrinter當列印發生錯誤時引發。它在printers庫單元中定義。例如你的應用程式試圖向一個不存在的列印機列印或由於某種原因列印工作無法送到列印機時,就會產生一個列印異常。 

12.1.2.3 圖形異常類 

  圖形異常類定義在Graphic 庫單元中,包括EInvalidGraphicEInvalidGraphicOperation兩類。

  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; { 引發一個圖形異常 }

...

12.1.2.4 字元串鍊表異常 

  字元串鍊表異常EStringListErrorEListError在用戶對字元串鍊表進行非法操作時引發。由於許多部件(TListBox,TMemo,TTabSet,)都有一個TStrings類的重要屬性,因而字元串鍊表異常在部件操作程式設計中非常有用。

  EStringListError異常一般在字元串鍊表越界時產生。例如對如下初始化的列示方塊:  

ListBox1.Items.Add('First item');

ListBox1.Items.Add('Second item');

ListBox1.Items.Add('Third item');

  則以下操作都會引起EStringListError異常: 

ListBox1.Item[3] := ' Not Exist';

str := ListBox1.Item [3];

  EListError異常一般在如下兩種情況下引發:

  1.當字元串鍊表的Duplicates屬性設定為dupError時,應用程式試圖加入一個重複的字元串;

  2.試圖往一個排序的字元串鍊表中插入一個字元串。 

12.1.3 部件異常類 

12.1.3.1 一般的部件異常類 

  一般的部件異常類常用的有三個:EInvalidOperationEComponentErrorEOutOfResource。其中EInvalidOperationEOutOfResourceControls單元中定義;EComponentErrorClasses單元中定義。

  1.非法操作異常 EInvalidOperation

  EInvalidOperation 引發的原因可能有:

  ● 應用程式試圖對一個Parent屬性為nil的部件進行一些需要Windows句柄的操作

  ● 試圖對一個視窗進行拖放操作

  ● 操作違反了部件屬性間內置的相互關係等 

  例如,ScrollBarGauge等部件要求Max屬性大於等於Min屬性,因而下面的語句: 

  ScrollBar1.Max := ScrollBar1.Min-1;

  將引發一個EInvalidOperation異常。 

  2.部件異常EComponentError

引發該異常的原因可能有:

  ● 在Register過程之外試圖登入一個部件(常用於自定義部件開發中)

  ● 應用程式在執行中改變了一個部件的標簽並使該部件與另一個部件重名

  ● 一個部件的標簽改變為一個Object Pascal非法的標識符

  ● 動態產生一個部件與已存在的另一部件重名 

3.資源耗盡異常EOutOfResource

當應用程式試圖建立一個Windows句柄而Windows 卻沒有多餘的句柄分配時引發該異常。 

12.1.3.2 專用部件異常類 

  許多部件都定義了相應的部件異常類。但並不是有關部件的任何錯誤都會引發相應的異常類。許多情況下它們將引發一個執行時間異常或物件異常。

  下面列出幾個典型的部件異常類。

  1.EMenuError

非法的選擇表操作,例如試圖解除一個不存在的選擇表項。這一異常類在Menus庫單元中定義。

  2.EInvalidGridOpertion

  非法的網格操作,比如試圖引用一個不存在的網格單元。這一異常類在Grids庫單元中定義。

  3.EDDEError

  DDE異常。比如應用程式找不到特定的伺服器或交談,或者一個聯接意外中止。這一異常類在DDEMan庫單元中定義。

  4.EDatabaseErrorEReportError

  資料庫異常(EDatabaseError)和報表異常(EReportError) 在進行資料庫和報表操作出現錯誤時引發。有關資料庫的問題請讀者參閱本書第二編。 

12.1.4 小結 

  在這一節中重點介紹了Delphi提供的異常類體系。我們力求給讀者一個清晰、全面的印象,使讀者能在自己的程式開發中實際使用它們。為便於理解我們也提供了一些簡單的敘述性示例。雖然在具體的使用中讀者還可能會碰到許多問題,但意識到應該用異常類來增強程式的健壯性卻是程式設計水準走上新台階的標誌。 

12.2 異常保護 

  確保回收分配的資源是程式健壯性的一個關鍵。但缺省情況下異常發生時程式會在出錯點自動結束目前模群組,因此需要一種特殊的機制來確保即使在異常發生的情況下釋放資源的語句仍能被執行。而Delphi的異常處理正提供了這種機制。 

12.2.1 需要保護的資源 

  一般說來需要保護的資源包括:

  ● 文件

  ● 記憶體

  ● Windows資源

  ● 物件 

  比如下面一段程式就會造成1K記憶體資源的丟失。 

var

APointer : Pointer ;

AInt , ADiv: Integer ;

begin

ADiv := 0;

GetMem ( APointer , 1024 );

AInt := 10 div ADiv ;

FreeMem ( Apointer , 1024 );

end; 

由於程式從異常發生點結束從而FreeMem永遠沒有執行的機會。 

12.2.2 產生一個資源保護塊 

  Delphi提供了一個保留字finally,用於實現資源的保護: 

  {分配資源}

  try

{資源使用情況}

finally

{釋放資源}

  end; 

tryfinallyend就形成了一個資源保護塊。finally後面的語句是在任何情況下,不論程式是否發生異常,都會執行的。

  對於(12.2.1)中的例子如下代碼即可確保所分配記憶體資源的釋放: 

var

APointer : Pointer ;

AInt , ADiv : Integer;

begin

ADiv := 0;

GetMem ( APointer , 1024 );

try

AInt := 10 div ADiv ;

finally

FreeMem ( Apointer , 1024 );

end;

end; 

下面的例子摘自(6.4)節,是在文件拷貝中實現文件資源的保護: 

procedure CopyFile(const FileName, DestName: TFileName);

var

CopyBuffer: Pointer;

TimeStamp, BytesCopied: Longint;

Source, Dest: Integer;

Destination: TFileName;

const

ChunkSize: Longint = 8192;

begin

Destination := ExpandFileName(DestName);

if HasAttr(Destination, faDirectory) then

Destination := Destination + '\' + ExtractFileName(FileName);

TimeStamp := FileAge(FileName);

GetMem(CopyBuffer, ChunkSize);

try

Source := FileOpen(FileName, fmShareDenyWrite);

if Source < 0 then

raise EFOpenError.Create(FmtLoadStr(SFOpenError, [FileName]));

try

Dest := FileCreate(Destination);

if Dest < 0 then

raise EFCreateError.Create(FmtLoadStr(SFCreateError, [Destination]));

try

repeat

BytesCopied := FileRead(Source, CopyBuffer^, ChunkSize);

if BytesCopied > 0 then

FileWrite(Dest, CopyBuffer^, BytesCopied);

until BytesCopied < ChunkSize;

finally

FileClose(Dest);

end;

finally

FileClose(Source);

end;

finally

FreeMem(CopyBuffer, ChunkSize);

end;

end;

程式的具體解釋見 (6.4)節。

  在異常保護的情況下,當異常發生時,系統會自動彈出一個訊息框用於顯示異常的訊息。結束目前模群組後異常類自動清除。

 

後一頁
前一頁
回目錄
回首頁