.NET 4.0裏異常處理的新機制

前幾天,有一個朋友問我爲什麼在.NET裏不能捕捉(catch)到一些異常了,而且在調試器裏也捕捉不到。研究了一下,是.NET 4.0裏新的異常處理機制搗的鬼。

在.NET 4.0之後,CLR 將會區別出一些異常(都是SEH異常),將這些異常標識爲破壞性異常(Corrupted State Exception)。針對這些異常,CLR 的 catch 塊不會捕捉這些異常,即使你用類似下面的代碼:

            try
            {
                TestMethod();
            }
            catch (Exception e)
            {
                Console.WriteLine("Catching exception: {0}", e);
            }

也沒有辦法捕捉到這些異常。之所以要這樣設計,在 MSDN 的文章 Handling Corrupted State Exceptions裏已經提到了。即,有一些支持插件的程序,例如 Visual Studio 或者SQL Server,它們支持調用託管代碼編寫成的插件,但是它們自己本身有很多代碼是由非託管的C++寫成的。由於插件經常會調用到非託管的API,而很多時間,這些插件的代碼根本就不知道如何處理非託管的API拋出來的 SEH 異常。在4.0以前,因爲SEH異常被轉換成了跟普通.NET異常相同的異常,這樣程序員只要用 catch (Exception e) 的模式就可以捕捉到所有的異常。這樣處理的問題是,由於SEH異常通常都不是託管代碼拋出的,託管代碼根本就不知道SEH異常被扔出來的原因,簡單的 catch (Exception e) 處理使得整個程序會處於一個非常不穩定的狀態,使得前面被忽略的問題在後面以更嚴重的方式出現—— 例如保存被破壞的數據。這樣,看起來使用catch (Exception e) 處理所有的異常的方法很簡單,但實際上讓程序員或者用戶在問題延後發生時,分析起來需要花費更多的精力。

因此在4.0以後,大部分SEH(我懷疑是所有)異常都被標識成破壞性異常,在.NET裏,默認情況下CLR不會捕捉它們,而是任由操作系統來處理——即關閉程序,並打開一個錯誤對話框通知用戶。爲了保證兼容性,在4.0以前編譯的程序,例如在2.0、3.0和3.5編譯的程序,依然採用的是老的策略—即.NET會同時捕捉.NET異常和SEH異常。而在4.0下面編譯的程序纔會使用新的策略,這也是在文章的開頭,我的朋友所碰到的問題。你可以在.NET 4.0下面編譯下面的程序,體驗一下這個新變化:
Program.cs:

using System;
using System.Runtime.InteropServices;
  
namespace ConsoleApplication1
{
     class Program
     {
         [DllImport("Ref.dll")]
         private extern static void TestMethod();
  
         static void Main(string[] args)
         {
             try
             {
                 TestMethod();
             }
             catch (Exception e)
             {
                 Console.WriteLine("Catching exception: {0}", e);
             }
         }
     }
}

Ref.cpp:

#include "stdafx.h"
  
 extern "C" __declspec(dllexport) void TestMethod()
 {
     int *p = NULL;
     // 會導致.NET拋出一個AccessViolation異常
     *p = 10;
 }

上面的代碼裏,Program.cs 使用 P/Invoke 技術調用了 Ref.dll 文件裏的 TestMethod,但是 TestMethod 嘗試給一個空指針賦值,導致一個 AccessViolation 異常。如果你在2.0下面編譯 program.cs,並執行的話,這個 AccessViolation 異常會被 catch(Exception e) 捕捉到,而如果你在4.0下面編譯並執行的話,你會發現catch (Exception e) 是不能捕捉到這個異常的。

然而並不是所有人都想要這個新的異常機制,如果你的程序是在4.0下面編譯並運行,而你又想在 .NET 程序裏捕捉到 SEH 異常的話,有兩個方案可以嘗試:

(1)在託管程序的 .config 文件裏,啓用 legacyCorruptedStateExceptionsPolicy 這個屬性,即簡化的 .config 文件類似下面的文件:
App.config:

<?xml version="1.0"?>
<configuration>
 <startup>
   <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
 </startup>
    <runtime>
      <legacyCorruptedStateExceptionsPolicy enabled="true" />
    </runtime>
</configuration>

這個設置告訴CLR 4.0,整個.NET 程序都要使用老的異常捕捉機制。

(2)在需要捕捉破壞性異常的函數外面加一個 HandleProcessCorruptedStateExceptions 屬性,這個屬性只控制一個函數,對託管程序的其他函數沒有影響,例如:

[HandleProcessCorruptedStateExceptions]
 static void Main(string[] args)
 {
     try
     {
         TestMethod();
     }
     catch (Exception e)
     {
         Console.WriteLine("Catching exception: {0}", e);
     }
 }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章