我們知道,.Net的應用程序運行在.net framework虛擬機上,對於在運行時發生的錯誤,我們有try...catch可以捕捉,實在不濟,對於winform和asp.net 我們都有全局的事件可以訂閱處理。具體請參見:http://www.cnblogs.com/eaglet/archive/2009/02/17/1392191.html ,但是如果不通過.net Framework,而是直接通過P/Invoke調用操作系統的C/C++寫的庫,而不是通過.Net Framework的BCL調用操作系統功能,那么上面鏈接所說的方法便失去作用。譬如我有一個方法:
[DllImport("kernel32")] private static extern void RtlMoveMemory(IntPtr dst, ref byte src, Int32 len);
然后我們拖個按鈕,寫個事件:
private void button1_Click(object sender, EventArgs e) { byte a = 0; RtlMoveMemory(IntPtr.Zero, ref a, 1); }
程序會直接崩潰,用上述的方法無論如何都捕捉不到的。不信您可以試試。
那怎么辦?解鈴還須系鈴人。你直接調用操作系統API,難道操作系統沒有提供異常捕獲機制么?有的。現在我們引入幾個術語。
術語:
SEH: 結構化異常處理
VEH: 向量化異常處理
TopLevelEH:頂層異常處理
相信寫過C或者C++程序的同學可能不會陌生。現在我們介紹當用P/Invoke調用出現異常時,操作系統的異常處理順序流程:
1. 交給調試器(前提是進程必須被調試)
2. 執行VEH
3. 執行SEH
4. TopLevelEH(進程被調試時不會被執行)
5. 交給調試器(上面的異常處理都說處理不了,就再次交給調試器)
6. 調用異常端口通知csrss.exe
下面咱就詳細討論一下各個步驟都干了哪些細活:
1. 第一次交給調試器
如果該出現異常的程序正在被調試,則該異常首先交給調試器處理(通過DebugPort)。調試器拿到這個異常后,需要判斷是否要處理該異常.
2. 執行VEH
這里就不講Veh的概念了,有興趣的去Google一下。
如果沒有被調試,或者調試器返回DBG_EXCEPTION_NOT_HANDLED,則就會檢查是否存在VEH。如果存在VEH,則把異常交給他們處理。
VEH是個鏈表,可以存在多個Veh。每個VEH按順序被調用。
一個VEH可以返回連個值:EXCEPTION_CONTINUE_SEARCH、EXCEPTION_CONTINUE_EXECUTION。返回 EXCEPTION_EXECUTE_HANDLER是無效的,等同於EXCEPTION_CONTINUE_SEARCH。
當一個Veh返回EXCEPTION_CONTINUE_SEARCH,則把異常交給下一個VEH處理。
如果返回EXCEPTION_CONTINUE_EXECUTION,認為已經被處理,退出異常處理器在異常指令處繼續執行。
從執行順序來看,VEH是在SEH之前執行的,並且不依賴某一線程,本進程中任何線程出現異常都可以被VEH處理,所以在有些時候是很有用處的。
那么怎么添加一個VEH呢?這里不是我們介紹的重點,請參考:http://msdn.microsoft.com/en-us/library/ms681420%28VS.85%29.aspx
3. 執行SEH
SEH是基於線程棧的異常處理機制。當所有的VEH都不處理該異常,該異常就會讓SEH處理,所以它只能處理自己線程的異常。
4. TopLevelEH
頂層異常處理,這個就是我們今天介紹的重點了,這個其實是利用SEH實現的。在最頂層的SEH中,可以注冊一個頂層異常處理器。雖然他是基於SEH實現的,但是它可以處理所有線程拋出的異常。
當SEH都處理不了該異常,在最頂層的SEH中就會檢查是否注冊了頂層異常處理,如果注冊了,則執行頂層異常處理。
注意:如果該進程正在調試狀態,頂層異常處理會被忽略,不會被執行。
頂層異常處理函數也可以返回三個值:EXCEPTION_CONTINUE_SEARCH、EXCEPTION_EXECUTE_HANDLER、EXCEPTION_CONTINUE_EXECUTION,先記着,等會有用。
這個到底有什么用呢?應用程序難免會出現崩潰的,一旦崩潰了怎么辦?想想QQ,每次崩潰都會彈出友好提示,它是怎么做到的呢?對了,就是這個TopLevelEH。
關於這個,某java開發的同學其實已經申請了專利:
公開號 CN101794243 A 發布類型 申請 專利申請號 CN 201010126856 公開日 2010年8月4日 申請日期 2010年3月18日 優先權日 2010年3月18日 摘要: 一種利用操作系統結構化異常處理加固java應用程序的方法 CN 101794243 A 本發明提供一種利用操作系統結構化異常處理加固java應用程序的方法,Java提供了在java程序中調用本地程序的方法,這些本地程序通常以動態庫的形式存在,一旦動態庫中出現沒有捕獲的錯誤,就會導致整個java虛擬機崩潰,並且沒有任何補救方法。通過利用操作系統的結構化異常處理機制,能保證java虛擬機在崩潰以后按照需要執行相應的補救措施,比如發出友好化的通知,重新啟動java虛擬機等。
看看,人家java的Jni(就是java中調用本地鏈接庫的方法,對應於.net的P/Invoke)也同樣遇到了這個問題,然后。。然后就專利了還,好吧這個專利太簡單,我送給.net的同學們好了。
該專利被華為引用:
公開號 WO2013071766 A1 發布類型 申請 專利申請號 PCT/CN2012/078524 公開日 2013年5月23日 申請日期 2012年7月12日 優先權日 2011年11月14日 公告號 CN103107905A, EP2712119A1, US20140115587 申請人 Huawei Technologies Co., Ltd., 華為技術有限公司 摘要: 本發明公開了一種異常處理方法、裝置和客戶端,屬於在線應用領域。該方法包括:虛擬管理服務器接收與第一客戶端交互的虛擬機發送的異常通知,所述異常通知至少攜帶用戶標識和應用標識;所述虛擬管理服務器根據保存的與所述用戶標識及應用標識對應的異常處理方式,保存所述虛擬機數據或與所述應用標識對應的應用的應用數據,並釋放所述虛擬機資源。該裝置包括:接收模塊和異常處理模塊。本發明使得不同應用、不同用戶可以根據其需求定制不同的異常處理方法,也使得客戶端不但可保存異常發生時的時刻或最接近異常發生時的時刻用戶的使用狀態,而且提高了在線應用系統的容量和效率。
說白了就是捕捉到異常之后發消息而已啦,其實所謂的專利就是這么簡單的東西。
關於5和6我們就略過好了,講講大家都關心的怎么來實現吧。
首先定義一個委托:
public delegate Int32 CallBack(ref long a); CallBack mycall;
注冊一個頂層異常處理器的方法如下:
[DllImport("kernel32")] private static extern Int32 SetUnhandledExceptionFilter(CallBack cb);
在系統初始化時,調用:
mycall = new CallBack(MyExceptionfilter); SetUnhandledExceptionFilter(mycall);
MyExceptionfilter簽名如下:
Int32 MyExceptionfilter(ref long a)
它返回一個Int值,對應上面的TopLevelEH:
EXCEPTION_EXECUTE_HANDLER == 1 表示我已經處理了異常,可以優雅地結束了
EXCEPTION_CONTINUE_SEARCH == 0 表示我不處理,其他人來吧,於是windows調用默認的處理程序顯示一個錯誤框,並結束
EXCEPTION_CONTINUE_EXECUTION e== -1 表示錯誤已經被修復,請從異常發生處繼續執行。
在實際方法中,你可以記錄下程序的dump文件(Dump文件有什么用?回頭我再寫一篇好了),發送消息,然后優雅的結束或者重啟,做出極好的客戶體驗,是不是很棒?!接下來我們處理開頭的異常,那其實很簡單了,必定會被捕獲到,不信您可以試試。
代碼實在很簡單,我就不上傳了,有心的同學一試就出來了。
覺得寫得好,您就點個贊~
參考鏈接:http://bbs.pediy.com/showthread.php?t=173853