一、概述
使用.NET建立的可執行程式 *.exe,并沒有直接承載到程序當中,而是承載到應用程式域(AppDomain)當中。在一個程序中可以包含多個應用程式域,一個應用程式域可以裝載一個可執行程式(*.exe)或者多個程式集(*.dll),這樣可以使應用程式域之間實作深度隔離,即使程序中的某個應用程式域出現錯誤,也不會影響其他應用程式域的正常運作。處理asp.net所涉及的類大多數定義在System.Web程式集中。
- 當exe程式集加載完畢,.Net會在目前程序中建立一個預設應用程式域,這個應用程式域的名稱與程式集名稱相同。預設應用程式域不能被解除安裝,并且與其所在的程序同生共滅。
- 應用程式域隻是允許它所加載的程式集通路由.Net Runtime所提供的服務。這些服務包括托管堆(Managed Heap),垃圾回收器(Garbage collector),JIT 編譯器等.Net底層機制.
- 一個應用程式域中如果出現了緻命錯誤導緻崩潰,隻會影響其本身,而不會影響到其他應用程式域。實作了錯誤隔離。
應用程式域是程序中承載程式集的邏輯分區,在應用程式域中存在更細粒度的用于承載.NET對象的實體,即.NET上下文Context。
所有的.NET對象都存在于.NET上下文當中,每個AppDomain當中至少存在一個預設上下文(context0),:
優點
托管程式為什麼要使用應用程式域呢?概括其優點如下:
- 在一個應用程式中出現的錯誤不會影響其他應用程式。
- 因為類型安全的代碼不會導緻記憶體錯誤,是以使用應用程式域可以確定在一個域中運作的代碼不會影響程序中的其他應用程式。
- 能夠在不停止整個程序的情況下停止單個應用程式。
- 使用應用程式域時,可以解除安裝在單個應用程式中運作的代碼。
- 在一個應用程式中運作的代碼不能直接通路其他應用程式中的代碼或資源。
屬性
- ActivationContext 擷取目前應用程式域的激活上下文。
- ApplicationIdentity 獲得應用程式域中的應用程式辨別。
- BaseDirectory 擷取基目錄,它由程式集沖突解決程式用來探測程式集。
- CurrentDomain 擷取目前 Thread 的目前應用程式域。
- FriendlyName 擷取此應用程式域的友好名稱。
- Id 獲得一個整數,該整數唯一辨別程序中的應用程式域。
- SetupInformation 擷取此執行個體的應用程式域配置資訊。
方法
- CreateDomain(String) 使用指定的名稱建立應用程式域。
- CreateInstance(String, String) 建立在指定程式集中定義的指定類型的新執行個體。
- CreateInstanceAndUnwrap(String, String) 建立指定類型的新執行個體。 形參指定定義類型的程式集以及類型的名稱。
- CreateInstanceFrom(String, String) 建立在指定程式集檔案中定義的指定類型的新執行個體。
- DoCallBack 在另一個應用程式域中執行代碼,該應用程式域由指定的委托辨別。
- ExecuteAssembly(String) 執行指定檔案中包含的程式集。
- ExecuteAssemblyByName(String) 在給定其顯示名稱的情況下執行程式集。
- GetAssemblies 擷取已加載到此應用程式域的執行上下文中的程式集。
- GetData 為指定名稱擷取存儲在目前應用程式域中的值。
- IsDefaultAppDomain 傳回一個值,訓示應用程式域是否是程序的預設應用程式域。
- Load(AssemblyName) 在給定 AssemblyName 的情況下加載 Assembly。
- SetData(String, Object) 為指定的應用程式域屬性配置設定指定值。
- Unload 解除安裝指定的應用程式域。
程式集
程式集是包含一個或多個類型定義檔案和資源檔案的集合。它允許我們分離可重用類型的邏輯表示和實體表示。程式集可以是靜态的或動态的。靜态程式集可以包括 .NET Framework 類型(接口和類),以及該程式集的資源(位圖、JPEG 檔案、資源檔案等)。靜态程式集存儲在磁盤上的可移植可執行 (PE) 檔案中。您還可以使用 .NET Framework 來建立動态程式集,動态程式集直接從記憶體運作并且在執行前不存儲到磁盤上。您可以在執行動态程式集後将它們儲存在磁盤上。
全局程式集緩存:安裝有公共語言運作時的每台計算機都具有稱為全局程式集緩存的計算機範圍内的代碼緩存。 全局程式集緩存中存儲了專門指定給由計算機中若幹應用程式共享的程式集。
二、基本操作
在.Net 中,将應用程式域封裝為了AppDomain類,這個類提供了應用程式域的各種操作,包含 加載程式集、建立對象、建立應用程式域 等。通常的程式設計情況下下,我們幾乎從不需要對AppDomain進行操作。
1、擷取目前運作的代碼所在的應用程式域。
可以使用AppDomain類的靜态屬性CurrentDoamin,擷取目前代碼所在的應用程式域;或者使用Thread類的靜态方法GetDomain(),得到目前線程所在的應用程式域:
AppDomain currentDomain = AppDomain.CurrentDomain;
AppDomain currentDomain = Thread.GetDomain();
NOTE:一個線程可以通路程序中所包含的所有應用程式域,因為雖然應用程式域是彼此隔離的,但是它們共享一個托管堆(Managed Heap)。
2、擷取應用程式域的名稱。
使用AppDomain的執行個體隻讀屬性,FriendlyName:
string name = AppDomain.CurrentDomain.FriendlyName;
3、從目前應用程式域中建立新應用程式域。
可以使用CreateDomain()靜态方法,并傳入一個字元串,作為新應用程式域的名稱(亦即設定FriendlyName屬性):
AppDomain newDomain = AppDomain.CreateDomain("New Domain");
4、配置應用程式域(在建立前):
AppDomainSetup domainInfo = new AppDomainSetup();
domainInfo.ApplicationBase = "e:\\AttributeTest";
domainInfo.ConfigurationFile = "e:\\AttributeTest\\1.exe.config";
AppDomain domain = AppDomain.CreateDomain("MyDomain", null, domainInfo);
5、判斷是否為預設應用程式域:
newDomain.IsDefaultAppDomain()
6、解除安裝應用程式集
AppDomain.Unload(newDomain);
三、在新的應用程式域中執行程式集
加載可執行程式:
AppDomain newAppDomain = AppDomain.CreateDomain("newAppDomain");
newAppDomain.ExecuteAssembly("Example.exe");//或使用ExecuteAssemblyByName
AppDomain.Unload(newAppDomain);
四、在新的應用程式域中建立對象
将程式集加載到應用程式域中的方式:
- a.Assembly.Load(assemblyName);使用程式集名來加載到應用程式域中,為常用方法。
- b.Assembly.LoadFrom():加載給定其檔案位置的程式集,通過此方法加載程式集将使用不同的加載上下文。
- c.Assembly.ReflectionOnlyLoad和 Assembly.ReflectionOnlyLoadFrom:将程式集加載到僅反射上下文中,可檢查(但不能執行)加載到該上下文中的程式集,同時允許檢查以其他平台為目标的程式集。
- e.Type.GetType()加載程式集
- f.AppDomain的Load方法加載程式集。主要用于實作COM互操作性,不應該使用該方法将程式集加載到除從其調用該方法的應用程式域以外的其他應用程式域。
- d.AppDomain的CreateInstance和CreateInstanceAndUnwrap方法。
具體見反射基礎:javascript:void(0)
在新的應用程式域中建立對象,可以使用AppDomain的執行個體方法CreateInstanceAndUnWrap()或者CreateInstance()方法。方法包含兩個參數,第一個參數為類型所在的程式集,第二個參數為類型全稱(這兩個方法後面會詳述):
DemoClass obj = (DemoClass)newDomain.CreateInstanceAndUnWrap("ClassLib", "ClassLib.DemoClass");
//或者
ObjectHandle objHandle = newDomain.CreateInstance("ClassLib", "ClassLib.DemoClass");
DemoClass obj = (DemoClass)objHandle.UnWrap();
//或者
DemoClass obj = (DemoClass)newDomain.CreateInstanceFromAndUnWrap("c:\\ClassLib.dll", "ClassLib.DemoClass");
注意:obj是在目前的預設應用程式域聲明的;而類型的執行個體(對象本身)卻在新建立的應用程式域NewDomain中建立的。這樣就出現了一種尴尬的情況:而預設情況下AppDomain是彼此隔離的,我們不能直接在一個應用程式中引用另一個應用程式域中的對象,是以這裡便會引發異常。
跨AppDomain運作代碼:
void Main()
{
//建立新的應用程式域對象
AppDomain newAppDomain = AppDomain.CreateDomain("newAppDomain");
CrossAppDomainDelegate crossAppDomainDelegate = new CrossAppDomainDelegate(MyCallBack);
newAppDomain.DoCallBack(crossAppDomainDelegate);
newAppDomain.DomainUnload += (obj, e) =>
{
Console.WriteLine("New Domain unload!");
};
AppDomain.Unload(newAppDomain);
}
static public void MyCallBack()
{
string name = AppDomain.CurrentDomain.FriendlyName;
for (int n = 0; n < 4; n++)
Console.WriteLine(string.Format(" Do work in {0}........", name));
}
五、封值傳送,Serializable
我們可以将對象标記為可序列化,将對象本身由另一應用程式域(遠端)傳遞到本地應用程式域中。
原理: 先在遠端建立對象,接着将對象序列化,然後傳遞對象,在本地進行反序列化,最後還原對象。
當位于ConsoleApp.exe的obj引用NewDomain中建立的對象時,.Net将NewDomain中對象的狀态進行複制、序列化,然後在ConsoleApp.exe中重新建立對象,還原狀态,并通過代理來對對象進行通路。這種跨應用程式域的通路方式叫做 傳值封送(Marshal by value)
[Serializable]
public class DemoClass { /*略*/ }
六、傳引用封送,MarshalByRefObject
讓對象依然保留在遠端(本例為NewDomain中),而在用戶端僅建立代理,上面已經說了代理的接口和遠端對象完全相同,是以用戶端以為仍然通路的是遠端對象,當用戶端調用代理上的方法時,由代理将對方法的請求發送給遠端對象,遠端對象執行方法請求,最後再将結果傳回。這種方式叫做 傳引用封送(Marshal by reference)。
對象或者對象引用在傳遞的過程中,是以一種包裝過的狀态(warpper state)進行傳遞。是以在建立對象時,要解包裝,是以在CreateInstanceAndUnWrap()方法後多了一個AndUnWrap字尾,實際上UnWrap還包含一個建立代理的過程。
讓對象繼承自MarshalByRefObject基類,實作傳引用封送。
public class DemoClass:MarshalByRefObject {/*略*/}
上下文綁定對象:ContextBoundObject
當系統需要對象使用消息接收器機制的時候,即可使用ContextBoundObject類。ContextBoundObject繼承了MarshalByRefObject類,保證了它的子類都會通過透明代理被通路。
一般類所建立的對象為上下文靈活對象(context-agile),它們都由CLR自動管理,可存在于任意的上下文當中。而 ContextBoundObject 的子類所建立的對象(稱作上下文綁定對象)隻能在建立它的對應上下文中正常運作,此狀态被稱為上下文綁定。其他對象想要通路ContextBoundObject 的子類對象時,都隻能通過代透明理來操作。ContextBound還有一個Synchronization特性,此特性會保證ContextBound對象被加載到一個線程安全的上下文當中運作。
七、應用程式域、程序與線程關系。
概述:
- 程序之間彼此是完全隔絕的,而線程與運作在相同程序的其他線程共享堆heap記憶體。
- 在應用程式域和線程之間沒有一對一的關聯。