C# 有关Assembly.Unload详解

    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 中对类的卸载就是最好的例子,前面那些理由实际上都是工作量和复杂度方面的问题,并不存在无法解决的技术问题。

# re: AppDomain and Shadow Copy 4/30/2004 2:34 AM chornbe

You must also encapsulate the loaded assembly into another class, which is loaded by the new appdomain. Here's the code as it's working for me: (I've created a few custom exception types, and you'll notice I had them back - they're not descended from MarshalByRefObject so I can't just throw them from the encapsulated code)

--- cut first class file

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();
}
}
}

}

--- end cut

--- cut second class file

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

}
}

--- end cut

相关的一些资源:
Why isn't there an Assembly.Unload method?
http://blogs.msdn.com/jasonz/archive/2004/05/31/145105.aspx

AppDomains ("application domains")
http://blogs.msdn.com/cbrumme/archive/2003/06/01/51466.aspx

AppDomain and Shadow Copy
http://blogs.msdn.com/junfeng/archive/2004/02/09/69919.aspx

到此这篇关于C# 有关Assembly.Unload详解的文章就介绍到这了,更多相关C# 有关Assembly.Unload内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C# Assembly类访问程序集信息

    C#中通过Assembly类可以访问程序集信息. 1.允许访问给定程序集的元元素,包含可以加载和执行程序集的方法: 2.加载程序集:使用静态方法Assembly.Load(程序集名称)或Assembly.LoadFrom(程序集完整路径名): 3.属性: FullName:程序集显示名称: 3.方法: GetTypes():获取程序集中定义的类型. TestAssembly.cs: view plaincopy to clipboardprint? using System; using Sys

  • C# PropertyGrid使用案例详解

    1. 只有public的property能显示出来,可以通过BrowsableAttribute来控制是否显示,通过CategoryAttribute设置分类,通过DescriptionAttribute设置描述,Attribute可以加在Class上,也可以加在属性上,属性上的Attribute优先级更高: 2. enum会自动使用列表框表示: 3. 自带输入有效性检查,如int类型输入double数值,会弹出提示对话框: 4. 基本类型Array:增加删除只能通过弹出的集合编辑器,修改可以直

  • C# Assembly.Load案例详解

     我们在使用C# 语言的Assembly.Load 来加载托管程序集并使用反射功能时,一般需要先通过Assembly.Load(), Assembly.LoadFrom() 等方法将目标托管程序集加载到当前应用程序域中,然后生成对应实例,最后再进行调用实例的属性或者方法. 一般情况下,我们调用Assembly.Load 一类方法是不会出问题的,但是对于以下几种情况Assembly.Load 方法无法处理: 程序集可能是延迟签名的. 程序集可能被CAS 策略保护. 宿主程序与目标程序集的处理器架构

  • C# ThreadPool之QueueUserWorkItem使用案例详解

    先看代码: //设置可以同时处于活动状态的线程池的请求数目. bool pool = ThreadPool.SetMaxThreads(8, 8); if (pool) { ThreadPool.QueueUserWorkItem(o => this.DoSomethingLong("参数1")); ThreadPool.QueueUserWorkItem(o => this.DoSomethingLong("参数2")); ThreadPool.Que

  • C# web.config之<customErrors>节点说明案例详解

    <customErrors>节点用于定义一些自定义错误信息的信息.此节点有Mode和defaultRedirect两个属性,其中defaultRedirect属性是一个可选属性,表示应用程序发生错误时重定向到的默认URL,如果没有指定该属性则显示一般性错误.Mode属性是一个必选属性,它有三个可能值,它们所代表的意义分别如下: Mode 说明 On 表示在本地和远程用户都会看到自定义错误信息. Off 禁用自定义错误信息,本地和远程用户都会看到详细的错误信息. RemoteOnly 表示本地用

  • C# 有关Assembly.Unload详解

        CLR 产品单元经理(Unit Manager) Jason Zander 在前几天一篇文章 Why isn't there an Assembly.Unload method? 中解释了为什么 CLR 中目前没有实现类似 Win32 API 中 UnloadLibrary 函数功能的 Assembly.Unload 方法. 他认为之所以要实现 Assembly.Unload 函数,主要是为了回收空间和更新版本两类需求.前者在使用完 Assembly 后回收其占用资源,后者则卸载当前版本

  • spring boot的maven配置依赖详解

    本文介绍了spring boot的maven配置依赖详解,分享给大家,具体如下: 我们通过引用spring-boot-starter-parent,添加spring-boot-starter-web 可以实现web项目的功能,当然不使用spring-boot-start-web,通过自己添加的依赖包也可以实现,但是需要一个个添加,费时费力,而且可能产生版本依赖冲突.我们来看下springboot的依赖配置: 利用pom的继承,一处声明,处处使用.在最顶级的spring-boot-dependen

  • ABP(现代ASP.NET样板开发框架)系列之二、ABP入门教程详解

    ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开源项目:https://github.com/aspnetboilerplate ABP 的由来 "DRY--避免重复

  • 详解Java编写并运行spark应用程序的方法

    我们首先提出这样一个简单的需求: 现在要分析某网站的访问日志信息,统计来自不同IP的用户访问的次数,从而通过Geo信息来获得来访用户所在国家地区分布状况.这里我拿我网站的日志记录行示例,如下所示: 121.205.198.92 - - [21/Feb/2014:00:00:07 +0800] "GET /archives/417.html HTTP/1.1" 200 11465 "http://shiyanjun.cn/archives/417.html/" &qu

  • Java中的反射机制详解

    Java中的反射机制详解 反射,当时经常听他们说,自己也看过一些资料,也可能在设计模式中使用过,但是感觉对它没有一个较深入的了解,这次重新学习了一下,感觉还行吧! 一,先看一下反射的概念: 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 反射是Java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接.但是反射使用不当会成本很高! 看概念很晕的,继续往下

  • Spring整合MyBatis(Maven+MySQL)图文教程详解

    一. 使用Maven创建一个Web项目 为了完成Spring4.x与MyBatis3.X的整合更加顺利,先回顾在Maven环境下创建Web项目并使用MyBatis3.X,第一.二点内容多数是回顾过去的内容 . 1.2.点击"File"->"New"->"Other"->输入"Maven",新建一个"Maven Project",如下图所示: 1.2.请勾选"Create a si

  • Jquery基础之事件操作详解

    事件是用户操作时页面或页面加载时引发的用来完成javascript和HTML之间的交互操作.常见的元素点击事件.鼠标事件.键盘输入事件等,较传Javascript 相比JQuery增加并扩展了基本的事件处理机制,极大的增强了事件处理的能力. 一.DOM加载事件 页面加载完毕后浏览器会通过javascript为Dom元素加载事件,使用Javascript时候使用的是window.onload方法,而Jquery使用的是$(document).ready()方法,下表 展示两个事件的异同. wind

  • Java 反射机制详解及实例

    Java 反射机制详解及实例 反射,当时经常听他们说,自己也看过一些资料,也可能在设计模式中使用过,但是感觉对它没有一个较深入的了解,这次重新学习了一下,感觉还行吧!            一,先看一下反射的概念: 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 反射是Java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接.但是反射使用不当会成本很高

  • 详解C#编程获取资源文件中图片的方法

    详解C#编程获取资源文件中图片的方法 本文主要介绍C#编程获取资源文件中图片的方法,涉及C#针对项目中资源文件操作的相关技巧,以供借鉴参考.具体内容如下: 例子: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Drawing; namespace CL { public class RES { /

随机推荐