Windows核心编程 第2 5章 未处理异常和C ++异常(上)
未處理異常和C?+?+異常(上)
? ? 前一章討論了當一個異常過濾器返回?E?X?C?E?P?T?I?O?N?_?C?O?N?T?I?N?U?E?_?S?E?A?R?C?H時會發生什么事情。返回EXCEPTION_CONTINUE_SEARCH?是告訴系統繼續上溯調用樹,去尋找另外的異常過濾器。但是當每個過濾器都返回E?X?C?E?P?T?I?O?N?_?C?O?N?T?I?N?U?E?_?S?E?A?R?C?H時會出現什么情況呢?在這種情況下,就出現了所謂的“未處理異常”(Unhandled?exception)。
在第6章里我們已經知道,每個線程開始執行,實際上是利用?K?e?r?n?e?l?3?2?.?d?l?l中的一個函數來調用B?a?s?e?P?r?o?c?e?s?s?S?t?a?r?t或B?a?s?e?T?h?r?e?a?d?S?t?a?r?t。這兩個函數實際是一樣的,區別在于一個函數用于進程的主線程(Primary?thread):
另一個函數用于進程的所有輔助線程(Secondary?thread):
? ? 注意這兩個函數都包含一個S?E?H框架。每個函數都有一個t?r?y塊,并從這個t?r?y塊里調用主線程或輔助線程的進入點函數。所以,當線程引發一個異常,所有過濾器都返回?E?X?C?E?P?T?I?O?N?_C?O?N?T?I?N?U?E?_?S?E?A?R?C?H時,將會自動調用一個由系統提供的特殊過濾器函數:?U?n?h?a?n?d?l?e?dE?x?c?e?p?t?i?o?n?F?i?l?t?e?r。
? ? 這個函數負責顯示一個消息框,指出有一個進程的線程存在未處理的異常,并且能讓用戶
結束或調試這個進程。
下面是寫個測試代碼來看下:
測試代碼,引發異常,但是沒有用__except去處理。根據上面的那個創建進程的函數,會先調用U?n?h?a?n?d?l?e?dE?x?c?e?p?t?i?o?n?F?i?l?t?e?r彈個窗,點擊關閉,然后全局展開,執行finally的代碼。
25.1?即時調試
? ? 隨時將調試程序連接到任何進程的能力稱為即時調試(Just-in-time?Debugging)。這里我們對它如何工作稍加說明:當程序員點擊?C?a?n?c?e?l按鈕,就是告訴U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數對進程進行調試。在內部,U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r調用調試程序,這需要查看下面的注冊表子關鍵字:
在這個子關鍵字里,?有一個名為D?e?b?u?g?g?e?r的數值,在安裝Visual?Studio時被設置成下面的值:
????這一行代碼是告訴系統要將哪一個程序(這里是?M?S?D?e?v.???e?x?e)作為調試程序運行。當然也可以選擇其他調試程序。U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r還在這個命令行中向調試程序傳遞兩個參數。第一個參數是被調試進程的?I?D。第二個參數規定一個可繼承的手工復位事件,這個事件是由U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r按無信號狀態建立的。廠商必須實現他們的調試程序,這樣才能認識指定進程I?D和事件句柄的-?p和-?e選項。
?????在進程?I?D和事件句柄都合并到這個串中之后,?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r通過調用C?r?e?a?t?e?P?r?o?c?e?s?s來執行調試程序。?這時,調試程序進程開始運行并檢查它的命令行參數。如果存在-?p選項,調試程序取得進程I?D,并通過調用D?e?b?u?g?A?c?t?i?v?e?P?r?o?c?e?s?s將自身掛接在該進程上。
BOOL?DebugActiveProcess(DWORD?dwProcessID);
???一旦調試程序完成自身的掛接,操作系統將被調試者(d?e?b?u?g?g?e?e)的狀態通報給調試程序。例如,系統將告訴調試程序,在被調試的進程中有多少線程?哪些?D?D?L加載到被調試進程的地址空間中?調試程序需要花時間來積累這些數據,以準備調試進程。在這些準備工作進行的時候,U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r中的線程必須等待。為此,這要調用?Wa?i?t?F?o?r?S?i?n?g?l?e?O?b?j?e?c?t函數并傳遞已經建立的手工復位事件的句柄作為參數。這個事件是按無信號狀態建立起來的,所以被調試進程的線程要立即被掛起以等待事件。
? ? 在調試程序完全初始化之后,它要再檢查它的命令行,找?-?e選項。如果該選項存在,調試程序取得相應的事件句柄并調用?S?e?t?E?v?e?n?t。調試程序可以直接使用事件的句柄值,因為事件句柄具有創建的可繼承性,并且被調試進程對?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數的調用也使調試程序進程成為一個子進程。設定這個事件將喚醒被調試進程的線程。被喚醒的線程將有關未處理異常的信息傳遞給調試程序。調試程序接收這些通知并加載相應的源代碼文件,再將自身放在引發異常的指令位置上。
? ? 還有,不必在調試進程之前等待異常的出現。可以隨時將一個調試程序連接在任何進程上,只需運行“MSDEV?-p?PID”,其中P?I?D是要調試的進程的?I?D。實際上,利用?Windows?2000Task?Manager,做這些事很容易。當觀察P?r?o?c?e?s?s標記欄時,可以選擇一個進程,點擊鼠標右鍵,并選擇D?e?b?u?g菜單選項。這將引起?Task?Manager去查看前面討論過的注冊表子關鍵字,調用C?r?e?a?t?e?P?r?o?c?e?s?s,并傳遞所選定的進程的?I?D作為參數。在這里,Task?Manager為事件句柄傳送0值。
嘗試了下那個函數,結果如下:
25.2?關閉異常消息框
? ? 有時候,在異常發生時,你可能不想在屏幕上顯示異常消息框。例如,你可能不想讓這些消息框出現在你產品的發售版本中。如果出現了消息框,很容易導致最終用戶意外地啟動調試程序來調試你的程序。最終用戶只需點擊一下消息框中的?C?a?n?c?e?l按鈕,就進入了不熟悉的、令人恐惶的區域?—?調試程序。可以使用幾種不同的方法來防止這種消息框的出現。
25.2.1?強制進程終止運行
為防止U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r顯示異常消息框,可以調用下面的S?e?t?E?r?r?o?r?M?o?d?e?l函數,并向它傳遞一個S?E?M?_?N?O?G?P?FA?U?LT?E?R?R?O?R?B?O?X標識符:
UINT?SetErrorMode(UINT?fuErrorMode);
然后,當調用U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數來處理異常時,看到已經設置了這個標志,就會立即返回E?X?C?E?P?T?I?O?N?_?E?X?E?C?U?T?E?_?H?A?N?D?L?E?R。這將導致全局展開并執行B?a?s?e?P?r?o?c?e?s?s?S?t?a?r?t或B?a?s?e?T?h?r?e?a?d?S?t?a?r?t中的處理程序。該處理程序結束進程。
主線程里異常不會崩潰
在其他線程里,也不會崩潰,相當于這個設置是整個進程的。
?
25.2.2?包裝一個線程函數
????使用另外一種辦法也可以避免出現這個消息框,就是針對主線程進入點函數(?m?a?i?n、
w?m?a?i?n、Wi?n?M?a?i?n或w?Wi?n?M?a?i?n)的整個內容安排一個t?r?y?-?e?x?c?e?p?t塊。保證異常過濾器的結果值總是E?X?C?E?P?T?I?O?N?_?E?X?E?C?U?T?E?_?H?A?N?D?L?E?R,這樣就保證異常得到處理,防止了系統再調用U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數。
????在你的異常處理程序中,你可以顯示一個對話框,在上面顯示一些有關異常的診斷信息。
用戶可以記錄下這些信息,并通報給你公司的客戶服務部門,以便能夠找到程序的問題根源。
你應該建立這個對話框,這樣用戶只能結束程序而不能調用調試程序。
????這種方法的缺點是它只能捕捉進程的主線程中發生的異常。如果其他線程在運行,并且其中有一個線程發生了一個未處理異常,系統就要調用內部的?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數。為了改正這一點,需要在所有的輔助線程進入點函數中包含t?r?y?-?e?x?c?e?p?t塊。
25.2.3?包裝所有的線程函數
? ? Wi?n?d?o?w?s還提供另外一個函數,S?e?t?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r,利用它可以按S?E?H格式包裝所有的線程函數:
PTOP_LEVEL_EXCEPTION_FILTER?SetUnhandledExceptionFilter(
PTOP_LEVEL_EXCEPTION_FILTER?pTopLevelExceptionFilter);
? ? 在進程調用這些函數之后,進程的任何線程中若發生一個未處理的異常,就會導致調用程序自己的異常過濾器。需要將這個過濾器的地址作為參數傳遞給?S?e?t?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r。過濾器函數原型必須是下面的樣子:
LONG?UnhandledExceptionFilter(PEXCEPTION_POINTERS?pExceptionInfo);
? ? 你可能會注意到這個函數同U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數的形式是一樣的。程序員可以在自己的異常過濾器中執行任何想做的處理,但要返回三個?E?X?C?E?P?T?I?O?N?_?*標識符中的一個。表2?5?-?1給出了當返回各標識符時所發生的事。
????為了使?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數再成為默認的過濾器,可以調用?S?e?t?U?n?h?a?n?d?l?e?dE?x?c?e?p?t?i?o?n?F?i?l?t?e?r并傳遞?N?U?L?L給它。而且,每當設置一個新的未處理的異常過濾器時,SetUnhandled?ExceptionFilter就返回以前安裝的異常過濾器的地址。如果?Unhandled?ExceptionF?i?l?t?e?r是當前所安裝的過濾器,則這個返回的地址就是?N?U?L?L。????如果你自己的過濾器要返回E?X?C?E?P?T?I?O?N?_?C?O?N?T?I?N?U?E?_?S?E?A?R?C?H,你就應該調用以前安裝的過濾器,其地址通過S?e?t?U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數返回。
以下是一個測試例子:
25.2.4?自動調用調試程序
? ? 現在再介紹關閉U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r消息框的最后一種方法。在前面提到的同一個注冊表子關鍵字里,還有另外一個數據值,名為?A?u?t?o。這個值用來規定U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r是應該顯示消息框,還是僅啟動調試程序。如果?A?u?t?o設置成1,U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r就不顯示消息框向用戶報告異常,而是立即調用調試程序。如果?A?u?t?o子關鍵設置成?0,U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r就顯示異常消息框,并按前面描述的那樣操作。
25.3?程序員自己調用U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r
? ? U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r函數是一個公開的、文檔完備的?Wi?n?d?o?w?s函數,程序員可以直接在自己的代碼中調用這個函數。這里是使用這個函數的一個例子:
?
? ? 在F?u?n?c?a?d?e?l?i?c函數中,t?r?y塊中的一個異常導致E?x?p?F?l?t?r函數被調用。G?e?t?E?x?c?e?p?t?i?o?n?I?n?f?o?r?m?a?t?i?o?n的返回值作為參數傳遞給?E?x?p?F?l?t?r函數。在異常過濾器內,要確定異常代碼并與?E?X?C?E?P?T?I?O?N?_A?C?C?E?S?S?_?V?I?O?L?AT?I?O?N相比較。如果發生一個存取違規,異常過濾器改正這個問題,并從過濾器返回E?X?C?E?P?T?I?O?N?_?C?O?N?T?I?N?U?E?_?E?X?E?C?U?T?I?O?N。這個返回值導致系統從最初引起異常的指令繼續執行。
如果發生了其他異常,E?x?p?F?l?t?r調用U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r,將E?X?C?E?P?T?I?O?N?_?P?O?I?N?T?E?R?S結構的地址傳遞給它作為參數。U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r顯示消息框,可使程序員結束進程或開始調試進程。U?n?h?a?n?d?l?e?d?E?x?c?e?p?t?i?o?n?F?i?l?t?e?r的返回值再由E?x?p?F?l?t?r返回。
?
?
?
?
?
總結
以上是生活随笔為你收集整理的Windows核心编程 第2 5章 未处理异常和C ++异常(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows核心编程 第2 4章 异常
- 下一篇: Windows PE 重定位表编程(枚举