Assembly.Unload


<script type="text/javascript"> </script>
http://flier_lu.blogone.net/?id=2164751


    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 的代码使用到的句柄和对现有托管代码的引用。例如现在 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


  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值