天天看點

應用程式域 z

應用程式域(AppDomain)已經不是一個新名詞了,隻要熟悉.net的都知道它的存在,不過我們還是先一起來重新認識下應用程式域吧,究竟它是何方神聖。

應用程式域

    衆所周知,程序是代碼執行和資源配置設定的最小單元,每個程序都擁有獨立的記憶體單元,而程序之間又是互相隔離的,自然而然,程序成為了代碼執行的安全邊界。

    一個程序對應一個應用程式是一個普遍的認知,而.net卻打破了這一慣例,因為它帶來了應用程式域這一全新的概念,CLR可使用應用程式域來提供應用程式 之間的隔離,而一個程序中可以運作多個應用程式域,也就是說隻要使用應用程式域,我們可以在一個程序中運作多個應用程式,而不會造成程序間調用或程序間切 換等方面的額外開銷。

    是不是覺得應用程式域是個很神奇的東東了,别急,我們再來看看它的隔離特性又為我們帶來了什麼。

優勢

    首先,應用程式域之間是不互相影響的,它是天生的異常隔離機制。也就是說,在一個應用程式域中出現的錯誤不會影響到其他應用程式域,因為類型安全的代碼不會導緻記憶體錯誤。

    其次,它能夠在運作時動态的加載和解除安裝程式集。我們都知道,在.net世界中,加載器一旦加載了程式集,那麼它将一直存在于應用程式的整個生命周期中,而應用程式域則改變了這一切,它為我們提供了解除安裝程式集的能力。

    最後,應用程式域可以單獨實施安全政策和配置政策。說白了就是可以為每個應用程式域配置相應的權限,以更好的管理應用程式。

    另外值得注意的是,應用程式域和線程之間不具有一對一的相關性。在任意給定時間,在單個應用程式域中可以執行多個線程,而特定線程并不局限在單個應用程式 域内。也就是說,線程可以自由跨越應用程式域邊界,如果沒有主動新啟線程,那麼多個應用程式域依然運作在同一個線程中。

    總的來說,應用程式域形成了托管代碼的隔離、解除安裝和安全邊界。而這些特性帶給一個插件式架構的将是異常隔離、動态加載解除安裝插件和更安全的插件運作環境。

由于這篇文章的定位是針對架構設計結合應用程式域的特性,是以假設你已經對應用程式域有了一定的了解了,下面通過示例,讓我們一步一步來認識應用程式域的這些特性。

建立和解除安裝AppDomain

         使用C#我們可以用如下的方式建立一個應用程式域,并在新域中執行一段代碼:

應用程式域 z

AppDomain domain = AppDomain.CreateDomain("Hello AppDomain!");

domain.DoCallBack(new CrossAppDomainDelegate(() =>

{

Window win =new Window

{

Width =300,

Height =100,

Content = AppDomain.CurrentDomain.FriendlyName

};

win.Show();

}));

應用程式域 z

運作後可以看到在新域中建立的Window展示如下:

應用程式域 z

解除安裝應用程式域則可以通過AppDomain靜态方法AppDomain.Unload(domain)實作,就是這麼簡單。

配置域加載方式

如果你運作了上面這段代碼,是不是發現新域建立的Window過了好久才呈現出來,這是怎麼回事呢,簡單來說,這是因 為.net加載器預設的行為是在每個域裡都會重新加載引用的程式集(包括Framework本身除了mscorlib外的程式集),當然我們可以更改這種 行為,不過在這之前我們先來了解下一個新概念”domain neutrality”, 詳細資料可以看這篇文章Domain Neutral Assemblies,簡單來說它擁有跨域共享程式集的能力,這就避免了重複加載的損耗,我們可以通過為程式入口main函數添加LoaderOptimization标簽修改預設加載方式:

應用程式域 z

[System.STAThreadAttribute()]

[System.Diagnostics.DebuggerNonUserCodeAttribute()]

[LoaderOptimization(

LoaderOptimization.MultiDomainHost)]

publicstaticvoid Main()

{

AppDomainTest.App app =new AppDomainTest.App();

app.InitializeComponent();

app.Run();

}

應用程式域 z

重新編譯運作,速度有了明顯的提升吧。

LoaderOptimization有三種方式(SingleDomain, MultiDomain和MultiDomainHost),在Domain Neutral Assemblies中均有詳細的解譯,有興趣的朋友可以看下,此處就不再重述了。

異常隔離 

    對于插件式架構而言,異常隔離是非常重要的,這是保證一個架構穩定性的必要特性。下面我們來看看使用應用程式域如何實作異常隔離。

首先我們來模拟在新建立的域中抛出異常:

應用程式域 z

win.Loaded += (obj, arg) =>

thrownew Exception("test exception.");

應用程式域 z

    這裡采用的是在Window loaded事件中直接抛出異常達到模拟效果,OK,編譯運作,很不幸,成功挂掉。

    這是因為新域中未處理的異常,最終都會抛至預設域,進而導緻崩潰。要驗證這一點,很容易,我們隻要在預設域中添加 AppDomain.CurrentDomain.UnhandledException事件處理就可以截獲到新域中抛出的異常,可惜在此你隻能截獲卻無 法改變崩潰的結果。

那麼如何才能處理掉這個異常,在預設域或者新域中注冊System.Windows.Threading.Dispatcher.CurrentDispatcher.UnhandledException事件處理,示例如下:

應用程式域 z

System.Windows.Threading.Dispatcher.CurrentDispatcher.UnhandledException += (obj, arg) =>

{

arg.Handled =true;

MessageBox.Show(arg.Exception.Message);

AppDomain.Unload(domain);

};

domain.DoCallBack(new CrossAppDomainDelegate(() =>

Window win =new Window

{

Width =300,

Height =100,

Content = AppDomain.CurrentDomain.FriendlyName

};

win.Loaded += (obj, arg) =>

win.Show();

}));

應用程式域 z

    注意到最關鍵的arg.Handled = true這一句,它的意義在于告訴系統這個事件已經被處理過了,不要再往下傳遞了,最後主動把新域解除安裝掉,而預設域則仍然正常運作着,如此便達到了異常隔離的效果。

組合不同域中的插件 

    假設所有的插件都處于不同的域中,那麼如何組合它們呢,即如何将不同域中的插件同時呈現到一個容器中。

    衆所周知,要實作對象在域之間傳遞,對象必須是可序列化的或者是繼承自MarshalByRefObject的類型,然而UI控件對此卻是 無能為力了, 在此就需要微軟的Addin架構幫助了,雖然大家都覺得Addin架構複雜、難用,但是裡面有好些東西還是很有用處的,比如這裡将要用到的 FrameworkElementAdapters類,它提供了兩個靜态方法ContractToViewAdapter和ViewToContractAdapter用于實作FrameworkElement和INativeHandleContract之間的互相轉換,傳說中這種轉換是通過句柄實作的。還是用例子來說明如何讓插件跨域呈現吧,首先添加System.Addin.Contract.dll和System.Windows.Presentation.dll兩個引用,然後編寫如下代碼

應用程式域 z

AppDomain domain = AppDomain.CreateDomain("test");

// 在新域中建立Button控件

Button btn =new Button { Content ="test" };

// 将Button控件轉換為INativeHandleContract

INativeHandleContract ict = FrameworkElementAdapters.ViewToContractAdapter(btn);

AppDomain.CurrentDomain.SetData("testbtn", ict);

// 在主域中擷取新域中的INativeHandleContract對象

INativeHandleContract iContract = domain.GetData("testbtn") as INativeHandleContract;

// 将INativeHandleContract對象轉換回FrameworkElement

FrameworkElement ctrl = FrameworkElementAdapters.ContractToViewAdapter(iContract);

Application.Current.MainWindow.Content = ctrl;

應用程式域 z