日期: 2005
當(dāng)我們提到CLR里的“異常”,要注意一個很重要的區(qū)別。有通過如C#的try/catch/finally暴露給應(yīng)用程序,并由運行時提供機制全權(quán)實現(xiàn)的托管異常。也有運行時自己使用的異常。大部分運行時開發(fā)人員很少需要想到如何實現(xiàn)并暴露托管異常模型。但每個運行時開發(fā)人員都應(yīng)該懂得CLR實現(xiàn)里是怎么使用異常的。為了保持區(qū)分,本文將托管程序拋出并捕捉的稱為托管異常,而將運行時自己使用的錯誤處理方式稱為 CLR內(nèi)部異常。本文主要討論CLR內(nèi)部異常。
異常在什么地方有用?
異常幾乎在所有地方都有用。最有用的地方就是拋出或捕捉異常的函數(shù)里,因為需要顯式編寫代碼來拋出異?;蛘卟蹲狡洳?yōu)雅的處理異常。即使一個函數(shù)本身不拋出異常,它也有可能調(diào)用拋出異常的函數(shù)。這樣該函數(shù)必須在異常拋出的時候行為正常。明智的使用支持物(holders)可以極大簡化正確編寫這類代碼。
為什么CLR內(nèi)部異常是不同的?
CLR內(nèi)部異常更像C++異常,但不完全是。CLR可以在Mac OSX、BSD還有Windows下編譯。操作系統(tǒng)和編譯器的差異使得我們不能僅使用標準C++的try/catch。另外,CLR內(nèi)部異常還提供了類似托管代碼的“finally”和“fault”這樣的功能。
通過一些宏,編寫異常處理代碼就像標準C++那樣簡單。
捕捉異常
EX_TRY
最基本的宏是:EX_TRY / EX_CATCH / EX_END_CATCH,使用方法如下:
EX_TRY
// 調(diào)用一些函數(shù),也許會拋出一個異常
Bar();
EX_CATCH
// 在這里,那就有錯誤發(fā)生了
m_finalDisposition = terminallyHopeless;
EX_END_CATCH(RethrowTransientExceptions)
EX_TRY宏就是引入try塊,很像C++的“try”,除了其還添加了一個大括號:“{”。
EX_CATCH
EX_CATCH宏結(jié)束一個try塊,并添加一個大括號:“}”,并且開始catch塊。跟EX_TRY類似,其也添加了一個大括號來開始catch塊。
這里和C++異常有很大的不同:CLR開發(fā)者根本不明確捕捉什么。實際上,這些宏捕捉包括類似AV的非C++異?;蛲泄墚惓5娜魏螙|西。如果一塊代碼只需要捕捉一個或者一小部分異常,那么它需要捕捉并檢查異常,然后將所有不相關(guān)的異常再次拋出。
需要再次指明的是EX_CATCH宏捕捉任何東西。這個可能不是一個函數(shù)需要的。下兩個章節(jié)討論如何處理不應(yīng)該被捕捉的異常。
GET_EXCEPTION() & GET_THROWABLE()
當(dāng)一個CLR開發(fā)人員捕捉到一個東西,那么他要如何決定做什么?取決于需求,有幾個選項:
第一,無論捕捉到什么(C++)異常,都是繼承自全局的Exception類的類的實例。一些繼承類很明顯,如OutOfMemoryException。另一些則有些領(lǐng)域相關(guān),如EETypeLoadException。還有些類只是系統(tǒng)異常的簡單封裝,如CLRException(包含OBJECTHANDLE字段指向一個托管異常),或HRException(HRESULT的封裝)。如果最初的異常不是從Exception繼承來的,那么宏會給其做一個封裝。(注意所有異常都是系統(tǒng)自帶而且眾所周知的)。
第二,每個CLR內(nèi)部異常都有一個關(guān)聯(lián)的HRESULT值。有時像HRException那樣,值從某個COM對象來的,但內(nèi)部異常和Win32 api錯誤值也有HRESULT值。
最后,幾乎所有CLR內(nèi)部發(fā)生的異常都有可能傳遞到托管代碼那邊,CLR內(nèi)部異常都有跟其對應(yīng)的托管異常。創(chuàng)建托管異常不是必須的,但是總有辦法獲取它。
那么,CLR開發(fā)人員將如何給一個異常分類呢?
常用的做法是,通過異常關(guān)聯(lián)的HRESULT值分類,而且有一個很簡單的辦法取值:
HRESULT hr = GET_EXCEPTION()->GetHR();
通過對應(yīng)的托管異常對象獲取更多信息是更便捷的辦法。如果異常要傳遞到托管代碼,無論是即時還是被捕捉稍后處理,都是需要這個托管對象的。而且這個異常對象也很容易讀取,其是一個托管的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()
有時,雖然是異常實現(xiàn)的底層,無法避免要用到C++異常對象。如果C++異常的類型很重要,也有一些輕量級的RTTI函數(shù)來幫助歸類異常,如:
Exception *pEx = GET_EXCEPTION();
if (pEx->IsType(CLRException::GetType())) {/* ... */}
可以反饋一個異常是否是(或繼承自)CLRException。
EX_END_CATCH(RethrowTransientExceptions)
在上面的例子中,“RethrowTransientExceptions”是宏EX_END_CATCH的一個參數(shù);它是三個預(yù)定義的宏,并可以看成“異常的性格”。下面是這些宏的解釋:
- SwallowAllExceptions: 命名很簡單巧妙。如名字所示,它吞沒任何對象。顯而易見,通常不是正確的做法。
- RethrowTerminalExceptions: 一個更好的名字應(yīng)該是"RethrowThreadAbort", 也就是這個宏的作用。
-
RethrowTransientExceptions:"臨時"異常的最好定義是,如果重試則該異常在其它環(huán)境里有可能不再發(fā)生。下面這些是臨時異常:
- 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ā)人員都需要考慮捕捉哪些異常,并只捕捉這些異常。而且,因為這個宏捕捉所有的東西,不去捕捉一個異常的唯一方法就是重新拋出它。
如果一個EX_CATCH / EX_END_CATCH塊正確分類異常,并在必要的時候重新拋出,那么SwallowAllExceptions就是告訴宏不必重新拋出異常的辦法。
EX_CATCH_HRESULT
有的時候需要的就是異常對應(yīng)的那個HRESULT值,特別是針對COM的代碼。對于這些情況,使用EX_CATCH_HRESULT宏比編寫一個EX_CATCH塊簡單的多。一個典型代碼片段如下:
HRESULT hr;
EX_TRY
// code
EX_CATCH_HRESULT (hr)
return hr;
然而,雖然很誘人,但不總是正確的。EX_CATCH_HRESULT捕捉所有的異常,保存HRESULT,并丟掉原始異常。因此,除非丟掉異常這個行為是函數(shù)所需要的,否則EX_CATCH_HRESULT并不是很合適。
EX_RETHROW
如上所述,異常宏捕捉所有異常;捕捉一個指定異常的唯一辦法是先捕捉所有的異常,再將除了要捕捉的其它異常再次拋出。因此,當(dāng)一個異常被捕捉,處理之后,結(jié)果其不是要被捕捉的,那它可能會被重新拋出。EX_RETHROW宏就是用來拋出相同異常的。