作者:小马
刚开始准备写关于异常的文章时,也没想到会一连写三篇.
本篇是关于如何把windows的SE异常与c++的try/catch整合在一起.
要实现整合, 要用_set_se_translator, 我并不是简单的直接用这个函数, 因为毕竟c++是一种面向对象语言, _set_se_translator是一个标准的c函数, 为了不破坏面向对象的思想, 我写了一类把_set_se_translator封装起来, 希望在实际的开发中, 可以类似这样应用(我把这个类的命名为CPony_SeException):
try { //各种处理...... } catch(CPony_SeException &e1) { cout << "windows se exception happened" <<endl; } catch(runtime_error &e2) { cout << "c++ run-time error happened" <<endl; } catch(...) { cout << "other c++ exception happened" <<endl; }
先简单说一下_set_se_translator的原理.
首先我们需要自己写一个函数, 这个函数的参数和返回值都必须符合下面的形式:
typedef void (*_se_translator_function)(unsigned int, struct _EXCEPTION_POINTERS* );
_set_se_translator是一个CRT,跟系统无关. 它提供了一种机制,可以把windows的seh异常转化为标准的c++异常(try/catch), 过程如下:当一个se异常产生时, 它会调用我们写的那个函数,而这个函数一般只做一个操作, 就是throw一个c++的异常.
另外, 补充两点,
1 _set_se_translator是基于线程的, 对于多线程的应用,每一个线程都要转换一次.
2 用_set_se_translator必须打开/eha(前面文章介绍过如何在vs2005中打开这个开关)
我写的异常类来如下:
class CPony_SeException :public exception { public: CPony_SeException(CPony_SeException const& copee); CPony_SeException(unsigned int exceptionID, EXCEPTION_POINTERS* exceptionPointers); virtual ~CPony_SeException(); unsigned int GetExceptionID() const; EXCEPTION_POINTERS* GetExceptionPointers() const; virtual char const * what() const; private: static void sehTranslator(unsigned int exceptionID, EXCEPTION_POINTERS* exceptionPointers); unsigned int m_exceptionID; EXCEPTION_POINTERS* m_exceptionPointers; static _se_translator_function m_oldSEHTranslator; };
比较简单的一个类, 解释几个地方:
1 exception是c++的异常基类, what是它的一个虚函数, c++中所有的异常类型都是派生自这个类, 更多详细的东西,可以看<<c++ primer>>.
2 EXCEPTION_POINTERS里记录了SE异常的详细内容, 比如异常发生的地址, 异常类型等, 我对外引出一个接口(GetExceptionPointers), 这样在异常发生时, 可以获取自己想要的信息.
3 GetExceptionID可以返回异常类型码, windows定义了以下几种:
#define WAIT_IO_COMPLETION STATUS_USER_APC #define STILL_ACTIVE STATUS_PENDING #define EXCEPTION_ACCESS_VIOLATION STATUS_ACCESS_VIOLATION #define EXCEPTION_DATATYPE_MISALIGNMENT STATUS_DATATYPE_MISALIGNMENT #define EXCEPTION_BREAKPOINT STATUS_BREAKPOINT #define EXCEPTION_SINGLE_STEP STATUS_SINGLE_STEP #define EXCEPTION_ARRAY_BOUNDS_EXCEEDED STATUS_ARRAY_BOUNDS_EXCEEDED #define EXCEPTION_FLT_DENORMAL_OPERAND STATUS_FLOAT_DENORMAL_OPERAND #define EXCEPTION_FLT_DIVIDE_BY_ZERO STATUS_FLOAT_DIVIDE_BY_ZERO #define EXCEPTION_FLT_INEXACT_RESULT STATUS_FLOAT_INEXACT_RESULT #define EXCEPTION_FLT_INVALID_OPERATION STATUS_FLOAT_INVALID_OPERATION #define EXCEPTION_FLT_OVERFLOW STATUS_FLOAT_OVERFLOW #define EXCEPTION_FLT_STACK_CHECK STATUS_FLOAT_STACK_CHECK #define EXCEPTION_FLT_UNDERFLOW STATUS_FLOAT_UNDERFLOW #define EXCEPTION_INT_DIVIDE_BY_ZERO STATUS_INTEGER_DIVIDE_BY_ZERO #define EXCEPTION_INT_OVERFLOW STATUS_INTEGER_OVERFLOW #define EXCEPTION_PRIV_INSTRUCTION STATUS_PRIVILEGED_INSTRUCTION #define EXCEPTION_IN_PAGE_ERROR STATUS_IN_PAGE_ERROR #define EXCEPTION_ILLEGAL_INSTRUCTION STATUS_ILLEGAL_INSTRUCTION #define EXCEPTION_NONCONTINUABLE_EXCEPTION STATUS_NONCONTINUABLE_EXCEPTION #define EXCEPTION_STACK_OVERFLOW STATUS_STACK_OVERFLOW #define EXCEPTION_INVALID_DISPOSITION STATUS_INVALID_DISPOSITION #define EXCEPTION_GUARD_PAGE STATUS_GUARD_PAGE_VIOLATION #define EXCEPTION_INVALID_HANDLE STATUS_INVALID_HANDLE #define EXCEPTION_POSSIBLE_DEADLOCK STATUS_POSSIBLE_DEADLOCK
4 sehTranslator就是我们要自己实现的异常转换函数,注意这里把它定义成static,因为它是基于类的, 而不是某个对象.
5 m_oldSEHTranslator是一个函数指针.
下面到.cpp文件, 是类CPony_SeException的实现
#define CASE(nSeCode,szDescription) case nSeCode: \ sprintf(szDescription, "%s", #nSeCode); \ break;
这里定义一个宏, 把整型类型码转换成字符串, 下面的what函数会用到, 方便输出.
_se_translator_function CPony_SeException::m_oldSEHTranslator = _set_se_translator(CPony_SeException::sehTranslator);
_set_se_translator本身有个返回值, 是上一个被它注册的函数指针, 这里保存这个指针,以防会用到. 其实这里面还有一个重要的学问. 仔细看一下, 这一句我是在其它.cpp文件里调用的, sehTranslator是私有函数, 我用_set_se_translator直接访问, 为什么没有报错呢? 原因就是等号左边起了作用, <<c++ primer>>有说, 一旦成员名出现, static的定义就在类的作用域里了. 所以我可以访问它的私有成员. 可以试一下, 如果把=号左边的去掉,直接写:
_set_se_translator(CPony_SeException::sehTranslator);
程序会报错的.
CPony_SeException::CPony_SeException(CPony_SeException const& exceptionCopee) :m_exceptionID(exceptionCopee.m_exceptionID), m_exceptionPointers(exceptionCopee.m_exceptionPointers) { } CPony_SeException::CPony_SeException(unsigned int exceptionID, EXCEPTION_POINTERS* exceptionPointers) : m_exceptionID(exceptionID), m_exceptionPointers(exceptionPointers) { } CPony_SeException::~CPony_SeException() { } unsigned int CPony_SeException::GetExceptionID() const { return m_exceptionID; } EXCEPTION_POINTERS* CPony_SeException::GetExceptionPointers() const { return m_exceptionPointers; }
上面这些没什么好解释的.
char const * CPony_SeException::what() const { char strError[100] = {0}; static char strWhat[200] = {0}; strcat(strWhat, "windows SE exception: "); switch (m_exceptionID) { CASE(EXCEPTION_ACCESS_VIOLATION,strError); CASE(EXCEPTION_DATATYPE_MISALIGNMENT,strError); CASE(EXCEPTION_BREAKPOINT,strError); CASE(EXCEPTION_SINGLE_STEP,strError); CASE(EXCEPTION_ARRAY_BOUNDS_EXCEEDED,strError); CASE(EXCEPTION_FLT_DENORMAL_OPERAND,strError); CASE(EXCEPTION_FLT_DIVIDE_BY_ZERO,strError); CASE(EXCEPTION_FLT_INEXACT_RESULT,strError); CASE(EXCEPTION_FLT_INVALID_OPERATION,strError); CASE(EXCEPTION_FLT_OVERFLOW,strError); CASE(EXCEPTION_FLT_STACK_CHECK,strError); CASE(EXCEPTION_FLT_UNDERFLOW,strError); CASE(EXCEPTION_INT_DIVIDE_BY_ZERO,strError); CASE(EXCEPTION_INT_OVERFLOW,strError); CASE(EXCEPTION_PRIV_INSTRUCTION,strError); CASE(EXCEPTION_IN_PAGE_ERROR,strError); CASE(EXCEPTION_ILLEGAL_INSTRUCTION,strError); CASE(EXCEPTION_NONCONTINUABLE_EXCEPTION,strError); CASE(EXCEPTION_STACK_OVERFLOW,strError); CASE(EXCEPTION_INVALID_DISPOSITION,strError); CASE(EXCEPTION_GUARD_PAGE,strError); CASE(EXCEPTION_INVALID_HANDLE,strError); default: strcpy(strError, "Unknown exception."); break; } strcat(strWhat, strError); return strWhat; }
重定义的what函数, 用到了上面的宏, 这样可以以字符串的形式返回SE的异常类型码, 非常方便.
void CPony_SeException::sehTranslator(unsigned int exceptionID, EXCEPTION_POINTERS* exceptionPointers) { throw CPony_SeException(exceptionID, exceptionPointers); }
这个就是异常转换函数, 它最好除了throw一个异常外, 什么也不要做.
好了, 类写完了, 我可以在我的main函数里这样用:
int main() { int *p = NULL; try { *p = 0;//非法内存访问 } //避免类对象的复制,这里用引用. catch(CPony_SeException &e) { cout << e.what() <<endl; } return 0; }
输出:
windows SE exception: EXCEPTION_ACCESS_VIOLATION
注意:一般要保证CPony_SeException类本身不能抛出SE异常, 如果出现了,肯定无法被它自己捕捉,也是会报系统错误的.
源码下载地址:
https://github.com/pony-maggie/LKE_lke2600_CardReader