所有开发人员都应该了解的关于运行时异常的知识(上)

日期: 2005

当我们提到CLR里的“异常”,要注意一个很重要的区别。有通过如C#的try/catch/finally暴露给应用程序,并由运行时提供机制全权实现的托管异常。也有运行时自己使用的异常。大部分运行时开发人员很少需要想到如何实现并暴露托管异常模型。但每个运行时开发人员都应该懂得CLR实现里是怎么使用异常的。为了保持区分,本文将托管程序抛出并捕捉的称为托管异常,而将运行时自己使用的错误处理方式称为 CLR内部异常。本文主要讨论CLR内部异常。

异常在什么地方有用?

异常几乎在所有地方都有用。最有用的地方就是抛出或捕捉异常的函数里,因为需要显式编写代码来抛出异常或者捕捉其并优雅的处理异常。即使一个函数本身不抛出异常,它也有可能调用抛出异常的函数。这样该函数必须在异常抛出的时候行为正常。明智的使用支持物(holders)可以极大简化正确编写这类代码。

为什么CLR内部异常是不同的?

CLR内部异常更像C++异常,但不完全是。CLR可以在Mac OSX、BSD还有Windows下编译。操作系统和编译器的差异使得我们不能仅使用标准C++的try/catch。另外,CLR内部异常还提供了类似托管代码的“finally”和“fault”这样的功能。

通过一些宏,编写异常处理代码就像标准C++那样简单。

捕捉异常

EX_TRY

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

EX_TRY
  // 调用一些函数,也许会抛出一个异常
  Bar();
EX_CATCH 
  // 在这里,那就有错误发生了
  m_finalDisposition = terminallyHopeless; 
EX_END_CATCH(RethrowTransientExceptions) 

EX_TRY宏就是引入try块,很像C++的“try”,除了其还添加了一个大括号:“{”。

EX_CATCH

EX_CATCH宏结束一个try块,并添加一个大括号:“}”,并且开始catch块。跟EX_TRY类似,其也添加了一个大括号来开始catch块。

这里和C++异常有很大的不同:CLR开发者根本不明确捕捉什么。实际上,这些宏捕捉包括类似AV的非C++异常或托管异常的任何东西。如果一块代码只需要捕捉一个或者一小部分异常,那么它需要捕捉并检查异常,然后将所有不相关的异常再次抛出。

需要再次指明的是EX_CATCH宏捕捉任何东西。这个可能不是一个函数需要的。下两个章节讨论如何处理不应该被捕捉的异常。

GET_EXCEPTION() & GET_THROWABLE()

当一个CLR开发人员捕捉到一个东西,那么他要如何决定做什么?取决于需求,有几个选项:

第一,无论捕捉到什么(C++)异常,都是继承自全局的Exception类的类的实例。一些继承类很明显,如OutOfMemoryException。另一些则有些领域相关,如EETypeLoadException。还有些类只是系统异常的简单封装,如CLRException(包含OBJECTHANDLE字段指向一个托管异常),或HRException(HRESULT的封装)。如果最初的异常不是从Exception继承来的,那么宏会给其做一个封装。(注意所有异常都是系统自带而且众所周知的)。

第二,每个CLR内部异常都有一个关联的HRESULT值。有时像HRException那样,值从某个COM对象来的,但内部异常和Win32 api错误值也有HRESULT值。

最后,几乎所有CLR内部发生的异常都有可能传递到托管代码那边,CLR内部异常都有跟其对应的托管异常。创建托管异常不是必须的,但是总有办法获取它。

那么,CLR开发人员将如何给一个异常分类呢?

常用的做法是,通过异常关联的HRESULT值分类,而且有一个很简单的办法取值:

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

通过对应的托管异常对象获取更多信息是更便捷的办法。如果异常要传递到托管代码,无论是即时还是被捕捉稍后处理,都是需要这个托管对象的。而且这个异常对象也很容易读取,其是一个托管的objectref引用,因此可以用常规办法:

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()

有时,虽然是异常实现的底层,无法避免要用到C++异常对象。如果C++异常的类型很重要,也有一些轻量级的RTTI函数来帮助归类异常,如:

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

可以反馈一个异常是否是(或继承自)CLRException。

EX_END_CATCH(RethrowTransientExceptions)

在上面的例子中,“RethrowTransientExceptions”是宏EX_END_CATCH的一个参数;它是三个预定义的宏,并可以看成“异常的性格”。下面是这些宏的解释:

  • SwallowAllExceptions: 命名很简单巧妙。如名字所示,它吞没任何对象。显而易见,通常不是正确的做法。
  • RethrowTerminalExceptions: 一个更好的名字应该是"RethrowThreadAbort", 也就是这个宏的作用。
  • RethrowTransientExceptions:"临时"异常的最好定义是,如果重试则该异常在其它环境里有可能不再发生。下面这些是临时异常:
    • 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开发人员在不确定的情况下一般应该使用RethrowTransientExceptions.

但在任何情况下,编写EX_END_CATCH的开发人员都需要考虑捕捉哪些异常,并只捕捉这些异常。而且,因为这个宏捕捉所有的东西,不去捕捉一个异常的唯一方法就是重新抛出它。

如果一个EX_CATCH / EX_END_CATCH块正确分类异常,并在必要的时候重新抛出,那么SwallowAllExceptions就是告诉宏不必重新抛出异常的办法。

EX_CATCH_HRESULT

有的时候需要的就是异常对应的那个HRESULT值,特别是针对COM的代码。对于这些情况,使用EX_CATCH_HRESULT宏比编写一个EX_CATCH块简单的多。一个典型代码片段如下:

HRESULT hr;
EX_TRY
  // code
EX_CATCH_HRESULT (hr)

return hr;

然而,虽然很诱人,但不总是正确的。EX_CATCH_HRESULT捕捉所有的异常,保存HRESULT,并丢掉原始异常。因此,除非丢掉异常这个行为是函数所需要的,否则EX_CATCH_HRESULT并不是很合适。

EX_RETHROW

如上所述,异常宏捕捉所有异常;捕捉一个指定异常的唯一办法是先捕捉所有的异常,再将除了要捕捉的其它异常再次抛出。因此,当一个异常被捕捉,处理之后,结果其不是要被捕捉的,那它可能会被重新抛出。EX_RETHROW宏就是用来抛出相同异常的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容