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