天天看點

C#.Net 如何動态加載與解除安裝程式集(.dll或者.exe)4-----Net下的AppDomain程式設計 [摘錄]

最近在對AppDomain程式設計時遇到了一個問題,解除安裝AppDomain後,在記憶體中還保留它加載的DLL的資料,是以即使解除安裝掉AppDomain,還是無法更新它加載的DLL.看來隻有關閉整個程序來更新DLL了.

--------------------------------------------------------------------------------------------------------------

    我們知道,程序是作業系統用于隔離衆多正在運作的應用程式的機制。在.Net之前,每一個應用程式被加載到單獨的程序中,并為該程序指定私有的虛拟記憶體。程序不能直接通路實體記憶體,作業系統通過其它的處理把這些虛拟記憶體映射到實體記憶體或IO裝置的某個區域,而這些實體記憶體之間不會有重疊,這就決定了一個程序不可能通路配置設定給另一個程序的記憶體。相應地,運作在該程序中的應用程式也不可能寫入另一個應用程式的記憶體,這確定了任何執行出錯的代碼不會損害其位址空間以外的應用程式。在這種機制下,程序作為應用程式之間一個獨立而安全的邊界在很大程度上提高了運作安全。 

程序的缺點是降低了性能。許多一起工作的程序需要互相通信,而程序卻不能共享任何記憶體,你不能通過任何有意義的方式使用從一個程序傳遞到另一個程序的記憶體指針。此外,你不能在兩個程序間進行直接調用。你必須代之以使用代理,它提供一定程度的間接性。雖然,使用動态連接配接庫dll讓所有的元件運作在同一空間,一定程度上可以提高性能,但這些元件互相影響,一個元件的錯誤将極有可能導緻整個應用程式的崩潰,“dll地獄”更是讓許多應用程式難以避免。 

應用程式域(AppDomain)

在.Net中,應用程式有了一個新的邊界:應用程式域(以下簡稱域)。它是一個用于隔離應用程式的虛拟邊界。為了禁止不應互動的代碼進行互動,這種隔離是必要的。.Net的應用程式在域層次上進行隔離,一個域中的應用程式不能直接通路另一個域中的代碼和資料。這種隔離使得在一個應用程式範圍内建立的所有對象都在一個域内建立,確定在同一程序中一個域内運作的代碼不會影響其他域内的應用程式,大大提高了運作的安全。 

.Net結構中,由于公共語言運作庫能夠驗證代碼是否為類型安全的代碼,是以它可以提供與程序邊界一樣大的隔離級别,其性能開銷也要低得多。你可以在單個程序中運作幾個域,而不會造成程序間調用或切換等方面的額外開銷。這種方法是把任何一個程序分解到多個域中,允許多個應用程式在同一程序中運作,每個域大緻對應一個應用程式,運作的每個線程都在一個特殊的域中。如果不同的可執行檔案都運作在同一個程序空間中,它們就能輕松地共享資料或直接通路彼此的資料。這種代碼同運作同一個程序但域不同的類型安全代碼一起運作時是安全的。在一個程序内運作多個應用程式的能力顯著增強了伺服器的可伸縮性。 

域間通信

域是.Net 帶來的一個重要改進,它不僅将衆多在運作的應用程式隔離開來,還不影響彼此間通信。雖然,公共語言運作庫禁止在不同域中的對象之間進行直接調用,但我們可以複制這些對象,或通過代理通路這些對象。如果以前一種方式,那麼對該對象的調用為本地調用。也就是說,調用方和被引用的對象位于同一域中。如果通過代理通路對象,調用方和被引用的對象位于不同的域中,對該對象的調用被視為遠端調用,這種情形與兩個程序間的調用或兩台計算機間的調用結構大緻相同。這時,需要被引用對象的中繼資料對于兩個域均可用,以便.Net即時編譯JIT能正确執行。 

域與線程的關系

在.Net中,線程是公共語言運作庫用來執行代碼的作業系統構造。在運作時,所有托管代碼均加載到一個域中,由特定的作業系統線程來運作。然而,域和線程之間并不具有一一對應關系。在任意給定時間,單個域中可以執行不止一個線程,而且特定線程也并不局限在單個域内。也就是說,線程可以跨越域邊界,不為每個域建立新線程。當然,在指定時刻,每一線程都隻能在一個域中執行。運作庫會跟蹤所有域中有哪些線程正在運作。通過調用.Net類庫的 Thread.GetDomain 方法,你還可以确定正在執行的線程所在的域。 

域的建立

作為公共語言運作庫的隔離單元,域在程序中建立和運作。.Net結構中,運作時宿主(也叫作運作時主機)是負責将運作時載入程序并在域中執行使用者代碼和托管代碼的應用程式。運作時宿主包括ASP.Net、浏覽器Internet Explorer 和 Windows等外殼程式,負責建立程序和預設域,例如,Asp.Net為每個運作在web伺服器上的web應用程式建立一個域。浏覽器Internet explore建立運作受管制控件的域。 

對多數應用程式,你并不必須建立相應的域,每次CLR在初始化一個程序時,将建立預設域,并使該程序運作于這個預設域下。然而,預設域不能由任何系統調用來解除安裝,該域隻有在程序被解除安裝之後才能被銷毀。如果直接在預設域下程式設計或運作代碼,而由于某種原因域的代碼崩潰了,那麼就有使得整個服務随之崩潰的風險。 

于是,針對不同的應用程式,應該建立和配置相應的域并載入适當的程式集。.Net為此提供了豐富的類庫。其中,AppDomain 類是域的程式設計接口,其大量的(重載)方法能完成以下任務: 

· 建立域 

· 在域中加載程式集和類型 

· 枚舉域中的程式集和線程 

· 解除安裝域 

建立新域時,使用AppDomain 類的靜态方法CreateDomain。你可以為域命名并按該名稱來引用域。下面的示例語句建立新域,并為它指定名稱 MyDomain: 

<ccid_code>AppDomain myDomain = AppDomain.CreateDomain("MyDomain");      

<ccid_nobr>

然後你可以查詢目前域的名稱和新建立子域的名稱: 

<ccid_code>string hostDomain=AppDomain.CurrentDomain.FriendlyName;
            string childDomain=myDomain.FriendlyName;      

<ccid_nobr>

在這裡,屬性FriendlyName表示的是域的友好名稱,友好名稱通過從程式集的基本代碼中去除目錄路徑而形成。例如,檔案名為 "d:\MyAppDomain\MyAssembly.exe" 的程式集加載到預設域中,域的友好名稱就是 "MyAssembly.exe"。 

更一般的是,在建立域之前,先設定好域的參數,這可以通過類AppDomainSetup來完成。該類的ApplicationBase 屬性定義應用程式的根目錄, AppDomainSetup 類還有一個極重要的屬性變量LoaderOptimizzation,取值可以是MultiDomain,MultiDomainHost和SignleDomain等,用以指定被加載程式集的類别(共享程式集或域專用程式集),例如,以下語句把程式集設定為域專用程式集: 

<ccid_code>appDomainSetup.LoaderOptimization=LoaderOptimizatiion.SigleDomain;      

<ccid_nobr>

對以上兩個方面簡單歸納一下,對域的典型操作就包括:設定參數然後建立兩個步驟,語句示例如下: 

<ccid_code>AppDomainSetup appDomainSetup=new AppDomainSetup();//執行個體化域設定
            appDomainSetup.LoaderOptimization=LoaderOptimization.SingleDomain; //指定域類别
            AppDoman ad=AppDomain.CreateDomain(domainName,appDomainSetup); //建立域
            ...
            //應用程式在這裡運作代碼
            ...
            AppDomain.Unload(ad);//解除安裝域      

<ccid_nobr>

解除安裝域

當使用完域時,可使用AppDomain類Unload()靜态方法将其解除安裝。要解除安裝程序中在運作的托管代碼,隻能解除安裝代碼運作時所在的域而不能解除安裝單獨的程式集或類型,Unload方法會正常關閉指定的域。這時,載入域的所有程式集都會被移除,并且無法再使用。不過,如果域中的程式集對域是非特定的(域無關程式集,也即共享程式集),則程式集的資料會保留在記憶體中,直到整個程序關閉。除了關閉整個程序,沒有機制可以解除安裝非特定于域的程式集。在某些情況下,解除安裝應用程式域的請求不起作用,并導緻 CannotUnloadAppDomainException。由于一個程序中允許包含多個域,某個域可以在不停止整個程序的情況下解除安裝。以這樣的方式解除安裝不再需要的代碼,可以減少記憶體占用并極大提高應用程式的可縮放性。此外,由于線程并不與域一一對應,當域中存在活動線程時,調用AppDomain.Unload方法可能無法将域解除安裝并導緻異常。

另外,ICorRuntimeHost 接口包括一個名為 Stop 的方法,宿主可以使用該方法從程序中強制解除安裝運作庫。當調用 Stop 時,将立即解除安裝所有域(包括預設域和所有非特定于域的代碼),并從程序中全部移除運作庫。當對程序調用 Stop 後,不能将運作庫加載回該程序。要再次開始運作托管代碼,必須建立一個新的程序。

在域中加載程式集

<ccid_code>Assembly SampleAssembly;
            …
            SampleAssembly = Assembly.Load("System.Data");//根據類型加載程式集      
<ccid_code>Assembly SampleAssembly;
            …
            SampleAssembly = Assembly.LoadFrom("c:\\Sample.Assembly.dll");//根據已有程式集名稱加載      

繼續閱讀