通過應用程式定義域AppDomain載入和卸載程式集

來源:互聯網
上載者:User

標籤:

微軟裝配車的大門似乎只為貨物裝載敞開大門,卻將卸載工人拒之門外。車門的鑰匙只有一把,若要獲得還需要你費一些心思。我在學習Remoting的時候,就遇到一個擾人的問題,就是Remoting為遠程對象僅提供Register的方法,如果你要登出時,只有另闢蹊徑。細心的開發員,會發現Visual Studio.Net中的反射機制,同樣面臨這個問題。你可以找遍MSDN的所有文檔,在Assembly類中,你永遠只能看到Load方法,卻無法尋覓到Unload的蹤跡。難道我們裝載了程式集後,就不能再將它卸載下來嗎?

想一想這樣一個情境。你通過反射動態載入了一個dll檔案,如今你需要在未關閉程式的情況下,刪除或覆蓋該檔案,那麼結果會怎樣?很遺憾,系統會提示你無法訪問該檔案。事實上該檔案正處於被調用的狀態,此時要對該檔案進行修改,就會出現爭用的情況。

顯然,為程式集提供卸載功能是很有必要的,但為什麼微軟在其產品中不提供該功能呢?CLR 產品單元經理(Unit Manager) Jason Zander 在文章 Why isn‘t there an Assembly.Unload method? 中解釋了沒有實現該功能的原因。Flier_Lu在其部落格裡(Assembly.Unload)有詳細的中文介紹。文中介紹瞭解決卸載程式集的折中方法。Eric Gunnerson在文章《AppDomain 和動態載入》中也提到:Assembly.Load() 通常運行良好,但程式集無法獨立卸載(只有 AppDomain 可以卸載)。Enrico Sabbadin 在文章《Unload Assemblies From an Application Domain》也有相關VB.Net實現該功能的相關說明。

尤其是Flier_Lu的部落格裡已經有了很詳細的代碼。不過,這些代碼沒有詳細地說明。我在我的項目中也需要這一項功能。這段代碼給了我很大的提示。但在實際的實現中,還是遇到一些具體的問題。所以我還是想再談談我的體會。

通過AppDomain來實現程式集的卸載,這個思路是非常清晰的。由於在程式設計中,非特殊的需要,我們都是運行在同一個應用程式定義域中。由於程式集的卸載存在上述的缺陷,我們必須要關閉應用程式定義域,方可卸載已經裝載的程式集。然而主程式域是不能關閉的,因此唯一的辦法就是在主程式域中建立一個子程式域,通過它來專門實現程式集的裝載。一旦要卸載這些程式集,就只需要卸載該子程式域就可以了,它並不影響主程式域的執行。

不過現在看來,最主要的問題不是子程式域如何建立,關鍵是我們必須實現一種機制,來達到兩個程式域之間完成通訊的功能。如果大家熟悉Remoting,就會想到這個問題不是和Remoting的機制有幾分相似之處嗎?那麼答案就可以呼之欲出了,對了,就是使用代理的方法!不過與Remoting不同的是兩個程式域之間的關係。因為子程式域是在主程式域中建立的,因此對該域的控制顯然就與Remoting不相同了。我想先用一副圖來表述實現的機制:

說明:
1、Loader類提供建立子程式域和卸載程式域的方法;
2、RemoteLoader類提供裝載程式集方法;
3、Loader類獲得RemoteLoader類的代理對象,並調用RemoteLoader類的方法;
4、RemoteLoader類的方法在子程式域中完成;
5、Loader類和RemoteLoader類均放在AssemblyLoader.dll組件檔中;

我們再來看代碼:
Loader類:

SetRemoteLoaderObject()方法:

private AppDomain domain = null;  private Hashtable domains = new Hashtable();    private RemoteLoader rl = null;public RemoteLoader SetRemoteLoaderObject(string dllName){    AppDomainSetup setup = new AppDomainSetup();                setup.ShadowCopyFiles = "true";    domain = AppDomain.CreateDomain(dllName,null,setup);                domains.Add(dllName,domain);        try    {                rl = (AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap(                "AssemblyLoader.dll","AssemblyLoader.RemoteLoader");             }    catch    {        throw new Exception();    }}

代碼中的變數rl為RemoteLoader類對象,在Loader類中是其私人成員。SetRemoteLoaderObject()方法實際上提供了兩個功能,一是建立了子程式域,第二則是獲得了RemoteLoader類對象。

請大家一定要注意語句:
rl = (AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap("AssemblyLoader.dll","AssemblyLoader.RemoteLoader");

這條語句就是實現兩個程式域之間通訊的關鍵。因為Loader類是在主程式域中,RemoteLoader類則是在子程式域中。如果我們在Loader類即主程式域中顯示執行個體化RemoteLoader類對象rl,此時調用rl的方法,實際上是在主程式域中調用的。因此,我們必須使用代理的方式,來獲得rl對象,這就是CreateInstanceFromAndUnwrap方法的目的。其中參數一為要建立類對象的組件檔名,參數二則是該類的類型名。

CreateCreateInstanceFromAndUnwrap方法有多個重載。代碼中的調用方式是當RemoteLoader類為預設建構函式時的其中一種重載。如果RemoteLoader類的建構函式有參數,則方法應改為:

object[] parms = {dllName};BindingFlags bindings = BindingFlags.CreateInstance |BindingFlags.Instance | BindingFlags.Public;rl = (AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap("AssemblyLoader.dll","AssemblyLoader.RemoteLoader",true,bindings,null,parms,null,null,null);

詳細的調用方式可以參考MSDN。

以下Loader類的Unload方法和LoadAssembly方法():

public Assembly LoadAssembly(string dllName){    try    {        SetRemoteLoaderObject(dllName);        return rl.LoadAssembly(dllName);    }    catch (Exception)    {        throw new AssemblyLoadFailureException();    }}

 

public void Unload(string dllName){    if (domains.ContainsKey(dllName))    {        AppDomain appDomain = (AppDomain)domains[dllName];        AppDomain.Unload(appDomain);        domains.Remove(dllName);    }            }

當我們調用Unload方法時,則程式域domain載入的程式集也將隨著而被卸載。LoadAssembly方法中的異常AssemblyLoadFailureException為自訂異常:

public class AssemblyLoadFailureException:Exception    {        public AssemblyLoadFailureException():base()        {                    }        public override string Message        {            get            {                return "Assembly Load Failure";            }        }    }

既然在Loader類獲得的RemoteLoader類執行個體必須通過代理的方式,因此該類對象必須支援被序列化。所以我們可以令該類派生MarshalByRefObject。RemoteLoader類的代碼:

public class RemoteLoader:MarshalByRefObject    {        public RemoteLoader(string dllName)        {            if (assembly == null)            {                assembly = Assembly.LoadFrom(dllName);            }        }                private Assembly assembly = null;        public Assembly LoadAssembly(string dllName)        {            try            {                assembly = Assembly.LoadFrom(dllName);                                return assembly;            }            catch (Exception)            {                throw new AssemblyLoadFailureException();            }        }    }

通過上述的兩個類,我們就可以實現程式集的載入和卸載。另外,為了保證應用程式定義域的對象在記憶體中被清除,應該令這兩個類都實現IDisposable介面,和實現Dispose()方法。

然而在實際的操作過程中,我發現在RemoteLoader類的LoadAssembly方法,是存在遺患的。在我的LoadAssembly方法中,會返回一個Assembly對象。令我百思不得其解的是,雖然都是Assembly對象,但在載入某些程式集並返回Assembly時,在Loader類中會拋出SerializationException異常,並報告還原序列化的對象狀態不足。這個異常是在序列化獲還原序列化過程中發生的。我反覆比較了兩個程式集,一個可以正常載入並序列化,一個會拋出如上異常。會拋出異常的程式集並沒有什麼特殊之處,且我在程式中的其他地方也沒有重複載入該程式集。這是一個疑問!!

不過通常我們在RemoteLoader類中,要實現的方法並非返回一個Assembly對象,而是通過反射載入程式集後,建立該程式集的對象。由於類對象都為object類型,此時序列化就不會出現問題。在我的項目中,因為要獲得程式集的版本號碼,比較版本號碼在確定是否需要更新,因此我在RemoteLoader類中,只需要在載入程式集後,返回程式集的版本號碼字串類型就可以了。字串類型是絕對支援序列化的。

AssemlbyLoader.Dll的原始碼可以點擊這裡獲得。在應用程式中,顯示添加對該程式集的引用,然後執行個體化Loader類對象,來調用該方法即可。我還做了一個簡單的測試程式,用的是LoadAssembly方法。大家可以測試一下,是否如我所說,對於某些程式集,可能會拋出序列化的異常!?

測試的代碼請點擊這裡獲得,測試介面如下:

同時,大家也可以測試一下,直接載入和通過AppDomain載入,刪除組件檔時會有什麼區別?

原文連結:

通過應用程式定義域AppDomain載入和卸載程式集

 

通過應用程式定義域AppDomain載入和卸載程式集

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.