Windows结构化异常处理浅析
分类:网站首页

近年直接被二个主题素材所干扰,正是写出来的程序老是现身无故崩溃,有的地点和睦领会或许有标题,不过有的地方又历来不能知道有哪些难题。越来越苦逼的事体是,大家的次第是供给7x24劳动顾客,即使没有供给实时精准零差错,然而总不可能冒出断线错失数据状态。故偏巧通过拍卖该难点,找到了有的化解方案,怎么捕获做客非法内存地址或者0除以多少个数。从而就碰见了那几个结构化非凡管理,今就轻便做个介绍认知下,方便大家蒙受相关主题素材后,首先知道难点由来,再便是如何消亡。废话相当的少说,上面走入正题。

什么样是结构化十分处理

结构化万分管理(structured exception handling,下文简单称谓:SEH卡塔尔,是用作意气风发种系统一编写制引进到操作系统中的,本身与语言非亲非故。在大家友好的前后相继中使用SEH能够让我们集中精力开垦首要成效,而把程序中所只怕现身的特别实行统意气风发的拍卖,使程序显得愈发洗练且增添可读性。

使用SHE,并不表示能够完全忽视代码中大概现身的失实,可是大家得以将软件专业流程和软件非常景况管理实行分离,先三月不知肉味干主要且急切的活,再来管理那些也许会凌驾各类的乖谬的显要不紧迫的标题(不急迫,但相对主要卡塔 尔(英语:State of Qatar)

当在前后相继中接纳SEH时,就改为编写翻译器相关的。其所产生的担任首要由编写翻译程序来顶住,比方编写翻译程序会发生部分表(table)来支撑SEH的数据结构,还有可能会提供回调函数。

注:
而不是混淆SHE和C++ 非凡管理。C++ 卓殊管理再方式上海展览中心现为使用首要字catchthrow,那么些SHE的样式不均等,再windows Visual C++中,是通过编写翻译器和操作系统的SHE举行落到实处的。

在所有 Win32 操作系统提供的体制中,使用最遍布的未公开的建制恐怕就要数SHE了。一提到SHE,恐怕就能让人想起 *__try__finally* 和 *__except* 之类的台词。SHE实际富含两地方的职能:停下管理(termination handing)非常管理(exception handing)

停下管理

终止管理程序确认保证不管贰个代码块(被爱护代码)是何等退出的,此外贰个代码块(终止处理程序)总是能被调用和施行,其语法如下:

__try
{
    //Guarded body
    //...
}
__finally
{
    //Terimnation handler
    //...
}

**__try __finally** 关键字标记了甘休管理程序的八个部分。操作系统和编写翻译器的合营专门的学问有限支撑了不许珍贵代码部分是何等退出的(不论是平常退出、如故要命退出)终止程序都会被调用,即**__finally**代码块都能进行。

try块的不奇怪化退出与卓殊退出

try块大概会因为returngoto,极度等非当然退出,也也许会因为成功履行而本来退出。但无论是try块是怎么着退出的,finally块的开始和结果都会被实践。

int Func1()
{
    cout << __FUNCTION__ << endl;
    int nTemp = 0;
    __try{
        //正常执行
        nTemp = 22;
        cout << "nTemp = " << nTemp << endl;
    }
    __finally{
        //结束处理
        cout << "finally nTemp = " << nTemp << endl;
    }
    return nTemp;
}

int Func2()
{
    cout << __FUNCTION__ << endl;
    int nTemp = 0;
    __try{
        //非正常执行
        return 0;
        nTemp = 22;
        cout << "nTemp = " << nTemp << endl;
    }
    __finally{
        //结束处理
        cout << "finally nTemp = " << nTemp << endl;
    }
    return nTemp;
}

结果如下:

Func1
nTemp = 22  //正常执行赋值
finally nTemp = 22  //结束处理块执行

Func2
finally nTemp = 0   //结束处理块执行

如上实例能够看见,通过动用终止管理程序可以卫戍太早实行return语句,当return说话视图退出try块的时候,编译器会让finally代码块再它早前实施。对于在三十二线程编制程序中经超过实际信号量访谈变量时,出现极度情形,能顺遂是不是功率信号量,那样线程就不会一向攻克二个功率信号量。当finally代码块实践完后,函数就重返了。

为了让全数机制运作起来,编写翻译器必需生成一些卓殊轮代理公司码,而系统也必得实践一些特别职业,所以理应在写代码的时候防止再try代码块中应用return语句,因为对应用程序质量有震慑,对于简易demo难题非常小,对于要长日子不间断运维的次第依然悠着点好,下文子禽提到一个重大字**__leave**驷比不上舌字,它能够扶植大家发掘存一些进展费用的代码。

一条好的阅世准则:毫不再结束管理程序中富含让try块提前退出的讲话,那表示从try块和finally块中移除return,continue,break,goto等说话,把那一个言辞放在终止管理程序以外。这样做的利润就是不用去捕获哪些try块中的提前退出,进而时编译器生成的代码量最小,提升程序的运作成效和代码可读性。

####finally块的清理作用及对程序结构的影响

在编码的经过中供给参预供给检验,检验功能是不是中标施行,若成功的话实践那一个,不成功的话须求作一些万分的清理专门的学问,例如释放内部存款和储蓄器,关闭句柄等。如若检查实验不是不菲的话,倒没什么影响;但若又超级多检验,且软件中的逻辑关系相比较复杂时,往往要求化极大精力来促成烦琐的检查测试决断。结果就能够使程序看起来结构相比较复杂,大大减弱程序的可读性,何况程序的体积也不仅叠合。

对应以此标题本人是深有心得,曾在写通过COM调用WordVBA的时候,要求层层获取对象、剖断目的是还是不是收获成功、实行相关操作、再自由对象,一个流水生产线下来,本来风流洒脱两行的VBA代码,C++ 写出来将要好几十行(那还得看操作的是多少个怎么着指标)。

上边就来三个方法让大家看看,为何有些人心爱脚本语言而不赏识C++的案由吧。

为了更有逻辑,更有等级次序地操作 OfficeMicrosoft 把应用(Application)按逻辑功效区划为如下的树形结构

Application(WORD 为例,只列出一部分)
  Documents(所有的文档)
        Document(一个文档)
            ......
  Templates(所有模板)
        Template(一个模板)
            ......
  Windows(所有窗口)
        Window
        Selection
        View
        .....
  Selection(编辑对象)
        Font
        Style
        Range
        ......
  ......

唯有打探了逻辑档次,我们才具精确的垄断 Office。举个例子来说,假设给出生机勃勃个VBA语句是:

Application.ActiveDocument.SaveAs "c:abc.doc"

那正是说,我们就明白了,这些操作的经过是:

  1. 第一步,取得Application
  2. 第二步,从Application中取得ActiveDocument
  3. 第三步,调用 Document 的函数 SaveAs,参数是三个字符串型的文书名。

那只是三个最简易的的VBA代码了。来个稍稍复杂点的如下,在选中处,插入二个书签:

 ActiveDocument.Bookmarks.Add Range:=Selection.Range, Name:="iceman"

此间流程如下:

  1. 获取Application
  2. 获取ActiveDocument
  3. 获取Selection
  4. 获取Range
  5. 获取Bookmarks
  6. 调用方法Add

获取种种对象的时候都亟待看清,还亟需付出错误管理,对象释放等。在那就提交伪码吧,全写出来篇幅有一点长

#define RELEASE_OBJ(obj) if(obj != NULL) 
                        obj->Realse();

BOOL InsertBookmarInWord(const string& bookname)
{
    BOOL ret = FALSE;
    IDispatch* pDispApplication = NULL;
    IDispatch* pDispDocument = NULL;
    IDispatch* pDispSelection = NULL;
    IDispatch* pDispRange = NULL;
    IDispatch* pDispBookmarks = NULL;
    HRESULT hr = S_FALSE;

    hr = GetApplcaiton(..., &pDispApplication);
    if (!(SUCCEEDED(hr) || pDispApplication == NULL))
        return FALSE;

    hr = GetActiveDocument(..., &pDispDocument);
    if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
        RELEASE_OBJ(pDispApplication);
        return FALSE;
    }

    hr = GetActiveDocument(..., &pDispDocument);
    if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
        RELEASE_OBJ(pDispApplication);
        return FALSE;
    }

    hr = GetSelection(..., &pDispSelection);
    if (!(SUCCEEDED(hr) || pDispSelection == NULL)){
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        return FALSE;
    }

    hr = GetRange(..., &pDispRange);
    if (!(SUCCEEDED(hr) || pDispRange == NULL)){
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        return FALSE;
    }

    hr = GetBookmarks(..., &pDispBookmarks);
    if (!(SUCCEEDED(hr) || pDispBookmarks == NULL)){
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        RELEASE_OBJ(pDispRange);
        return FALSE;
    }

    hr = AddBookmark(...., bookname);
    if (!SUCCEEDED(hr)){
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        RELEASE_OBJ(pDispRange);
        RELEASE_OBJ(pDispBookmarks);
        return FALSE;
    }
    ret = TRUE;
    return ret;

这只是伪码,就算也得以通过goto减弱代码行,然则goto用得不佳就出错了,上边程序中稍不留神就goto到不应当获得地点了。

BOOL InsertBookmarInWord2(const string& bookname)
{
    BOOL ret = FALSE;
    IDispatch* pDispApplication = NULL;
    IDispatch* pDispDocument = NULL;
    IDispatch* pDispSelection = NULL;
    IDispatch* pDispRange = NULL;
    IDispatch* pDispBookmarks = NULL;
    HRESULT hr = S_FALSE;

    hr = GetApplcaiton(..., &pDispApplication);
    if (!(SUCCEEDED(hr) || pDispApplication == NULL))
        goto exit6;

    hr = GetActiveDocument(..., &pDispDocument);
    if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
        goto exit5;
    }

    hr = GetActiveDocument(..., &pDispDocument);
    if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
        goto exit4;
    }

    hr = GetSelection(..., &pDispSelection);
    if (!(SUCCEEDED(hr) || pDispSelection == NULL)){
        goto exit4;
    }

    hr = GetRange(..., &pDispRange);
    if (!(SUCCEEDED(hr) || pDispRange == NULL)){
        goto exit3;
    }

    hr = GetBookmarks(..., &pDispBookmarks);
    if (!(SUCCEEDED(hr) || pDispBookmarks == NULL)){
        got exit2;
    }

    hr = AddBookmark(...., bookname);
    if (!SUCCEEDED(hr)){
        goto exit1;
    }

    ret = TRUE;
exit1:
    RELEASE_OBJ(pDispApplication);
exit2:
    RELEASE_OBJ(pDispDocument);
exit3:
    RELEASE_OBJ(pDispSelection);
exit4:
    RELEASE_OBJ(pDispRange);
exit5:
    RELEASE_OBJ(pDispBookmarks);
exit6:
    return ret;

此地照旧通过SEH的甘休管理程序来重新该格局,那样是还是不是更清晰明了。

BOOL InsertBookmarInWord3(const string& bookname)
{
    BOOL ret = FALSE;
    IDispatch* pDispApplication = NULL;
    IDispatch* pDispDocument = NULL;
    IDispatch* pDispSelection = NULL;
    IDispatch* pDispRange = NULL;
    IDispatch* pDispBookmarks = NULL;
    HRESULT hr = S_FALSE;

    __try{
        hr = GetApplcaiton(..., &pDispApplication);
        if (!(SUCCEEDED(hr) || pDispApplication == NULL))
            return FALSE;

        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
            return FALSE;
        }

        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
            return FALSE;
        }

        hr = GetSelection(..., &pDispSelection);
        if (!(SUCCEEDED(hr) || pDispSelection == NULL)){
            return FALSE;
        }

        hr = GetRange(..., &pDispRange);
        if (!(SUCCEEDED(hr) || pDispRange == NULL)){
            return FALSE;
        }

        hr = GetBookmarks(..., &pDispBookmarks);
        if (!(SUCCEEDED(hr) || pDispBookmarks == NULL)){
            return FALSE;
        }

        hr = AddBookmark(...., bookname);
        if (!SUCCEEDED(hr)){
            return FALSE;
        }

        ret = TRUE;
    }
    __finally{
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        RELEASE_OBJ(pDispRange);
        RELEASE_OBJ(pDispBookmarks);
    }
    return ret;

这个函数的作用是平等的。能够观看在InsertBookmarInWord中的清理函数(RELEASE_OBJ卡塔尔四处都是,而InsertBookmarInWord3中的清理函数则整个聚齐在finally块,假如在翻阅代码时只需看try块的源委就能够领会程序流程。那多个函数本人都异常的小,能够细细体会下这五个函数的分别。

关键字 __leave

try块中央银行使**__leave重大字会使程序跳转到try块的末梢,进而自然的步入finally块。
对此上例中的InsertBookmarInWord3try块中的return完全能够用
__leave** 来替换。两个的分裂是用return会引起try太早退出系统会开展一些进展而扩充系统开垦,若采取**__leave**就能理所必然退出try块,开销就小的多。

BOOL InsertBookmarInWord4(const string& bookname)
{
    BOOL ret = FALSE;
    IDispatch* pDispApplication = NULL;
    IDispatch* pDispDocument = NULL;
    IDispatch* pDispSelection = NULL;
    IDispatch* pDispRange = NULL;
    IDispatch* pDispBookmarks = NULL;
    HRESULT hr = S_FALSE;

    __try{
        hr = GetApplcaiton(..., &pDispApplication);
        if (!(SUCCEEDED(hr) || pDispApplication == NULL))
            __leave;

        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL))
            __leave;

        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL))
            __leave;

        hr = GetSelection(..., &pDispSelection);
        if (!(SUCCEEDED(hr) || pDispSelection == NULL))
            __leave;

        hr = GetRange(..., &pDispRange);
        if (!(SUCCEEDED(hr) || pDispRange == NULL))
            __leave;

        hr = GetBookmarks(..., &pDispBookmarks);
        if (!(SUCCEEDED(hr) || pDispBookmarks == NULL))
            __leave;

        hr = AddBookmark(...., bookname);
        if (!SUCCEEDED(hr))
            __leave;

        ret = TRUE;
    }
    __finally{
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        RELEASE_OBJ(pDispRange);
        RELEASE_OBJ(pDispBookmarks);
    }
    return ret;
}

那些管理程序

软件极度是大家都不甘于看看的,但是错误依然平常常有,比如CPU捕获雷同违法内存访谈和除0那样的标题,风度翩翩旦考察到这种张冠李戴,就抛出有关分外,操作系统会给大家应用程序叁个查看相当类型的机缘,而且运路程序本身管理那一个那三个。非凡管理程序结构代码如下

  __try {
      // Guarded body
    }
    __except ( exception filter ) {
      // exception handler
    }

注意关键字**__except**,任何try块,前面总得更叁个finally代码块或许except代码块,但是try后又不可能何况有finallyexcept块,也无法何况有多个finnalyexcept块,可是能够并行嵌套使用

相当管理为主流程

int Func3()
{
    cout << __FUNCTION__ << endl;
    int nTemp = 0;
    __try{
        nTemp = 22;
        cout << "nTemp = " << nTemp << endl;
    }
    __except (EXCEPTION_EXECUTE_HANDLER){
        cout << "except nTemp = " << nTemp << endl;
    }
    return nTemp;
}

int Func4()
{
    cout << __FUNCTION__ << endl;
    int nTemp = 0;
    __try{
        nTemp = 22/nTemp;
        cout << "nTemp = " << nTemp << endl;
    }
    __except (EXCEPTION_EXECUTE_HANDLER){
        cout << "except nTemp = " << nTemp << endl;
    }
    return nTemp;
}

结果如下:

Func3
nTemp = 22  //正常执行

Func4
except nTemp = 0 //捕获异常,

Func3try块只是贰个简单易行操作,故不会促成非凡,所以except块中代码不会被实践,Func4try块视图用22除0,导致CPU捕获这么些事件,并抛出,系统定点到except块,对该非常举行拍卖,该处有个要命过滤表明式,系统中有三该定义(定义在Windows的Excpt.h中):

1. EXCEPTION_EXECUTE_HANDLER:
    我知道这个异常了,我已经写了代码来处理它,让这些代码执行吧,程序跳转到except块中执行并退出
2. EXCEPTION_CONTINUE_SERCH
    继续上层搜索处理except代码块,并调用对应的异常过滤程序
3. EXCEPTION_CONTINUE_EXECUTION
    返回到出现异常的地方重新执行那条CPU指令本身

面是三种为主的采用情势:

  • 艺术黄金时代:直接行使过滤器的多少个重临值之黄金时代
__try {
   ……
}
__except ( EXCEPTION_EXECUTE_HANDLER ) {
   ……
}
  • 办法二:自定义过滤器
__try {
   ……
}
__except ( MyFilter( GetExceptionCode() ) )
{
   ……
}

LONG MyFilter ( DWORD dwExceptionCode )
{
  if ( dwExceptionCode == EXCEPTION_ACCESS_VIOLATION )
    return EXCEPTION_EXECUTE_HANDLER ;
  else
    return EXCEPTION_CONTINUE_SEARCH ;
}

.NET4.0中捕获SEH异常

在.NET 4.0随后,CLCR-V将会有别于出风流倜傥部分充裕(都以SEH卓殊卡塔尔,将那么些分外标志为破坏性极度(Corrupted State Exception卡塔 尔(阿拉伯语:قطر‎。针对这一个特别,CL哈弗的catch块不会捕捉这个非常,一下代码也一直不章程捕捉到这一个格外。

try{
    //....
}
catch(Exception ex)
{
    Console.WriteLine(ex.ToString());
}

因为并非全数人都供给捕获这一个可怜,借让你的前后相继是在4.0底下编写翻译并运营,而你又想在.NET程序里捕捉到SEH十分的话,有八个方案得以尝尝:

  • 在托管程序的.config文件里,启用legacyCorruptedStateExceptionsPolicy那些特性,即简化的.config文件相同上面包车型客车文件:
App.Config

<?xml version="1.0"?>
<configuration>
 <startup>
   <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
 </startup>
    <runtime>
      <legacyCorruptedStateExceptionsPolicy enabled="true" />
    </runtime>
</configuration>

本条设置告诉CL昂Cora 4.0,整个.NET程序都要动用老的至极捕捉机制。

  • 在须要捕捉破坏性十分的函数外面加贰个HandleProcessCorruptedStateExceptions属性,这么些天性只调控一个函数,对托管程序的其余函数未有影响,比方:
[HandleProcessCorruptedStateExceptions]
try{
    //....
}
catch(Exception ex)
{
    Console.WriteLine(ex.ToString());
}

本文由六和开奖现场发布于网站首页,转载请注明出处:Windows结构化异常处理浅析

上一篇:没有了 下一篇:没有了
猜你喜欢
热门排行
精彩图文