• .NET:鲜为人知的 “Load Context”


    背景

    任何一门语言都要了解其类型加载过程,如: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 探测过程

    弱签名程序集的探测过程

    代码

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

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

    1 BaseDirectoryB.dll
    2 BaseDirectoryBB.dll
    3 BaseDirectoryB.exe
    4 BaseDirectoryBB.exe

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

    1 BaseDirectoryzh-CNB.dll
    2 BaseDirectoryBzh-CNB.dll
    3 BaseDirectoryzh-CNB.exe
    4 BaseDirectoryBzh-CNB.exe

    使用 probing 指定 privatePath

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

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

    1 BaseDirectoryB.dll
    2 BaseDirectoryBB.dll
    3 BaseDirectory\_BB.dll
    4 BaseDirectory\_BBB.dll
    5 BaseDirectoryB.exe
    6 BaseDirectoryBB.exe
    7 BaseDirectory\_BB.exe
    8 BaseDirectory\_BBB.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 中已经包含了要加载的程序集(标识相同),就不会加载两次,即使两次加载的路径不同,下面的例子输出的数值是一样的:
      1 Assembly.LoadFrom(@"E:CodingHappyStudyAssemblyStudyMaininDebugG_G.dll");
      2 PrintAssemlyCount();
      3 Assembly.LoadFrom(@"E:CodingHappyStudyAssemblyStudyMaininDebugG__G.dll");
      4 PrintAssemlyCount();
    • Load-From Context 中的程序集对于探测过程是不可用的,下面的例子输出为 null:
      1 Assembly.LoadFrom(@"E:CodingHappyStudyAssemblyStudyMaininDebugG_G.dll");
      2 Console.WriteLine(Type.GetType("G.ClassG, G"));
      3

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

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

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

    • 依赖的程序集会自动加载,可以从 Default Context 加载,也可以从 Load-Form Context 维护的 Path 下加载,算法后面单独分析,见下例:
       1 using System;
       2 using System.Collections.Generic;
       3 using System.Linq;
       4 using System.Text;
       5 using System.Threading.Tasks;
       6 
       7 namespace H
       8 {
       9     public class ClassH
      10     {
      11         public ClassH()
      12         {
      13             Console.WriteLine("自动加载A:" + Type.GetType("A.ClassA, A").Assembly.Location);
      14             Console.WriteLine("自动加载I:" + Type.GetType("I.ClassI, I").Assembly.Location);
      15         }
      16     }
      17 }



      在 Main 中执行如下代码:
      1 var hAss = Assembly.LoadFrom(@"E:CodingHappyStudyAssemblyStudyHinDebugH.dll");
      2 
      3 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,在事件监听器里手工加载其它依赖。
      下面这个例子会出现异常:
      1             Assembly
      2                 .LoadFile(@"E:CodingHappyStudyLoadContextStudyTestinDebugPlugsImplements1.0.0.0Contracts.dll");
      3             var operatorType = Assembly
      4                 .LoadFile(@"E:CodingHappyStudyLoadContextStudyTestinDebugPlugsImplements1.0.0.0Implements.dll")
      5                 .GetTypes()
      6                 .First(x => x.Name == "Operator");
      7             var operatorInstance = Activator.CreateInstance(operatorType);

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

    • 只要路径不同,会返回多个相同程序集标识的程序集实例,如下例:
      1             Assembly.LoadFile(@"E:CodingHappyStudyAssemblyStudyMaininDebugF_F.dll");
      2             PrintAssemlyCount();
      3             Assembly.LoadFile(@"E:CodingHappyStudyAssemblyStudyMaininDebugF__F.dll");
      4             PrintAssemlyCount();

    Relfection-Only Context

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

    参考资料

    备注

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

  • 相关阅读:
    笔记7-7
    输出九九乘法表
    eclipse配置Maven——菜鸟篇
    IOC和AOP使用扩展之AOP详解实现类
    --------Hibernate框架之双向多对多关系映射
    易买网----------有感
    有关于TreeSet的自我理解
    爱学习当当网----图片的切换,书栏的循环滚动
    有关于购物车买买买?剁手吧
    致童年,一生都无法忘记的技能
  • 原文地址:https://www.cnblogs.com/happyframework/p/3399975.html
Copyright © 2020-2023  润新知