學習軟件工程時有句老話,“不存在沒有錯誤的程序”,十余年歷練,各種錯誤如影隨行一如鬼魅,由此看真理是不需要檢驗的,你只需要信仰就可以了。
Windows的程序員對于上圖應用程序崩潰的對話框應該再熟悉不過,這是所謂的結(jié)構(gòu)性異常的默認處理方式。空指針讀寫、數(shù)組越界、除零錯誤、溢出等嚴重錯誤,Windows都將產(chǎn)生結(jié)構(gòu)性異常。由于MFC的框架并不提供結(jié)構(gòu)性異常的封裝,所以無論使用SDK或者MFC的程序員都必須面對結(jié)構(gòu)性異常。針對錯誤,當然你要做的大抵三個層次:
(1)、捕捉錯誤,定位錯誤,并事后糾正錯誤。
(2)、運行中如非致命性錯誤,忽略錯誤,維持程序帶病運行。
(3)、當然你足夠水平,最好是補救錯誤,維持程序正確運行。
我日常工作寫的最多的是24小時運行的后臺值守程序,所以持續(xù)運行很關鍵,但也很困難。目前我只做前兩點,有時嘗試做第三點,所以寫了段代碼在捕捉結(jié)構(gòu)性錯誤的同時,利用dbghelp或imagehlp.dll的調(diào)試函數(shù)產(chǎn)生內(nèi)存Dump文件,并產(chǎn)生文本文件捕捉一些相關信息用于定位錯誤。并將結(jié)構(gòu)性異常轉(zhuǎn)換為C++異常,以期維持程序繼續(xù)運行。對于可預見的關鍵代碼段做一些保護性工作,以期能夠補救錯誤。
以往的做法將結(jié)構(gòu)性異常處理代碼在各個項目拷來拷去再適當修改,時間久了、項目多了也覺得不好。去年打包了一下,有改動,所有的項目可以一起升級,規(guī)范一些。今天略作整理、精簡,希望和有需要的朋友分享。技術上是簡單的,用起來也挺簡單,可以解決大家一些敲鍵盤的時間。專業(yè)度高的、熟悉結(jié)構(gòu)性異常的朋友可以跳過,不用浪費時間,沒接觸過的朋友可以看看代碼,代碼是最能說明問題的,應該有些益處。至于結(jié)構(gòu)性異常的知識俺就不介紹了,網(wǎng)絡上多如牛毛。
之所以貼出來,就因為使用簡單,舉例說明如下,一般應用(seh.h 下載鏈接 SEH頭文件):
#include "seh.h"
void Call1(void *p1, void *p);
void Call2(void *p1, void *p);
void Call3(void *p1, void *p);
void Call1(void *p1, void *p)
{
Call2(p, (void*)0x11223344);
}
void Call2(void *p1, void *p )
{
Call3(p, (void*)0x55667788);
}
void Call3(void *p1, void *p )
{
*((char*)p1) = 'a';//產(chǎn)生結(jié)構(gòu)性異常
}
int main(int argc, char *argv[])
{
SEH<>::DoCatch();//頂層捕捉結(jié)構(gòu)性異常,捕捉到后產(chǎn)生報告文件并退出,報告文件存于.\seh目錄下
Call1((void*)0xaabbccdd, (void*)0xeeff0011);
printf("\n seh Call exit
\n");
return 0;
}
將結(jié)構(gòu)性異常轉(zhuǎn)換為C++標準異常:
int main(int argc, char *argv[])
{
SEH<>::DoCatch();//頂層捕捉 捕捉漏網(wǎng)之魚
SEH<>::DoCatchCpp();//將當前線程的結(jié)構(gòu)性異常轉(zhuǎn)換為C++異常
try
{
Call1((void*)0xaabbccdd, (void*)0xeeff0011);
}
catch (exception& e)
{
printf("exception:%s\n", e.what());
}
return 0;
}
有啟用捕捉功能當然也要有停用功能:
SEH<>::DoCatch(false);
SEH<>::DoCatchCpp(false);
//當然這個功能一般用不上,DoCatchCpp將占用一個線程局部存儲空間(TLS)
用戶自行定制部分。封裝一定要注意將變化部分暴露出來。結(jié)構(gòu)性異常處理兩個關鍵事項,一個是生產(chǎn)什么樣的報告文件,二是轉(zhuǎn)換為哪個標準的C++異常,所以我在這里用兩個模板參數(shù)提供變化策略:
template<class ReportType = SehReport, class ThrowType = SehThrowStd>
class SEH ;
簡單定制,替換模板參數(shù)即可,復雜的就需要擴展編寫新的類。
//一下策略,將不產(chǎn)生報告文件,捕捉到就行異常將拋出MFC異常
SEH<SehNvlReport, SehThrowMfc>::DoCatchCpp();
//自定義報告類,必須實現(xiàn)void Report(_EXCEPTION_POINTERS* pException)
class MySehReport : public SehReport
{
public:
void Report(_EXCEPTION_POINTERS* pException)
{
system("ipconfig -a > ip.txt");//保存出錯程序當前運行機器的IP配置
}
};
//自定義異常拋出 必須實現(xiàn)static void Throw(LPCTSTR pMsg)
class MyThrowSeh
{
public:
static void Throw(LPCTSTR pMsg)
{
throw pMsg;
}
};
//使用
SEH<MySehReport, MyThrowSeh>::DoCatchCpp();
try
{
Call1((void*)0xaabbccdd, (void*)0xeeff0011);
}
catch(LPCTSTR pMsg)
{
printf("LPCTSTR:%s\n", pMsg);
}
其他注意事項:
如果需將結(jié)構(gòu)性異常轉(zhuǎn)換為C++異常,應在編譯參數(shù)中添加/EHa,這樣做是為了避免VC優(yōu)化器當檢測不到拋出異常語句,會將捕捉語句優(yōu)化去除,比如
try
{
//
如果不包含throw new CException()
}
catch(CException* E)
{
//
本語句將被優(yōu)化忽略
}
多線程程序?qū)⒔Y(jié)構(gòu)性異常轉(zhuǎn)換為C++異常,必須在每一個線程入口點加入SEH<>::DoCatchCpp();
而SEH<>::DoCatch();整個程序只需一個 。