分享
 
 
 

C与C++中的异常处理17

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

1. C++异常和Visual C++ SEH的混合使用

我在Part2介绍了Structured Exception Handling(简称SEH)。在那时我就说过,SEH是window及其平台上的编译器专有的。它不是定义在ISO C++标准中的,使用它的程序将不能跨编译器移植。因为我注重于标准兼容和可移植性,所以我对将windows专有的SEH映射为ISO标准C++的exception handing(简称EH)很感兴趣。

同时,我不是SEH的专家。对它的了解绝大部分来自于本专栏前面的研究。当我考虑混合使用SEH与EH时,我猜想解决方法应该是困难的和不是显而易见的。这是它花了我两个星期的原因:我预料到需要额外的时间来研究和试验。

很高兴,我完全错了。我不知道的是Visual C++运行期库直接支持了绝大部分我所想要的东西。不用创造新的方法了,我可以展示你Visual C++已经支持了的东西,以及改造为所需要的东西的方法。基于这个目的,我将研究同一个例子的四个不同版本。

1.1 Version 1:定义一个转换函数

捆绑SEH和EH的方法分两步:

l 一个用户自定义的转换函数来捕获SEH的异常并将它映射为C++的异常。

l 一个Visual C++运行期库函数来安装这个转换函数

用户自定义的转换函数必要有如下形式:

void my_translator(unsigned code, EXCEPTION_POINTERS *info);

转换函数接受一个SEH异常(通过给定的异常code和info来定义的)。然后抛出一个C++异常,以此将传入的SEH异常映射为向外传的C++异常。这个C++异常将出现在原来的SEH异常发生点上并向外传播。

这个机制非常象std::set_terminate()和std::set_unexpected()。要安装转换函数,要调用Visual C++库函数_set_se_translator()。这个函数申明在头文件eh.h中:

typedef void (*_se_translator_function)(unsigned, EXCEPTION_POINTERS *);

_se_translator_function _set_se_translator(_se_translator_function);

它接受一个指向新转换函数的指针,返回上次安装的指针。一旦安装了一个转换函数,前一次的就丢失了;任何时候只有一个转换函数有效。(在多线程程序中,每个线程有一个独立的转换函数。)

如果还没安装过转换函数,第一次调用_set_se_translator()返回值可能是(也可能不是)NULL。也就是说,不能不分青红皂白就通过其返回的指针调用函数。很有趣的,如果返回值是NULL,而你又通过此NULL调用函数,将产生一个SEH异常,并且进入你刚刚安装的转换函数。

一个简单的例子:

#include <iostream>

using namespace std;

int main()

{

try

{

*(int *) 0 = 0; // generate Structured Exception

}

catch (unsigned exception)

{

cout << "caught C++ exception " << hex << exception << endl;

}

return 0;

}

运行它的话,这个控制台程序将导致如此一个windows messagebox:

它是由于一个未被捕获的SEH异常传递到程序外面造成的。

现在,增加一个异常转换函数,并将Visual C++运行库设为使用这个转换函数:

#include <iostream>

using namespace std;

#include "windows.h"

static void my_translator(unsigned code, EXCEPTION_POINTERS *)

{

throw code;

}

int main()

{

_set_se_translator(my_translator);

try

{

*(int *) 0 = 0; // generate Structured Exception

}

catch (unsigned exception)

{

cout << "caught C++ exception " << hex << exception << endl;

}

return 0;

}

再运行程序。现在将看到:

caught C++ exception c0000005

my_translator()截获了SEH异常,并转换为C++异常,其类型为unsigned,内容为SEH异常码(本例中为C0000005h,它是一个非法读取错误)。因为这个C++异常出现在原来的SEH异常发生点,也就说在try块中,所以被try块的异常处理函数捕获了。

1.2 Version 2:定义一个转换对象

上面的例子非常简单,将每个SEH异常转换为一个unsigned值。实际上,你可能需要一个比较复杂的异常对象:

#include <iostream>

using namespace std;

//#include "windows.h"

#include "structured_exception.h"

/*static void my_translator(unsigned code, EXCEPTION_POINTERS *)

{

throw code;

}*/

int main()

{

//_set_se_translator(my_translator);

structured_exception::install();

try

{

*(int *) 0 = 0; // generate Structured Exception

}

catch (structured_exception const &exception)

{

cout << "caught C++ exception " << hex << exception.what()

<< " thrown from " << exception.where() << endl;

}

return 0;

}

这个例子抛出了一个用户自定义类型(structured_exception)的C++异常。为了让这个例子更具实际意义,也更方便阅读,我将structured_exception的申明放到了头文件structured_exception.h中:

#if !defined INC_structured_exception_

#define INC_structured_exception_

#include "windows.h"

class structured_exception

{

public:

structured_exception(EXCEPTION_POINTERS const &) throw();

static void install() throw();

unsigned what() const throw();

void const *where() const throw();

private:

void const *address_;

unsigned code_;

};

#endif // !defined INC_structured_exception_

其实现文件为:

#include "structured_exception.h"

#include "eh.h"

//

// ::

//

static void my_translator(unsigned, EXCEPTION_POINTERS *info)

{

throw structured_exception(*info);

}

//

// structured_exception::

//

structured_exception::structured_exception

(EXCEPTION_POINTERS const &info) throw()

{

EXCEPTION_RECORD const &exception = *(info.ExceptionRecord);

address_ = exception.ExceptionAddress;

code_ = exception.ExceptionCode;

}

void structured_exception::install() throw()

{

_set_se_translator(my_translator);

}

unsigned structured_exception::what() const throw()

{

return code_;

}

void const *structured_exception::where() const throw()

{

return address_;

}

这些函数的意义是:

l my_translator()是异常转换函数。我把它从main文件中移到这儿。于是,main文件不再需要包含windows.h了。

l install()将运行器库的全局转换函数设置为my_translator()。

l structured_exception的构造函数接收并解析SEH异常的信息。

l what()返回SEH异常的异常码。

l where()返回SEH异常发生的地点。注意,where()的返回类型是void const *,虽然C++标准不同意将代码地址转换为void指针。我只是重复了Micorsoft的用法,因为Visual C++运行库将地址存在了SEH异常的EXCEPTION_RECORD的一个void *成员中了。

编译并链接这三个文件。运行结果是:

caught C++ exception c0000005 thrown from 0040181D

(其中的代码地址值在你的系统上可能有所不同。)

1.3 Version 3:模仿C++标准运行库

在my_translator()中,所有的SEH异常映射为同样的structured_exception类型。这使得异常容易被捕获,因为它们匹配于我们的唯一的异常处理函数:

catch (structured_exception const &exception)

虽然捕获了异常,但我们没有办法事先知道异常的类型。唯一能做的是运行期查询,调用这个异常的what()成员:

catch (structured_exception const &exception)

{

switch (exception.what())

{

case EXCEPTION_ACCESS_VIOLATION:

// ...

case EXCEPTION_INT_DIVIDE_BY_ZERO:

// ...

case EXCEPTION_STACK_OVERFLOW:

// ...

// ...

}

这样的查询需要windows.h中的信息,以知道最初的SEH异常码的含意。这样的需求违背了structured_exception的抽象原则。此外,switch语句也经常违背了多态的原则。从用户代码的角度看,你通常应该用继承和模板来实现它。

C++标准运行库在这方面提供了一些指导。如我在Part3中勾画的,头文件<stdexcept>定义了一个异常类层次,std::exception是根结点。这个根类定义了虚成员what(),它返回一个编译器自定义的NTBS(C++标准中是“以NULL结束的字符串”)。每个继承类指定自己的what()的返回值。虽然C++标准没有规定这些值的内容,但我相信标准委员会打算用这个字符串来描述异常的类型或含意的。

根据这种精神,standard_exception的申明是:

#if !defined INC_structured_exception_

#define INC_structured_exception_

#include "eh.h"

#include "windows.h"

class structured_exception

{

public:

structured_exception(EXCEPTION_POINTERS const &) throw();

static void install() throw();

virtual char const *what() const throw();

void const *where() const throw();

private:

void const *address_;

//unsigned code_;

};

class access_violation : public structured_exception

{

public:

access_violation(EXCEPTION_POINTERS const &) throw();

virtual char const *what() const throw();

};

class divide_by_zero : public structured_exception

{

public:

divide_by_zero(EXCEPTION_POINTERS const &) throw();

virtual char const *what() const throw();

};

#endif // !defined INC_structured_exception_

实现是:

#include <exception>

using namespace std;

#include "structured_exception.h"

#include "windows.h"

//

// ::

//

static void my_translator(unsigned code, EXCEPTION_POINTERS *info)

{

switch (code)

{

case EXCEPTION_ACCESS_VIOLATION:

throw access_violation(*info);

break;

case EXCEPTION_INT_DIVIDE_BY_ZERO:

case EXCEPTION_FLT_DIVIDE_BY_ZERO:

throw divide_by_zero(*info);

break;

default:

throw structured_exception(*info);

break;

}

}

//

// structured_exception::

//

structured_exception::structured_exception

(EXCEPTION_POINTERS const &info) throw()

{

EXCEPTION_RECORD const &exception = *(info.ExceptionRecord);

address_ = exception.ExceptionAddress;

//code_ = exception.ExceptionCode;

}

void structured_exception::install() throw()

{

_set_se_translator(my_translator);

}

char const *structured_exception::what() const throw()

{

return "unspecified Structured Exception";

}

void const *structured_exception::where() const throw()

{

return address_;

}

//

// access_violation::

//

access_violation::access_violation

(EXCEPTION_POINTERS const &info) throw()

: structured_exception(info)

{

}

char const *access_violation::what() const throw()

{

return "access violation";

}

//

// divide_by_zero::

//

divide_by_zero::divide_by_zero

(EXCEPTION_POINTERS const &info) throw()

: structured_exception(info)

{

}

char const *divide_by_zero::what() const throw()

{

return "divide by zero";

}

注意:

l 那些本来在用户的异常处理函数中的switch语句,现在移到了my_translator()中。不再是将所有SEH异常映射为单个值(如version 1中)或单个类型的对象(version 2),现在的my_translator()将它们映射为多个类型的对象(取决于运行时的实际环境)。

l structured_exception成为了一个基类。我没有让它成为纯虚类,这是跟从了C++标准运行库的引导(std::exception是个实体类)。

l 我没有定义任何析构函数,因为编译器隐含提供的的析构函数对这些简单类足够了。如果我定义了析构函数,它们将需要定义为virtual。

l what()现在返回了一个用户友好的文本,取代了原来的SEH异常码。

l 因为我不再测试和显示这些代码,我去掉了数据成员code_。这使得structured_exception对象的大小减小了。(别太高兴:节省的空间又被新增的vptr指针抵销了,因为有了虚函数。)

l 因为模板方式更好,你应该放弃这种继承模式的。我将它留给你作为习题。

试一下新的方案,将main文件改为:

#include <iostream>

using namespace std;

#include "structured_exception.h"

int main()

{

structured_exception::install();

//

// discriminate exception by dynamic type

//

try

{

*(int *) 0 = 0; // generate Structured Exception

}

catch (structured_exception const &exception)

{

cout << "caught " << exception.what() << endl;

}

//

// discriminate exception by static type

//

try

{

static volatile int i = 0;

i = 1 / i; // generate Structured Exception

}

catch (access_violation const &)

{

cout << "caught access violation" << endl;

}

catch (divide_by_zero const &)

{

cout << "caught divide by zero" << endl;

}

catch (structured_exception const &)

{

cout << "caught unspecified Structured Exception" << endl;

}

return 0;

}

再次运行,结果是:

caught access violation

caught divide by zero

1.4 Version 4:匹配于C++标准运行库

我们所有的standard_exception继承类都提供公有的成员

virtual char const *what() const;

来识别异常的动态类型。我不是随便选取的函数名:所有的C++标准运行库中的std::exception继承类为同样的目的提供了同样的公有成员。并且,what()是每个继承类的唯一的多态函数。

你可能已经注意到:

#include <exception>

class structured_exception : public std::exception

{

public:

structured_exception(EXCEPTION_POINTERS const &info) throw();

static void install() throw();

virtual char const *what() const throw();

void const *where() const throw();

private:

void const *address_;

};

因为structured_exception现在也是一个std:exception,我们可以用一个异常处理函数来同时捕获这个异常族:

catch (std::exception const &exception)

并且用同样的多态函数来获取异常的类型:

catch (std::exception const &exception)

{

cout << "caught " << exception.what();

}

用这样的方案,SEH异常能够表现得与标准C++的固有行为一致。同时,我们仍然能够特殊对待structured_exceptions并访问它的特殊成员:

catch (structured_exception const &exception)

{

cout << "caught Structured Exception from " << exception.where();

}

当然,如果你想放弃没有出现在std::exception继承体系中的类成员,如where(),你完全可以不使用基类structured_exception,而是直接从std::exception继承出access_violation等类。例如:一个divide-by-zero异常表示了一个程序值域控制错误,也就是说是个逻辑错误。你所以想直接从std::logic_error甚至是std::out_of_range派生devide_by_zero类。

我建议你看一下C++标准subclause 19.1 (“Exception classes”)以更好地理解C++标准运行库的异常继承体系,以及如何更好地将你的自定义异常熔入此继承体系。

1.5 总结束(略)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有