Windows结构化卓殊处理浅析

近日直接被一个题材所苦恼,就是写出来的主次老是出新无故崩溃,有的地点和谐精通或者有题目,但是一些地方又历来没办法知道有什么问题。更苦逼的作业是,大家的顺序是急需7×24劳动客户,即使不需要实时精准零差错,可是总不可能冒出断线丢失数据状态。故刚好通过拍卖该问题,找到了一些解决方案,怎么捕获做客非法内存地址或者0除以一个数。从而就遇上了这一个结构化非凡处理,今就大概做个介绍认识下,方便我们碰着相关问题后,首先知道问题原因,再就是何等解决。废话不多说,下面进入正题。

哪些是结构化分外处理

结构化异常处理(structured exception
handling
,下文简称:SEH),是作为一种系统编制引入到操作系统中的,本身与语言无关。在我们友好的次序中行使SEH可以让我们集中精力开发重点效率,而把程序中所可能现身的不得了举办联合的处理,使程序显得尤其简洁且扩充可读性。

使用SHE,并不意味着可以完全忽略代码中或许现身的一无是处,不过大家得以将软件工作流程和软件十分状况处理进行分离,先集中精力干重要且迫切的活,再来处理那个也许会碰到各样的荒唐的机要不紧急的问题(不紧急,但相对首要)

当在先后中使用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从此,CLR将会有别出一些优异(都是SEH万分),将这么些特别标识为破坏性分外(Corrupted
State
Exception)。针对这个非常,CLR的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>

这多少个装置告诉CLR 4.0,整个.NET程序都要利用老的百般捕捉机制。

  • 在需要捕捉破坏性很是的函数外面加一个HandleProcessCorruptedStateExceptions属性,这多少个特性只控制一个函数,对托管程序的另外函数没有影响,例如:

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

相关文章