所有開發(fā)人員都應(yīng)該了解的關(guān)于運(yùn)行時(shí)異常的知識(shí)(上)

日期: 2005

當(dāng)我們提到CLR里的“異?!?,要注意一個(gè)很重要的區(qū)別。有通過如C#的try/catch/finally暴露給應(yīng)用程序,并由運(yùn)行時(shí)提供機(jī)制全權(quán)實(shí)現(xiàn)的托管異常。也有運(yùn)行時(shí)自己使用的異常。大部分運(yùn)行時(shí)開發(fā)人員很少需要想到如何實(shí)現(xiàn)并暴露托管異常模型。但每個(gè)運(yùn)行時(shí)開發(fā)人員都應(yīng)該懂得CLR實(shí)現(xiàn)里是怎么使用異常的。為了保持區(qū)分,本文將托管程序拋出并捕捉的稱為托管異常,而將運(yùn)行時(shí)自己使用的錯(cuò)誤處理方式稱為 CLR內(nèi)部異常。本文主要討論CLR內(nèi)部異常。

異常在什么地方有用?

異常幾乎在所有地方都有用。最有用的地方就是拋出或捕捉異常的函數(shù)里,因?yàn)樾枰@式編寫代碼來拋出異?;蛘卟蹲狡洳?yōu)雅的處理異常。即使一個(gè)函數(shù)本身不拋出異常,它也有可能調(diào)用拋出異常的函數(shù)。這樣該函數(shù)必須在異常拋出的時(shí)候行為正常。明智的使用支持物(holders)可以極大簡(jiǎn)化正確編寫這類代碼。

為什么CLR內(nèi)部異常是不同的?

CLR內(nèi)部異常更像C++異常,但不完全是。CLR可以在Mac OSX、BSD還有Windows下編譯。操作系統(tǒng)和編譯器的差異使得我們不能僅使用標(biāo)準(zhǔn)C++的try/catch。另外,CLR內(nèi)部異常還提供了類似托管代碼的“finally”和“fault”這樣的功能。

通過一些宏,編寫異常處理代碼就像標(biāo)準(zhǔn)C++那樣簡(jiǎn)單。

捕捉異常

EX_TRY

最基本的宏是:EX_TRY / EX_CATCH / EX_END_CATCH,使用方法如下:

EX_TRY
  // 調(diào)用一些函數(shù),也許會(huì)拋出一個(gè)異常
  Bar();
EX_CATCH 
  // 在這里,那就有錯(cuò)誤發(fā)生了
  m_finalDisposition = terminallyHopeless; 
EX_END_CATCH(RethrowTransientExceptions) 

EX_TRY宏就是引入try塊,很像C++的“try”,除了其還添加了一個(gè)大括號(hào):“{”。

EX_CATCH

EX_CATCH宏結(jié)束一個(gè)try塊,并添加一個(gè)大括號(hào):“}”,并且開始catch塊。跟EX_TRY類似,其也添加了一個(gè)大括號(hào)來開始catch塊。

這里和C++異常有很大的不同:CLR開發(fā)者根本不明確捕捉什么。實(shí)際上,這些宏捕捉包括類似AV的非C++異?;蛲泄墚惓5娜魏螙|西。如果一塊代碼只需要捕捉一個(gè)或者一小部分異常,那么它需要捕捉并檢查異常,然后將所有不相關(guān)的異常再次拋出。

需要再次指明的是EX_CATCH宏捕捉任何東西。這個(gè)可能不是一個(gè)函數(shù)需要的。下兩個(gè)章節(jié)討論如何處理不應(yīng)該被捕捉的異常。

GET_EXCEPTION() & GET_THROWABLE()

當(dāng)一個(gè)CLR開發(fā)人員捕捉到一個(gè)東西,那么他要如何決定做什么?取決于需求,有幾個(gè)選項(xiàng):

第一,無論捕捉到什么(C++)異常,都是繼承自全局的Exception類的類的實(shí)例。一些繼承類很明顯,如OutOfMemoryException。另一些則有些領(lǐng)域相關(guān),如EETypeLoadException。還有些類只是系統(tǒng)異常的簡(jiǎn)單封裝,如CLRException(包含OBJECTHANDLE字段指向一個(gè)托管異常),或HRException(HRESULT的封裝)。如果最初的異常不是從Exception繼承來的,那么宏會(huì)給其做一個(gè)封裝。(注意所有異常都是系統(tǒng)自帶而且眾所周知的)。

第二,每個(gè)CLR內(nèi)部異常都有一個(gè)關(guān)聯(lián)的HRESULT值。有時(shí)像HRException那樣,值從某個(gè)COM對(duì)象來的,但內(nèi)部異常和Win32 api錯(cuò)誤值也有HRESULT值。

最后,幾乎所有CLR內(nèi)部發(fā)生的異常都有可能傳遞到托管代碼那邊,CLR內(nèi)部異常都有跟其對(duì)應(yīng)的托管異常。創(chuàng)建托管異常不是必須的,但是總有辦法獲取它。

那么,CLR開發(fā)人員將如何給一個(gè)異常分類呢?

常用的做法是,通過異常關(guān)聯(lián)的HRESULT值分類,而且有一個(gè)很簡(jiǎn)單的辦法取值:

HRESULT hr = GET_EXCEPTION()->GetHR();

通過對(duì)應(yīng)的托管異常對(duì)象獲取更多信息是更便捷的辦法。如果異常要傳遞到托管代碼,無論是即時(shí)還是被捕捉稍后處理,都是需要這個(gè)托管對(duì)象的。而且這個(gè)異常對(duì)象也很容易讀取,其是一個(gè)托管的objectref引用,因此可以用常規(guī)辦法:

OBJECTREF throwable = NULL;
GCPROTECT_BEGIN(throwable);
// . . .
EX_TRY
    // . . . do something that might throw
EX_CATCH
    throwable = GET_THROWABLE();
EX_END_CATCH(RethrowTransientExceptions)
// . . . do something with throwable
GCPROTECT_END()

有時(shí),雖然是異常實(shí)現(xiàn)的底層,無法避免要用到C++異常對(duì)象。如果C++異常的類型很重要,也有一些輕量級(jí)的RTTI函數(shù)來幫助歸類異常,如:

Exception *pEx = GET_EXCEPTION();
if (pEx->IsType(CLRException::GetType())) {/* ... */}

可以反饋一個(gè)異常是否是(或繼承自)CLRException。

EX_END_CATCH(RethrowTransientExceptions)

在上面的例子中,“RethrowTransientExceptions”是宏EX_END_CATCH的一個(gè)參數(shù);它是三個(gè)預(yù)定義的宏,并可以看成“異常的性格”。下面是這些宏的解釋:

  • SwallowAllExceptions: 命名很簡(jiǎn)單巧妙。如名字所示,它吞沒任何對(duì)象。顯而易見,通常不是正確的做法。
  • RethrowTerminalExceptions: 一個(gè)更好的名字應(yīng)該是"RethrowThreadAbort", 也就是這個(gè)宏的作用。
  • RethrowTransientExceptions:"臨時(shí)"異常的最好定義是,如果重試則該異常在其它環(huán)境里有可能不再發(fā)生。下面這些是臨時(shí)異常:
    • COR_E_THREADABORTED
    • COR_E_THREADINTERRUPTED
    • COR_E_THREADSTOP
    • COR_E_APPDOMAINUNLOADED
    • E_OUTOFMEMORY
    • HRESULT_FROM_WIN32(ERROR_COMMITMENT_LIMIT)
    • HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY)
    • (HRESULT)STATUS_NO_MEMORY
    • COR_E_STACKOVERFLOW
    • MSEE_E_ASSEMBLYLOADINPROGRESS

CLR開發(fā)人員在不確定的情況下一般應(yīng)該使用RethrowTransientExceptions.

但在任何情況下,編寫EX_END_CATCH的開發(fā)人員都需要考慮捕捉哪些異常,并只捕捉這些異常。而且,因?yàn)檫@個(gè)宏捕捉所有的東西,不去捕捉一個(gè)異常的唯一方法就是重新拋出它。

如果一個(gè)EX_CATCH / EX_END_CATCH塊正確分類異常,并在必要的時(shí)候重新拋出,那么SwallowAllExceptions就是告訴宏不必重新拋出異常的辦法。

EX_CATCH_HRESULT

有的時(shí)候需要的就是異常對(duì)應(yīng)的那個(gè)HRESULT值,特別是針對(duì)COM的代碼。對(duì)于這些情況,使用EX_CATCH_HRESULT宏比編寫一個(gè)EX_CATCH塊簡(jiǎn)單的多。一個(gè)典型代碼片段如下:

HRESULT hr;
EX_TRY
  // code
EX_CATCH_HRESULT (hr)

return hr;

然而,雖然很誘人,但不總是正確的。EX_CATCH_HRESULT捕捉所有的異常,保存HRESULT,并丟掉原始異常。因此,除非丟掉異常這個(gè)行為是函數(shù)所需要的,否則EX_CATCH_HRESULT并不是很合適。

EX_RETHROW

如上所述,異常宏捕捉所有異常;捕捉一個(gè)指定異常的唯一辦法是先捕捉所有的異常,再將除了要捕捉的其它異常再次拋出。因此,當(dāng)一個(gè)異常被捕捉,處理之后,結(jié)果其不是要被捕捉的,那它可能會(huì)被重新拋出。EX_RETHROW宏就是用來拋出相同異常的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容