背景

任何一门语言都要了解其类型加载过程,如:Java 的 Class Loader,NodeJS 的搜索方式等,本文概述一下我对 CLR 如何加载程序集,重点说一下 Load Context。

其编译时只是在程序集中生成了元数据(如:依赖的程序集标识)和代码。当代码执行时,CLR 会根据元数据加载依赖的程序集。

Load Context

参考文章:http://msdn.microsoft.com/en-us/library/dd153782(v=vs.110).aspx

Assembly 会被加载到三个 Load Context 中的任意一个,或者没有在任何上下文中,这三个 Load Context 的名字叫:Default Context、Load-From Context 和 Relfection-Only Context,另外一个没有在任何上下文,可以叫:No Context。

每一个 Load Context 都有自己加载依赖程序集的规则,在解释每个 Load Context 解析依赖的规则之前,先看一下 CLR 的探测过程。

CLR 探测过程

弱签名程序集的探测过程

代码

 Console.WriteLine(Type.GetType("B.ClassB, B"));

文化中立程序集的探测优先级

 BaseDirectory\B.dll
BaseDirectory\B\B.dll
BaseDirectory\B.exe
BaseDirectory\B\B.exe

文化相关程序集的探测优先级

 BaseDirectory\zh-CN\B.dll
BaseDirectory\B\zh-CN\B.dll
BaseDirectory\zh-CN\B.exe
BaseDirectory\B\zh-CN\B.exe

使用 probing 指定 privatePath

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="B_" />
</assemblyBinding>
</runtime>
</configuration>

指定 privatePath 后的程序集探测优先级

 BaseDirectory\B.dll
BaseDirectory\B\B.dll
BaseDirectory\_B\B.dll
BaseDirectory\_B\B\B.dll
BaseDirectory\B.exe
BaseDirectory\B\B.exe
BaseDirectory\_B\B.exe
BaseDirectory\_B\B\B.exe

注:pirvatePath 只能定义为 BaseDirectory 的子目录。

注:上例只是列出了文化中立程序集的探测优先级,文化相关程序集的探测优先级应该不难猜测出来。

注:也可以使用 codeBase 配置元素来指定探测的子目录,这里就不介绍了。

强签名程序集的探测过程

强签名程序集会先探测 GAC,如果在 GAC 中没有找到,就按照弱签名程序集的探测过程执行探测。

注:在探测之前 CLR 会根据应用程序配置文件、发布者策略文件和全局配置文件中的配置获取重定向后的版本,然后使用这个重定向后的版本执行探测。

注:强签名程序集的 codeBase 可以指定任意目录(包括 Web 地址)。

Default Context

使用探测过程加载的程序集都会加载在 Default Context 中,Default Context 中的程序集具备如下特点:

  • 依赖的程序集会使用探测过程被自动加载到 DefaultContext。
  • 只包含探测过程可以探测到的程序集。
  • 其它 Load Context 加载的程序集对于探测过程是不可用的。

Load-From Context

使用 Assembly.LoadFrom 或 Assembly.Load 的某些重载方法加载的程序集时,如果参数使用了基于路径的形式,而非基于标识的形式,那么加载的程序集会加载在 Load-From Context 中。

Load-From Context 中的程序集具备如下特点:

  • 如果 Load-From Context 中已经包含了要加载的程序集(标识相同),就不会加载两次,即使两次加载的路径不同,下面的例子输出的数值是一样的:

     Assembly.LoadFrom(@"E:\Coding\HappyStudy\AssemblyStudy\Main\bin\Debug\G_\G.dll");
    PrintAssemlyCount();
    Assembly.LoadFrom(@"E:\Coding\HappyStudy\AssemblyStudy\Main\bin\Debug\G__\G.dll");
    PrintAssemlyCount();
  • Load-From Context 中的程序集对于探测过程是不可用的,下面的例子输出为 null:
     Assembly.LoadFrom(@"E:\Coding\HappyStudy\AssemblyStudy\Main\bin\Debug\G_\G.dll");
    Console.WriteLine(Type.GetType("G.ClassG, G"));

    注:G.DLL 不要在探测路径上。

  • Load-From Context 和 Default Context(或者其它Context)都可以同时加载相同标识的程序集,但是程序集内的类型已经不是相同的类型了(虽然内容一样 ),如下例输出为 false:
     var ass = Assembly.LoadFrom(@"E:\Coding\HappyStudy\AssemblyStudy\Main\bin\Debug\G_\G.dll");
    Console.WriteLine(Type.GetType("G.ClassG, G") == ass.GetTypes().First(x => x.Name == "ClassG"));

    注:G.DLL 不要在探测路径上。

  • 依赖的程序集会自动加载,可以从 Default Context 加载,也可以从 Load-Form Context 维护的 Path 下加载,算法后面单独分析,见下例:

     using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks; namespace H
    {
    public class ClassH
    {
    public ClassH()
    {
    Console.WriteLine("自动加载A:" + Type.GetType("A.ClassA, A").Assembly.Location);
    Console.WriteLine("自动加载I:" + Type.GetType("I.ClassI, I").Assembly.Location);
    }
    }
    }

    在 Main 中执行如下代码:

     var hAss = Assembly.LoadFrom(@"E:\Coding\HappyStudy\AssemblyStudy\H\bin\Debug\H.dll");
    
     Activator.CreateInstance(hAss.GetTypes().First(x => x.Name == "ClassH"));

    注:上例中 ClassH 依赖的两个类型都得到正常的加载了。

Load-From Context 中的程序集的依赖程序集的加载算法如下:

注:依赖的程序集的依赖的程序集的算法同上。

No Context

通过 Assembly.LoadFile 或 Assembly.Load(byte[]) 形式加载的程序集会加载在 No Context 中,No Context 中的程序集具备如下特点:

  • 依赖的程序集会自动加载,只会从 Default Context 加载,可以监听这个事件:AppDomain.CurrentDomain.AssemblyResolve,在事件监听器里手工加载其它依赖。
    下面这个例子会出现异常:

                 Assembly
    .LoadFile(@"E:\Coding\HappyStudy\LoadContextStudy\Test\bin\Debug\Plugs\Implements\1.0.0.0\Contracts.dll");
    var operatorType = Assembly
    .LoadFile(@"E:\Coding\HappyStudy\LoadContextStudy\Test\bin\Debug\Plugs\Implements\1.0.0.0\Implements.dll")
    .GetTypes()
    .First(x => x.Name == "Operator");
    var operatorInstance = Activator.CreateInstance(operatorType);

    注:Implements.dll 依赖 Contracts.dll。如果 Contracts.dll 包含在探测路径中,则程序会正常执行,不过会加载两个 Contracts.dll。

  • 只要路径不同,会返回多个相同程序集标识的程序集实例,如下例:
                 Assembly.LoadFile(@"E:\Coding\HappyStudy\AssemblyStudy\Main\bin\Debug\F_\F.dll");
    PrintAssemlyCount();
    Assembly.LoadFile(@"E:\Coding\HappyStudy\AssemblyStudy\Main\bin\Debug\F__\F.dll");
    PrintAssemlyCount();

Relfection-Only Context

只有开发工具是才会用到,这里就不介绍了。

参考资料

备注

说了挺多的,有啥用处呢?其实 Load Context + AppDomain.CurrentDomain.AssemblyResolve 可以模拟 Java 的 ClassLoader,也可以进一步的模拟 OSGI,下篇文章写个简单的 Demo,不过微软不推荐用这种方式,推荐使用 AppDomain。

.NET:鲜为人知的 “Load Context”的更多相关文章

  1. Load ContextCLR 探测

    目录 背景Load ContextCLR 探测过程弱签名程序集的探测过程强签名程序集的探测过程Default ContextLoad-From ContextNo ContextRelfection- ...

  2. Assembly中Load, LoadFrom, LoadFile以及AppDomain, Activator类中相应函数的区别

    Assembly和AppDomain的一些关于动态加载程序集的函数有些令人头疼,但细细研究后还是可以将他们区分的. 这些函数大致可以分为四类: 第一类:加载到Load Context内 Load Co ...

  3. Could not load file or assembly 'System.Web.Http

    使用FusLogVw https://stackoverflow.com/questions/4469929/could-not-load-file-or-assembly-or-one-of-its ...

  4. 分享个 之前写好的 android 文件流缓存类,专门处理 ArrayList、bean。

    转载麻烦声明出处:http://www.cnblogs.com/linguanh/ 目录: 1,前序 2,作用 3,特点 4,代码 1,前序  在开发过程中,client 和 server 数据交流一 ...

  5. EF里单个实体的增查改删以及主从表关联数据的各种增删 改查

    本文目录 EF对单个实体的增查改删 增加单个实体 查询单个实体 修改单个实体 删除单个实体 EF里主从表关联数据的各种增删改查 增加(增加从表数据.增加主从表数据) 查询(根据主表找从表数据.根据从表 ...

  6. Notepad++源码编译及其分析

    Notepad++是一个小巧精悍的编辑器,其使用方法我就不多说了,由于notepad++是使用c++封装的windows句柄以及api来实现的,因此对于其源码的研究有助于学习如何封装自己简单的库(当然 ...

  7. 《Entity Framework 6 Recipes》中文翻译系列 (28) ------ 第五章 加载实体和导航属性之测试实体是否加载与显式加载关联实体

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-11  测试实体引用或实体集合是否加载 问题 你想测试关联实体或实体集合是否已经 ...

  8. spring boot源码分析之SpringApplication

    spring boot提供了sample程序,学习spring boot之前先跑一个最简单的示例: /* * Copyright 2012-2016 the original author or au ...

  9. Spring Boot启动流程详解(一)

    环境 本文基于Spring Boot版本1.3.3, 使用了spring-boot-starter-web. 配置完成后,编写了代码如下: @SpringBootApplication public ...

随机推荐

  1. win7下scheme环境配置

    运行lisp方言--scheme,在windows下,用eclipse. 1.eclipse装好 2.eclipse安装插件scheme48 development took, http://www. ...

  2. Error_code: 1594(mysql 5617)主从同步报错

    报错信息 2017-09-05 09:37:22 7425 [ERROR] Slave SQL: Relay log read failure: Could not parse relay log e ...

  3. OS X 配置 Apache

    1.去掉javascript:void(0);Include /private/etc/apache2/extra/httpd-vhosts.con的注释,以启用虚拟主机: 2.在<Direct ...

  4. 学习Nodejs的第一步

    最近看了几本关于Node.js的书,本来个人技术分享网站http://yuanbo88.com/是打算用Node.js作为服务器端语言来处理后台的,后来又改成了PHP(也是自己研究,毕竟网上DEMO多 ...

  5. 004 关于Java如何扫描指定package下所有的类

    q前言: 在工作中看到这个知识点,就顺便参考了百度的一些资料,整理一下,希望以后用的到. 一:理论部分 1.使用场景 写一个MVC框架,需要从包中扫描出组件并注册到容器中,而JDK没有提供现成的从方法 ...

  6. 全文搜索引擎 Elasticsearch (三)logstash-input-jdbc同步数据 到elasticsearch

    参考链接: 1, 源码地址,官方介绍 2, logstash-input-jdbc使用建议 3, 官网介绍例子,使用 logstash-input-jdbc 到 elasticsearch 一.安装 ...

  7. OpenGL笔记<5> shader 调试信息获取 Debug

    我们今天来讲调试信息,这个东西讲起来会比较无聊,因为都是一些函数调用,没啥可讲的,函数就是那样用的,不过其效果挺好玩的,同时在程序设计中也是很必要的,所以还是来写一下,不过,就是因为知识比较固定且简单 ...

  8. [代码审计]DM企业建站系统v201710 sql注入漏洞分析 | 新版v201712依旧存在sql注入

    0x00 前言 本来呢,这套CMS都不想审的了.下载下来打开一看,各种debug注释,排版烂的不行. 贴几个页面看看 感觉像是新手练手的,没有审下去的欲望了. 但想了想,我tm就是新手啊,然后就继续看 ...

  9. Error after SQL Server 2012 installation: Login Failure for "SQL Server Integration Services 11.0" SSIS service

    When you install SQL Server 2012 and you try to connect to SSIS services, you cannot due to that the ...

  10. CSS3组件化之圆波扩散

    本篇文章主要介绍用CSS3实现的水波扩散涟漪,圆波扩散,光圈扩散,雷达波向外散发动画. 预期效果应该是这样:,其实应该比这个更优美,因为设计师提供的gif出现透明度丢失问题,所以建议用css3实现. ...