Assembly.Unload

來源:互聯網
上載者:User
http://www.blogcn.com/user8/flier_lu/index.html?id=2164751&run=.04005F8

CLR 產品單元經理(Unit Manager) Jason Zander 在前幾天一篇文章 Why isn't there an Assembly.Unload method? 中解釋了為什麼 CLR 中目前沒有實作類別似 Win32 API 中 UnloadLibrary 函數功能的 Assembly.Unload 方法。
    他認為之所以要實現 Assembly.Unload 函數,主要是為了回收空間和更新版本兩類需求。前者在使用完 Assembly 後回收其佔用資源,後者則卸載目前的版本載入更新的版本。例如 ASP.NET 中對頁面用到的 Assembly 程式的動態更新就是一個很好的使用樣本。但如果提供了 Assembly.Unload 函數會引發一些問題:

    1.為了封裝 CLR 中代碼所引用的代碼地址都是有效,必須跟蹤諸如 GC 對象和 COM CCW 之類的特殊應用。否則會出現 Unload 一個 Assembly 後,還有 CLR 對象或 COM 組件使用到這個 Assembly 的代碼或資料地址,進而導致訪問異常。而為了避免這種錯誤進行的跟蹤,目前是在 AppDomain 一級進行的,如果要加入 Assembly.Unload 支援,則跟蹤的粒度必須降到 Assembly 一級,這雖然在技術上不是不能實現,但代價太大了。

    2.如果支援 Assembly.Unload 則必須跟蹤每個 Assembly 的代碼使用到的控制代碼和對現有Managed 程式碼的引用。例如現在 JITer 在編譯方法時,產生代碼都在一個統一的地區,如果要支援卸載 Assembly 則必須對每個 Assembly 都進行獨立編譯。此外還有一些類似的資源使用問題,如果要分離跟蹤技術上雖然可行,但代價較大,特別是在諸如 WinCE 這類資源有限的系統上問題比較明顯。

    3.CLR 中支援跨 AppDomain 的 Assembly 載入最佳化,也就是 domain neutral 的最佳化,使得多個 AppDomain 可以共用一份代碼,加快載入速度。而目前 v1.0 和 v1.1 無法處理卸載 domain neutral 類型代碼。這也導致實現 Assembly.Unload 完整語義的困難性。

    基於上述問題, Jason Zander 推薦使用其他的設計方法來迴避對此功能的使用。如 Junfeng Zhang 在其 BLog 上介紹的 AppDomain and Shadow Copy,就是 ASP.NET 解決類似問題的方法。

    在構造 AppDomain 時,通過 AppDomain.CreateDomain 方法的 AppDomainSetup 參數中 AppDomainSetup.ShadowCopyFiles 設定為 "true" 啟用 ShadowCopy 策略;然後設定 AppDomainSetup.ShadowCopyDirectories 為複製目標目錄;設定 AppDomainSetup.CachePath + AppDomainSetup.ApplicationName 指定緩衝路徑和檔案名稱。
    通過這種方法可以類比 Assembly.Unload 的語義。實現上是將需要管理的 Assembly 載入到一個動態建立的 AppDomain 中,然後通過跨 AppDomain 的透明代理調用其功能,使用 AppDomain.Unload 實現 Assembly.Unload 語義的類比。chornbe 給出了一個簡單的封裝類,具體代碼見文章末尾。
    
    這樣做雖然在語義上能夠基本上類比,但存在很多問題和代價:
    
    1.效能:在 CLR 中,AppDomain 是類似作業系統進程的邏輯概念,跨 AppDomain 通訊就跟以前跨進程通訊一樣受到諸多限制。雖然通過透明代理對象能夠實作類別似跨進程 COM 對象調用的功能,自動完成參數的 Marshaling 操作,但必須付出相當的代價。Dejan Jelovic給出的例子(Cross-AppDomain Calls are Extremely Slow)中,P4 1.7G 下只使用內建類型的調用大概需要 1ms。這對於某些需要被頻繁調用的函數來說代價實在太大了。如他提到實現一個繪圖的外掛程式,在 OnPaint 裡面畫 200 個點需要 200ms 的調用代價。雖然可以通過批量調用進行最佳化,但跨 AppDomain 調用效率的懲罰是肯定無法逃脫的。好在據說 Whidbey 中,對跨 AppDomain 調用中的內建類型,可以做不 Marshal 的最佳化,以至於達到比現有實現調用速度快 7 倍以上,...,我不知道該誇獎 Whidbey 實現的好呢,還是痛罵現有版本之爛,呵呵
    
    2.易用性:需要單獨卸載的 Assembly 中類型可能不支援 Marshal,此時就需要自行處理類型的管理。
    
    3.版本:在多個 AppDomain 中如何封裝版本載入的正確性。
    
    此外還有安全方面問題。對普通的 Assembly.Load 來說,載入的 Assembly 是運行在載入者的 evidence 下,而這絕對是一個安全隱患,可能遭受類似 unix 下面通過溢出以 root 許可權讀寫檔案的程式來改寫系統檔案的類似攻擊。而單獨在一個 AppDomain 中載入 Assembly 就能夠單獨設定 CAS 許可權,降低執行許可權。因為 CLR 架構下的四級許可權控制機制,最細的粒度只能到 AppDomain。好在據說 Whidbey 會加入對使用不同 evidence 載入 Assembly 的支援。
    
    通過這些討論可以看到,Assembly.Unload 對於基於外掛程式模型的程式來說,其語義的存在是很重要的。但在目前和近幾個版本來說,通過 AppDomain 來類比其語義是比較合適的選擇,雖然要付出效能和易用性的問題,但能夠更大程度上控制功能和安全性等方面因素。長遠來說,Assembly.Unload 的實現是完全可行的,Java 中對類的卸載就是最好的例子,前面那些理由實際上都是工作量和複雜度方面的問題,並不存在無法解決的技術問題。

以下為引用:

// ObjectLoader.cs
using System;
using System.Reflection;
using System.Collections;

namespace Loader{

  /* contains assembly loader objects, stored in a hash
  * and keyed on the .dll file they represent. Each assembly loader
  * object can be referenced by the original name/path and is used to
  * load objects, returned as type Object. It is up to the calling class
  * to cast the object to the necessary type for consumption.
  * External interfaces are highly recommended!!
  * */
  public class ObjectLoader : IDisposable
  {
    // essentially creates a parallel-hash pair setup
    // one appDomain per loader
    protected Hashtable domains = new Hashtable();
    // one loader per assembly DLL
    protected Hashtable loaders = new Hashtable();

    public ObjectLoader() {/*...*/}

    public object GetObject( string dllName, string typeName, object[] constructorParms )
    {
      Loader.AssemblyLoader al = null;
      object o = null;

      try{
        al = (Loader.AssemblyLoader)loaders[ dllName ];
      } catch (Exception){}

      if( al == null )
      {
        AppDomainSetup setup = new AppDomainSetup();
        setup.ShadowCopyFiles = "true";

        AppDomain domain = AppDomain.CreateDomain( dllName, null, setup );

        domains.Add( dllName, domain );

        object[] parms = { dllName };
        // object[] parms = null;
        BindingFlags bindings = BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public;

        try{
          al = (Loader.AssemblyLoader)domain.CreateInstanceFromAndUnwrap(
            "Loader.dll", "Loader.AssemblyLoader", true, bindings, null, parms, null, null, null);
        } catch (Exception){
          throw new AssemblyLoadFailureException();
        }

        if( al != null )
        {
          if( !loaders.ContainsKey( dllName ) )
          {
            loaders.Add( dllName, al );
          }
          else
          {
            throw new AssemblyAlreadyLoadedException();
          }
        }
        else
        {
          throw new AssemblyNotLoadedException();
        }
      }

      if( al != null )
      {
        o = al.GetObject( typeName, constructorParms );

        if( o != null && o is AssemblyNotLoadedException )
        {
          throw new AssemblyNotLoadedException();
        }

        if( o == null || o is ObjectLoadFailureException )
        {
          string msg = "Object could not be loaded. Check that type name " + typeName +
            " and constructor parameters are correct. Ensure that type name " + typeName +
            " exists in the assembly " + dllName + ".";

          throw new ObjectLoadFailureException( msg );
        }
      }
      return o;
    }

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

    ~ObjectLoader()
    {
      dispose( false );
    }

    public void Dispose()
    {
      dispose( true );
    }

    private void dispose( bool disposing )
    {
      if( disposing )
      {
        loaders.Clear();

        foreach( object o in domains.Keys )
        {
          string dllName = o.ToString();
          Unload( dllName );
        }
        domains.Clear();
      }
    }
  }
}

以下為引用:

// Loader.cs
using System;
using System.Reflection;

namespace Loader {
  // container for assembly and exposes a GetObject function
  // to create a late-bound object for casting by the consumer
  // this class is meant to be contained in a separate appDomain
  // controlled by ObjectLoader class to allow for proper encapsulation
  // which enables proper shadow-copying functionality.
  internal class AssemblyLoader : MarshalByRefObject, IDisposable {

    #region class-level declarations
    private Assembly a = null;
    #endregion

    #region constructors and destructors
    public AssemblyLoader( string fullPath )
    {
      if( a == null )
      {
        a = Assembly.LoadFrom( fullPath );
      }
    }

    ~AssemblyLoader()
    {
      dispose( false );
    }

    public void Dispose()
    {
      dispose( true );
    }

    private void dispose( bool disposing )
    {
      if( disposing )
      {
        a = null;
        System.GC.Collect();
        System.GC.WaitForPendingFinalizers();
        System.GC.Collect( 0 );
      }
    }
    #endregion

    #region public functionality
    public object GetObject( string typename, object[] ctorParms )
    {
      BindingFlags flags = BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public;

      object o = null
      ;
      if( a != null )
      {
        try
        {
          o = a.CreateInstance( typename, true, flags, null, ctorParms, null, null );
        }
        catch (Exception)
        {
          o = new ObjectLoadFailureException();
        }
      }
      else
      {
        o = new AssemblyNotLoadedException();
      }
      return o;
    }

    public object GetObject( string typename )
    {
      return GetObject( typename, null );
    }
    #endregion

  }
}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.