ITPub博客

首页 > 应用开发 > IT综合 > C与C++中的异常处理17 (转)

C与C++中的异常处理17 (转)

原创 IT综合 作者:worldblog 时间:2007-12-12 15:18:17 0 删除 编辑
C与C++中的异常处理17 (转)[@more@]

1.  C++异常和Visual C++ SEH的混合使用XML:namespace prefix = o ns = "urn:schemas-microsoft-com:Office:office" />

  我在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

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

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

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中勾画的,头文件定义了一个异常类层次,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

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

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

 

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  总结束

(略)

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10752043/viewspace-992224/,如需转载,请注明出处,否则将追究法律责任。

下一篇: CWinApp::CWinApp (转)
请登录后发表评论 登录
全部评论
  • 博文量
    6241
  • 访问量
    2404592