delphi中異常的截獲及其個性化處理
黃悅、謝曉東 【摘要】應(yīng)用程序中的異常幾乎是不可避免的,本文探討了delphi的異常處理機(jī)制和異常處理的幾種方法,并結(jié)合一些實例介紹如何根據(jù)應(yīng)用程序的需要開發(fā)自己的異常處理程序。 一、 異常及異常處理機(jī)制 異??梢岳斫鉃橐环N特殊的事件,當(dāng)這種特殊的事件發(fā)生時,程序正常的執(zhí)行流程將被打斷。異常處理機(jī)制能夠確保在發(fā)生異常的情況下,應(yīng)用程序不會中止運(yùn) 行,并且能以一致的風(fēng)格進(jìn)行處理。許多剛剛涉及delphi的程序員感覺異常處理機(jī)制高深莫測,是高級程序員的“專利”,總有盡可能繞開它的想法,因而不 得不小心翼翼地檢查每一次函數(shù)調(diào)用的返回值,或者通過額外的代碼捕獲可能的錯誤,即使這樣,也只能保證程序代碼不出錯,而無法保證包括操作系統(tǒng)、設(shè)備驅(qū)動 程序、數(shù)據(jù)庫驅(qū)動程序、delphi自身的元件庫和運(yùn)行時間庫在內(nèi)的軟硬件設(shè)備不出錯。問題就在于,通過檢查函數(shù)的返回值或設(shè)置錯誤陷阱只能捕獲可預(yù)見的 錯誤,如果出現(xiàn)沒有預(yù)見到的錯誤,或者函數(shù)調(diào)用本身就已失敗,程序正常的流程就會被打亂。由此可見,異常幾乎是不可避免的。其實,object pascal提供了多種異常處理方法,使異常的處理變得輕而易舉,可以將你從為你的應(yīng)用程序執(zhí)行的每個任務(wù)編寫單獨(dú)的錯誤處理程序的繁瑣工作中解放出來。 二、 處理異常的方法 1. try...except 結(jié)構(gòu) try...except 結(jié)構(gòu)是這樣工作的:try后面到except之前的語句通常是希望正常執(zhí)行的代碼,在執(zhí)行過程中如果觸發(fā)了異常,程序就跳入except部分。在except部分做出的處理可以有以下三種類型: (a) 不需要確切知道異常的類型,只需要做籠統(tǒng)的處理時,可以彈出一個警告信息框,或者釋放已分配的資源,或者退出程序。程序示例如下: 例1 procedure tform1.button1click(sender :tobject) var num: integer; begin try num:=strtoint(edit1.text); edit2.text:=inttostr(num*num); except showmessage(edit1.text+'無法轉(zhuǎn)成整數(shù)!'); end; end; 該例中,點擊button1后,程序試圖將編輯框edit1中的內(nèi)容轉(zhuǎn)換為整型數(shù),整數(shù)平方之后再轉(zhuǎn)換成字符串類型數(shù)據(jù),在編輯框edit2中顯示。 如果edit1.text是7.89,傳遞給strtoint將產(chǎn)生econverterror異常,因為7.89不是一個有效整數(shù)。異常產(chǎn)生后,將顯示 一條警告信息,并退出該過程。 (b) 利用異常處理句柄處理某個特定的異常 例如,在例1中可以針對econverterror異常做出如下的處理: 例2 procedure tform1.button1click(sender :tobject) var num: integer; begin try num:=strtoint(edit1.text); edit2.text:=inttostr(num*num); except on econverterror do showmessage(edit1.text+'無法轉(zhuǎn)成整數(shù)!'); end; end; (c) 利用異常處理句柄處理多種異常 通常,執(zhí)行的代碼不只產(chǎn)生一種異常,這就需要在except部分進(jìn)行更具體的測試,根據(jù)產(chǎn)生的異常類型分別做出處理?,F(xiàn)將例2稍加改動如下: 例3 procedure tform1.button1click(sender :tobject) var num: integer; begin try num:=strtoint(edit1.text); edit2.text:=inttostr(30 div num); except on econverterror do showmessage(edit1.text+'無法轉(zhuǎn)成整數(shù)!'); on edivbyzero do begin showmessage('除數(shù)不能為零!'); edit2.text:='0'; end; end; end; 在例3中,try部分的第一條語句可能產(chǎn)生econverterror 異常;當(dāng) num為零時,try 部分的第二條語句還會產(chǎn)生被零除的異常(即產(chǎn)生edivbyzero 異常)。由于產(chǎn)生異常原因不同,需要分別進(jìn)行不同的處理。例3中的except 部分的代碼也可以寫成以下的形式,這兩種異常處理的結(jié)果是一樣的。 except on e:exception do begin if e is econverterro then showmessage(edit1.text+'無法轉(zhuǎn)成整數(shù)!'); if e is edivbyzero then begin showmessage('除數(shù)不能為零!'); edit2.text:='0'; end; end; 2. try...finally 結(jié)構(gòu) try...finally 結(jié)構(gòu)最大的用處是在異常發(fā)生的情況下,確保釋放應(yīng)用程序已經(jīng)分配的資源,或者完成一些必須的操作,比如:剪貼板clipboard 在打開之后必須調(diào)用close 方法將剪貼板關(guān)閉;數(shù)據(jù)感知組件更新禁止之后必須調(diào)用enablecontrols方法才能使更新顯示有效等。try...finally 結(jié)構(gòu)之所以能做到這一點,是因為不管異常是否發(fā)生,程序都要執(zhí)行finally 部分。請看下面的例子: 例4 procedure tform1.button1click(sender :tobject) var icon : ticon; begin try icon:=ticon.create; icon.loadfromfile('spin.ico'); imagelist1.replaceicon(0,icon); finally icon.free; end; end; 在例4中,try 部分代碼是從磁盤獲取一個圖標(biāo),用來代替imagelist1 中的0號圖標(biāo)。如果圖標(biāo)文件spin.ico 不存在或者被損壞,將會產(chǎn)生efopenerror 異常,中斷 try 部分代碼的執(zhí)行,但是程序會執(zhí)行finally 部分的代碼,使得icon 占用的資源得以釋放。當(dāng)然,如果沒有發(fā)生任何異常,finally 部分也將被執(zhí)行??梢哉f,finally 部分是 try...finally 結(jié)構(gòu)的“必經(jīng)之路”。 3. try...except 與try...finally 的嵌套結(jié)構(gòu) try...finally 結(jié)構(gòu)確實有它獨(dú)到的功能,但是try...finally結(jié)構(gòu)中沒有 try...except 結(jié)構(gòu)中的異常處理句柄,也就無法知道當(dāng)前確切的異常類型,并且不論異常是否發(fā)生,程序都執(zhí)行 finally 部分,這就決定了 try...finally 結(jié)構(gòu)無法對發(fā)生的異常進(jìn)行特別的處理。如果在應(yīng)用程序中,既需要完成一些必要的操作,又要對發(fā)生的異常進(jìn)行處理,那么最好使用這兩種結(jié)構(gòu)的嵌套。請看下 例: 例5 procedure tform1.button1click(sender :tobject) var icon : ticon; begin try try icon:=ticon.create; icon.loadfromfile('spin.ico'); imagelist1.replaceicon(0,icon); finally icon.free; end; except on efopenerror do showmessage('無法打開圖標(biāo)文件spin.ico'); end; end; 在例5中,無論異常發(fā)生與否,都將執(zhí)行finally 部分釋放圖標(biāo)占用的資源。一旦異常發(fā)生,則產(chǎn)生警告信息。上例是在 try...except 結(jié)構(gòu)的 try 部分嵌套 try...finally 結(jié)構(gòu),也可以在 try...finally 結(jié)構(gòu)的 try 部分嵌套 try...except 結(jié)構(gòu),前者是先執(zhí)行必須的操作,再進(jìn)行異常的處理,而后者是先進(jìn)行異常的處理,再執(zhí)行必須的操作。在多數(shù)情況下,兩者的區(qū)別并不大。除此之 外,try...except 結(jié)構(gòu)和try...finally 結(jié)構(gòu)還可以各自嵌套。在復(fù)雜的應(yīng)用程序中,except 部分或finally部分的異常處理代碼本身又有可能觸發(fā)異常,這就需要采用嵌套結(jié)構(gòu)編寫更深一層的異常處理代碼,這里就不再詳細(xì)舉例了。 4. 定義自己的異常類 我們的探討將進(jìn)入更深的一層——定義自己的異常類。雖然在簡單的應(yīng)用程序中,程序員并無太大必要定義自己的異常類,但是它對編寫個性化的異常處理程序 仍然是有幫助的。例7中的過程是對數(shù)據(jù)集 table1 中的字段name進(jìn)行合法性檢查,如果name 沒有值或為空字符串,則產(chǎn)生異常,這其中就定義了一個 exception 的派生類emyexception。 例6 type emyexception=class(exception); {定義自己的異常類emyexception} procedure tform1.table1namevalidate(sender : tfield); begin if ( sender.isnull ) or ( sender.asstring='') then raise emyexception.create('必須填寫名稱'); end; 三、個性化的異常處理句柄 delphi會自動處理大部分的異常,但全英文信息提示會讓應(yīng)用程序的最終用戶感覺并不友好。那么如何編寫自己的異常處理代碼呢? 在forms單元中聲明的tapplication 類中的onexception 事件為編寫自己的異常處理代碼提供了可能。事實上,用于onexception 事件的事件句柄就是應(yīng)用程序默認(rèn)的異常處理句柄。請看下面的例子。 在 project resource 中,首先創(chuàng)建一個新類 tmyclass, tmyclass 中有一個myexceptionhandler 方法,可以根據(jù)應(yīng)用程序的需要來編寫,這里體現(xiàn)了個性化的異常處理。由myexceptionhandler事件來響應(yīng)application 的 onexception事件,這樣在程序發(fā)生異常時,首先調(diào)用的就是自己編寫的異常處理事件myexceptionhandler事件。工程文件如下: 例7 program exceptprj; uses forms, windows, sysutils, classes, dialogs, db, dbtables, example in 'example.pas' {form1}; {創(chuàng)建一個tobject的派生類tmyclass} type tmyclass=class(tobject) public procedure myexceptionhandler(sender: tobject; einstance: exception); end; {編寫自己的異常處理句柄} procedure tmyclass.myexceptionhandler(sender: tobject; einstance: exception); var errorfile:textfile; filename,content:string; findflag:boolean; begin {截獲出現(xiàn)的新的異常,并存入文件errorinfo.txt.} filename:=applicationpath+'errorinfo.txt'; { applicationpath 是在主form中定義的全局變量,記錄應(yīng)用程序所在的目錄} {打開文件} assignfile(errorfile,filename); if not fileexists(filename) then rewrite(errorfile) else reset(errorfile); {檢查出現(xiàn)的異常是否曾經(jīng)記錄在文件errorinfo.txt中} findflag:=false; while not seekeof(errorfile) do begin readln(errorfile,content); if pos(einstance.classname+':'+einstance.message,content)<>0 then begin findflag:=true; break; end; end; {如果是一個未被記錄過的異常,則將它記錄在文件中} if not findflag then begin showmessage('出現(xiàn)新的錯誤!'); append(errorfile); writeln(errorfile,einstance.classname+':'+einstance.message); end; {關(guān)閉文件} closefile(errorfile); {對出現(xiàn)的異常顯示中文提示} if einstance is edivbyzero then application.messagebox('除數(shù)不能為零!','錯誤',mb_ok+mb_iconstop) else if einstance is eaccessviolation then application.messagebox('訪問了無效的內(nèi)存區(qū)域!','錯誤',mb_ok+mb_iconstop) else if (einstance is edatabaseerror) then application.messagebox('數(shù)據(jù)庫操作出現(xiàn)錯誤!','錯誤',mb_ok+mb_iconstop) else if (einstance is efopenerror) then application.messagebox('文件不能打開!','錯誤',mb_ok+mb_iconstop) else if (einstance is econverterror) then application.messagebox('非法的類型轉(zhuǎn)換!','錯誤',mb_ok+mb_iconstop) else messagedlg(einstance.classname+':'+einstance.message,mtinformation,[mbok],0) end; {$r *.res} var myobject: tmyclass; {聲明tmyclass類的一個變量} begin application.initialize; application.createform(tform1, form1); applicationpath:=extractfilepath(application.exename); myobject:=tmyclass.create; {創(chuàng)建tmyclass類的一個實例} application.onexception:=myobject.myexceptionhandler; {響應(yīng)onexception事件} application.run; end. 例7中的myexceptionhandler 事件是我們在開發(fā)鐵路車站行車工作細(xì)則管理系統(tǒng)時編制的,由于篇幅所限,在此僅將這一事件的整體框架呈現(xiàn)給大家,希望通過這個例子來說明開發(fā)異常處理句柄的方法。 到此為止,我們對delphi異常處理的幾種方法由淺入深地進(jìn)行了歸納,并對應(yīng)于各種方法列舉了實例,希望對異常處理懷有“膽怯”心理的編程朋友有所幫助。當(dāng)然,在遇到具體問題的時候,還需要您選擇最適合的異常處理方法。 |
|
來自: 遠(yuǎn)在南非 > 《異常處理》