應用程式域(AppDomain)已經不是一個新名詞了,隻要熟悉.net的都知道它的存在,不過我們還是先一起來重新認識下應用程式域吧,究竟它是何方神聖。
應用程式域
衆所周知,程序是代碼執行和資源配置設定的最小單元,每個程序都擁有獨立的記憶體單元,而程序之間又是互相隔離的,自然而然,程序成為了代碼執行的安全邊界。
一個程序對應一個應用程式是一個普遍的認知,而.net卻打破了這一慣例,因為它帶來了應用程式域這一全新的概念,CLR可使用應用程式域來提供應用程式 之間的隔離,而一個程序中可以運作多個應用程式域,也就是說隻要使用應用程式域,我們可以在一個程序中運作多個應用程式,而不會造成程序間調用或程序間切 換等方面的額外開銷。
是不是覺得應用程式域是個很神奇的東東了,别急,我們再來看看它的隔離特性又為我們帶來了什麼。
優勢
首先,應用程式域之間是不互相影響的,它是天生的異常隔離機制。也就是說,在一個應用程式域中出現的錯誤不會影響到其他應用程式域,因為類型安全的代碼不會導緻記憶體錯誤。
其次,它能夠在運作時動态的加載和解除安裝程式集。我們都知道,在.net世界中,加載器一旦加載了程式集,那麼它将一直存在于應用程式的整個生命周期中,而應用程式域則改變了這一切,它為我們提供了解除安裝程式集的能力。
最後,應用程式域可以單獨實施安全政策和配置政策。說白了就是可以為每個應用程式域配置相應的權限,以更好的管理應用程式。
另外值得注意的是,應用程式域和線程之間不具有一對一的相關性。在任意給定時間,在單個應用程式域中可以執行多個線程,而特定線程并不局限在單個應用程式 域内。也就是說,線程可以自由跨越應用程式域邊界,如果沒有主動新啟線程,那麼多個應用程式域依然運作在同一個線程中。
總的來說,應用程式域形成了托管代碼的隔離、解除安裝和安全邊界。而這些特性帶給一個插件式架構的将是異常隔離、動态加載解除安裝插件和更安全的插件運作環境。
由于這篇文章的定位是針對架構設計結合應用程式域的特性,是以假設你已經對應用程式域有了一定的了解了,下面通過示例,讓我們一步一步來認識應用程式域的這些特性。
建立和解除安裝AppDomain
使用C#我們可以用如下的方式建立一個應用程式域,并在新域中執行一段代碼:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5CMwUWOiNjZzYzMiNDOwUTZjlDZ4UWN5EzMlNmNjJjZ08CX2AzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.gif)
AppDomain domain = AppDomain.CreateDomain("Hello AppDomain!");
domain.DoCallBack(new CrossAppDomainDelegate(() =>
{
Window win =new Window
{
Width =300,
Height =100,
Content = AppDomain.CurrentDomain.FriendlyName
};
win.Show();
}));
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5CMwUWOiNjZzYzMiNDOwUTZjlDZ4UWN5EzMlNmNjJjZ08CX2AzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.gif)
運作後可以看到在新域中建立的Window展示如下:
解除安裝應用程式域則可以通過AppDomain靜态方法AppDomain.Unload(domain)實作,就是這麼簡單。
配置域加載方式
如果你運作了上面這段代碼,是不是發現新域建立的Window過了好久才呈現出來,這是怎麼回事呢,簡單來說,這是因 為.net加載器預設的行為是在每個域裡都會重新加載引用的程式集(包括Framework本身除了mscorlib外的程式集),當然我們可以更改這種 行為,不過在這之前我們先來了解下一個新概念”domain neutrality”, 詳細資料可以看這篇文章Domain Neutral Assemblies,簡單來說它擁有跨域共享程式集的能力,這就避免了重複加載的損耗,我們可以通過為程式入口main函數添加LoaderOptimization标簽修改預設加載方式:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5CMwUWOiNjZzYzMiNDOwUTZjlDZ4UWN5EzMlNmNjJjZ08CX2AzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.gif)
[System.STAThreadAttribute()]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[LoaderOptimization(
LoaderOptimization.MultiDomainHost)]
publicstaticvoid Main()
{
AppDomainTest.App app =new AppDomainTest.App();
app.InitializeComponent();
app.Run();
}
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5CMwUWOiNjZzYzMiNDOwUTZjlDZ4UWN5EzMlNmNjJjZ08CX2AzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.gif)
重新編譯運作,速度有了明顯的提升吧。
LoaderOptimization有三種方式(SingleDomain, MultiDomain和MultiDomainHost),在Domain Neutral Assemblies中均有詳細的解譯,有興趣的朋友可以看下,此處就不再重述了。
異常隔離
對于插件式架構而言,異常隔離是非常重要的,這是保證一個架構穩定性的必要特性。下面我們來看看使用應用程式域如何實作異常隔離。
首先我們來模拟在新建立的域中抛出異常:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5CMwUWOiNjZzYzMiNDOwUTZjlDZ4UWN5EzMlNmNjJjZ08CX2AzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.gif)
win.Loaded += (obj, arg) =>
thrownew Exception("test exception.");
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5CMwUWOiNjZzYzMiNDOwUTZjlDZ4UWN5EzMlNmNjJjZ08CX2AzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.gif)
這裡采用的是在Window loaded事件中直接抛出異常達到模拟效果,OK,編譯運作,很不幸,成功挂掉。
這是因為新域中未處理的異常,最終都會抛至預設域,進而導緻崩潰。要驗證這一點,很容易,我們隻要在預設域中添加 AppDomain.CurrentDomain.UnhandledException事件處理就可以截獲到新域中抛出的異常,可惜在此你隻能截獲卻無 法改變崩潰的結果。
那麼如何才能處理掉這個異常,在預設域或者新域中注冊System.Windows.Threading.Dispatcher.CurrentDispatcher.UnhandledException事件處理,示例如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5CMwUWOiNjZzYzMiNDOwUTZjlDZ4UWN5EzMlNmNjJjZ08CX2AzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.gif)
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();
}));
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5CMwUWOiNjZzYzMiNDOwUTZjlDZ4UWN5EzMlNmNjJjZ08CX2AzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.gif)
注意到最關鍵的arg.Handled = true這一句,它的意義在于告訴系統這個事件已經被處理過了,不要再往下傳遞了,最後主動把新域解除安裝掉,而預設域則仍然正常運作着,如此便達到了異常隔離的效果。
組合不同域中的插件
假設所有的插件都處于不同的域中,那麼如何組合它們呢,即如何将不同域中的插件同時呈現到一個容器中。
衆所周知,要實作對象在域之間傳遞,對象必須是可序列化的或者是繼承自MarshalByRefObject的類型,然而UI控件對此卻是 無能為力了, 在此就需要微軟的Addin架構幫助了,雖然大家都覺得Addin架構複雜、難用,但是裡面有好些東西還是很有用處的,比如這裡将要用到的 FrameworkElementAdapters類,它提供了兩個靜态方法ContractToViewAdapter和ViewToContractAdapter用于實作FrameworkElement和INativeHandleContract之間的互相轉換,傳說中這種轉換是通過句柄實作的。還是用例子來說明如何讓插件跨域呈現吧,首先添加System.Addin.Contract.dll和System.Windows.Presentation.dll兩個引用,然後編寫如下代碼
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5CMwUWOiNjZzYzMiNDOwUTZjlDZ4UWN5EzMlNmNjJjZ08CX2AzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.gif)
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;
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5CMwUWOiNjZzYzMiNDOwUTZjlDZ4UWN5EzMlNmNjJjZ08CX2AzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.gif)