天天看點

.NET 漫淡(一) --- 需要充分認識的應用程式域-AppDomain

隔離應用程式的優點

作業系統和運作時環境通常會在應用程式間提供某種形式的隔離。例如,Windows 和 Unix 使用程序來隔離應用程式 。為確定在一個應用程式中運作的代碼不會對其他不相關的應用程式産生不良影響,這種隔離是必需的。使用程序邊界來隔離在同一台計算機上運作的應用程式。每一個應用程式被加載到單獨的程序中,這樣就将該應用程式與在同一台計算機上運作的其他應用程式相隔離。 隔離這些應用程式的原因在于記憶體位址是與程序相關的;在目标程序中,不能通過任何有意義的方式使用從一個程序傳遞到另一個程序的記憶體指針。此外,您不能在兩個程序間進行直接調用。您必須使用代理/管道等手段,它提供一定程度的間接性。

隔離應用程式對于應用程式安全也是十分重要的。例如,您可以在單個浏覽器程序中運作幾個 Web 應用程式中的控件,同時使這些控件不能通路彼此的資料和資源。

應用程式域

應用程式域是.NET架構中非常重要的概念。 它為安全性、可靠性、版本控制以及解除安裝程式集提供了隔離邊界。由運作時宿主 (HOST) 建立,運作時宿主負責在運作應用程式之前引導公共語言運作時。在開發普通的WinForm程式時,應用程式域的作用不是非常明确,但如果開發Asp.net,Nt Service, SOA等服務為主體的應用程式時,熟悉應用程式域是非常有必要的。

使用應用程式域隔離可能終止程序的任務。如果正在執行任務的 AppDomain 的狀态變得不穩定,則可以解除安裝 AppDomain,但不會影響程序。 當程序必須不重新啟動而長時間運作時,這一點很重要。還可使用應用程式域隔離不應共享資料的任務。

  • 如果程式集被加載到預設應用程式域中,則當程序運作時将無法從記憶體中解除安裝該程式集。但是,如果打開另一個應用程式域來加載和執行程式集,則解除安裝該應用程式域時也會同時解除安裝程式集。使用此技術最小化長時間運作的程序的工作集,這些程序偶爾會使用大型 DLL。
  • 在一個應用程式中出現的錯誤不會影響其他應用程式。因為類型安全的代碼不會導緻記憶體錯誤,是以使用應用程式域可以確定在一個域中運作的代碼不會影響程序中的其他應用程式。
  • 能夠在不停止整個程序的情況下停止單個應用程式。使用應用程式域使您可以解除安裝在單個應用程式中運作的代碼。
  • 托管代碼必須先通過一個驗證過程,然後才能運作(除非管理者已授權跳過該驗證)。此驗證過程将驗證以下内容:這些代碼是否會嘗試通路無效的記憶體位址?是否會嘗試執行某些導緻程序(該代碼運作時所在的程序)無法正常進行的其他操作?通過此驗證測試的代碼将被認為是類型安全的。由于公共語言運作時能夠驗證代碼是否為類型安全的代碼,是以它可以提供與程序邊界一樣大的隔離級别,而其性能開銷則要低得多。
  • 應用程式域提供了一個更安全、用途更廣的處理單元,公共語言運作時可使用該單元提供應用程式之間的隔離。您可以在具有同等隔離級别(存在于單獨的程序中)的單個程序中運作幾個應用程式域,而不會造成程序間調用或程序間切換等方面的額外開銷。在一個程序内運作多個應用程式的能力顯著增強了伺服器的可伸縮性。
  • 不能解除安裝單個程式集或類型。隻能解除安裝整個域。
  • 在一個應用程式中運作的代碼不能直接通路其他應用程式中的代碼或資源。為了強制實施此隔離,公共語言運作時禁止在不同應用程式域中的對象之間進行直接調用。要在各域之間傳遞對象,可以複制這些對象,或通過代理通路這些對象。如果複制對象,那麼對該對象的調用為本地調用。也就是說,調用方和被引用的對象位于同一應用程式域中。如果通過代理通路對象,那麼對該對象的調用為遠端調用。在此情況下,調用方和被引用的對象位于不同的應用程式域中。域間調用所采用的遠端調用基礎結構與兩個程序間的調用或兩台計算機間的調用的基礎結構相同。是以,被引用的對象的中繼資料必須對于兩個應用程式域均可用,以便用 JIT 正确編譯該方法調用。如果調用域對被調用對象的中繼資料沒有通路權,則編譯可能失敗,并引發類型為 System.IO.FileNotFound 的異常。
  • 代碼行為的作用範圍由它運作所在的應用程式決定。換言之,應用程式域将提供應用程式版本政策等配置設定、它所通路的任意遠端程式集的位置,以及加載到該域中的程式集的位置資訊。
  • 向代碼授予的權限可以由代碼運作所在的應用程式域來控制。
  • 多個應用程式域可以在一個程序中運作;但是,在應用程式域和線程之間沒有一對一的關聯。多個線程可以屬于一個應用程式域,盡管給定的線程并不局限于一個應用程式域,但在任何給定時間,線程都在一個應用程式域中執行。

AppDomain

應用程式域(由 AppDomain 對象表示)為執行托管代碼提供隔離、解除安裝和安全邊界。 應用程式域 ( AppDomain ) 是從.NET 1.0開始就一直存在于System名字空間,它能夠在應用程式程序内建立起獨立的執行環境。其原始定義為:

[ComVisibleAttribute(true)]

[ClassInterfaceAttribute(ClassInterfaceType.None)]

public sealed class AppDomain : MarshalByRefObject,_AppDomain, IEvidenceFactory

.csharpcode, .csharpcode pre

{

font-size: small;

color: black;

font-family: consolas, "Courier New", courier, monospace;

background-color: #ffffff;

/*white-space: pre;*/

}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #006080; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt

background-color: #f4f4f4;

100%;

margin: 0em;

.csharpcode .lnum { color: #606060; }

此類從 MarshalByRefObject繼承,實作了_AppDomain 和 IEvidenceFactory 接口。 MarshalByRefObject 允許在支援遠端處理的應用程式中跨應用程式域邊界通路對象。

使用 CreateDomain 方法建立應用程式域。 AppDomain 執行個體用于加載和執行程式集 ( Assembly)。 當不再使用 AppDomain 時,可以将它解除安裝。

AppDomain 類實作一組事件,這些事件使應用程式可以在加載程式集、要解除安裝應用程式域或引發未經處理的異常時進行響應。

在任何情況下都不應建立 AppDomain 對象的可遠端控制的包裝,即不要使用WCF技術将AppDomain對象丢給遠端通路者。 因為,這樣做可釋出對該 AppDomain 的遠端引用,将諸如 CreateInstance 方法向遠端通路公開,并有效損壞該 AppDomain 的代碼通路安全性。 連接配接到遠端 AppDomain 的惡意用戶端可以獲得對 AppDomain 本身可通路的所有資源的通路權。 您不應為任何以下類型建立可遠端控制的包裝:擴充 MarshalByRefObject 的類型和實作惡意用戶端可用來繞過安全系統的方法的類型。

應用程式域和程式集之間的關系

在可以執行程式集中所包含的代碼之前,必須将程式集加載到應用程式域中。在運作反射類型時,加載被引用的程式集是必須的。運作普通的應用程式會導緻将幾個程式集加載到一個應用程式域中。

程式集的加載方式決定其實時 (JIT) 編譯代碼是否可以在程序中由多個應用程式域共享,以及該程式集是否可以從程序中解除安裝。

  • 如果程式集是以非特定于域的形式進行加載,則共享相同安全授權集的所有應用程式域都可以共享相同的 JIT 編譯代碼,進而減少應用程式所需的記憶體。但是,程式集則永遠不能從程序中解除安裝。
  • 如果程式集不是以非特定于域的形式進行加載,則它必須在加載的每個應用程式域中都是 JIT 編譯的。但是,通過解除安裝程式集加載的所有應用程式域,可以從程序中解除安裝程式集。

運作時宿主決定在将運作時加載到程序中時是否以非特定于域的形式加載程式集。對于托管應用程式,将 LoaderOptimizationAttribute 特性應用于程序的入口點方法,并從關聯的 LoaderOptimization 枚舉指定一個值。 對于承載公共語言運作時的非托管應用程式,當您調用 CorBindToRuntimeEx 函數 方法時,指定适當的标志。

有三個選項用于加載非特定于域的程式集:

  • SingleDomain 不以非特定于域的形式加載任何程式集(Mscorlib 除外,它始終以非特定于域的形式加載)。 此設定稱作單域,因為它通常用在宿主隻運作程序中的單個應用程式時。
  • MultiDomain 以非特定于域的形式加載所有程式集。 此設定用于以下情況:程序中有多個應用程式域,所有這些應用程式域均運作相同的代碼。
  • MultiDomainHost 以非特定于域的形式加載強名稱程式集(如果它們以及它們的所有依賴項都已在全局程式集緩存中安裝)。 其他程式集都将針對它們加載的每個應用程式域分别進行加載和 JIT 編譯,進而可以從程序中解除安裝。如果您在同一程序中運作多個應用程式,或者如果您有混合的程式集,其中包括許多應用程式域共享的程式集和需要從程序中解除安裝的程式集,則可以使用此設定。

以下程式集不能共享 JIT 編譯代碼:使用 Assembly 類的 LoadFrom 方法加載到“加載源”上下文中的程式集,或者使用 Load 方法的重載(指定位元組數組)從圖像加載的程式集。

使用 Ngen.exe(本機映像生成器) 編譯為本機代碼的程式集如果在第一次加載到程序中時是以非特定于域的形式加載的,則可以在不同應用程式域之間共享這些程式集。

包含應用程式入口點的程式集的 JIT 編譯代碼隻有在其所有依賴項都可以被共享的情況下,才可以被共享。

非特定于域的程式集可以進行多次 JIT 編譯。例如,如果兩個應用程式域的安全授權集不同,則它們不能共享相同的 JIT 編譯代碼。但是,JIT 編譯程式集的每個副本都可以與其他具有相同授權集的應用程式域共享。

當您決定是否以非特定于域的形式加載程式集時,必須在減少記憶體占用和降低其他性能因素之間加以權衡。

  • 對于非特定于域的程式集,對靜态資料和方法的通路較慢的原因在于需要隔離程式集。通路該程式集的每一應用程式域都必須具有靜态資料的單獨副本,以避免跨域邊界引用靜态字段中的對象。是以,運作時包含附加的邏輯,用以将調用方引導到靜态資料或靜态方法的适當副本。這一額外的邏輯将降低調用速度。
  • 當以非特定于域的形式加載程式集時,必須找到并加載該程式集的所有依賴項,因為如果一個依賴項不能以非特定于域的形式加載,則會妨礙以非特定于域的形式加載程式集。

應用程式域和線程之間的關系

應用程式域為安全性、版本控制、可靠性和托管代碼的解除安裝形成隔離邊界。線程是公共語言運作時用來執行代碼的作業系統構造。在運作時,所有托管代碼均加載到一個應用程式域中,由托管線程來運作。

應用程式域和線程之間不具有一對一的相關性。在任意給定時間,在單個應用程式域中可以執行幾個線程,而且特定線程并不局限在單個應用程式域内。也就是說,線程可以自由跨越應用程式域邊界;不為每個應用程式域建立新線程。

在任意給定時間,每一線程都在一個應用程式域中執行。在任何給定的應用程式域内可能有零個、一個或多個線程正在執行。運作時會跟蹤在哪些應用程式域中有哪些線程正在運作。通過調用 GetDomain 方法,您可以随時确定線程執行所在的域。

可以向線程附加 CultureInfo 對象。但是,為了防止惡意代碼進入其他應用程式域, CultureInfo 對象在其線程跨越應用程式域邊界時自動被設定為隻讀。

如果對 CultureInfo 對象進行了自定義(例如使用自定義 Calendar),則會線上程嘗試跨越應用程式域邊界時引發 InvalidOperationException。

對應用程式域進行程式設計

應用程式域通常由運作時宿主以程式設計的方式來建立和操作。但是,有時應用程式還可能要和應用程式域結合起來使用。例如,應用程式可能将應用程式元件加載到域中以便能夠在不停止整個應用程式的情況下解除安裝域(以及該元件)。

AppDomain 類是應用程式域的程式設計接口。此類包括各種方法,這些方法可以建立和解除安裝域、建立域中各類型的執行個體以及注冊各種通知(如應用程式域解除安裝)。下表列出了常用的 AppDomain 方法。

CreateDomain 建立新的應用程式域。建議使用此方法指定 AppDomainSetup 對象的重載形式。 這是設定新域的各個屬性的首選方式,這些屬性包括應用程式基(即該應用程式的根目錄)、域的配置檔案的位置、以及公共語言運作時用于将程式集加載到域中的搜尋路徑等。

ExecuteAssembly 和 ExecuteAssemblyByName  執行應用程式域中的程式集。這是一個執行個體方法,是以它可用來執行另一個應用程式域(您擁有對該域的引用)中的代碼。

CreateInstanceAndUnwrap 在應用程式域中建立指定類型的執行個體,并傳回一個代理。使用此方法以避免将包含建立的類型的程式集加載到調用程式集中。

Unload 執行域的正常關閉。隻有應用程式域中正在運作的所有線程都已停止或域中不再有運作的線程之後,才解除安裝該應用程式域。 應用程式域可以在不停止整個程序的情況下解除安裝。宿主可以利用這一特點來解除安裝不再需要的代碼,進而減少記憶體占用并增加其應用程式的可縮放性。

System. AppDomain 類包含一個名為 Unload 的靜态方法,宿主可以使用該方法解除安裝特定的應用程式域。 AppDomain. Unload 執行的是正常關機,隻要存在任何活動線程,它就不會解除安裝應用程式域。 如果不關閉整個程序,就無法解除安裝預設應用程式域中加載的程式集,或以非特定于應用程式域的方式加載的程式集。

公共語言運作時不支援全局方法序列化,是以不能使用委托來執行其他應用程式域中的全局方法。

公共語言運作時承載接口規範中介紹的非托管接口也提供對應用程式域的通路。運作時宿主可以使用非托管代碼的接口在程序内建立應用程式域和擷取對這些應用程式域的通路。

運作時宿主

公共語言運作時已經過專門設計,支援各種類型的應用程式,包括從 Web 伺服器應用程式到具有傳統的豐富 Windows 使用者界面的應用程式在内的所有應用程式。每種應用程式都需要一個運作時宿主來啟動它。運作時宿主将該運作時加載到程序中,在該程序内建立應用程式域,并且将使用者代碼加載到該應用程式域中。

.NET Framework 附帶有多種不同的運作時宿主,包括下表中列出的宿主。

ASP.NET

将運作時加載到要處理 Web 請求的程序中。ASP.NET 還為将在 Web 伺服器上運作的每個 Web 應用程式建立一個應用程式域。

Microsoft Internet Explorer

建立要在其中運作托管控件的應用程式域。.NET Framework 支援下載下傳和執行基于浏覽器的控件。運作時通過 MIME 篩選器與 Microsoft Internet Explorer 的擴充性機制相連接配接,以建立要在其中運作托管控件的應用程式域。預設情況下,将為每個網站建立一個應用程式域。

繼續閱讀