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

日期: 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宏就是用來拋出相同異常的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內(nèi)容