轉載請註明出處
作者:小馬
剛開始準備寫關於異常的文章時,也沒想到會一連寫三篇.
本篇就是接著寫上篇寫的, 關於如何把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.這個過程可以認為是用_set_se_translator註冊這個函數.
_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異常, 如果出現了,肯定無法被它自己捕捉,也是會報系統錯誤的.