再谈CLR查找和加载程序集的方式

时间:2021-07-15 04:54:39

原文:再谈CLR查找和加载程序集的方式

这是一个老问题,以前也有朋友写过一些文章介绍,但可能还不是很全面。我也多次被人问到,这里结合案例再次谈谈,希望对大家有所帮助。

本文范例代码可以通过这里下载 http://files.cnblogs.com/chenxizhang/AssemblyMatchDemoSolution.zip

根据程序集的特征,讨论这个问题,我们大致上有两个分类

没有做强名称签名的程序集

对于这种情况,CLR查找和加载程序集的方式如下

  1. 程序的根目录
  2. 根目录下面,与被引用程序集同名的子目录
  3. 根目录下面被明确定义为私有目录的子目录

同时,这种情况下,如果有定义codebase,则codebase的优先级最高,而且如果codebase指定的路径找不到,则直接报告错误,不再查找其他目录

有做强名称签名的程序集

对于这种情况,CLR查找和加载程序集的方式如下

  1. 全局程序集缓存
  2. 如果有定义codebase,则以codebase定义为准,如果codebase指定的路径找不到,则直接报告错误
  3. 程序的根目录
  4. 根目录下面,与被引用程序集同名的子目录
  5. 根目录下面被明确定义为私有目录的子目录

我们帮助大家更好地理解以上的说明,我准备用范例来做讲解。

    1.准备基本范例

    下面的范例演示了一个应用程序(MyApplication),和一个类库(MyLibrary) ,MyApplication是引用了MyLibrary的。

    再谈CLR查找和加载程序集的方式

    MyLibrary中有一个TestClass类型,提供了一个简单的方法(SayHello)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks; namespace MyLibrary
    {
    public class TestClass
    {
    public void SayHello()
    {
    //这里为了演示方便,显示出来当前加载的程序集完整路径
    Console.WriteLine(this.GetType().Assembly.Location);
    Console.WriteLine("Hello,world");
    }
    }
    }

    .csharpcode, .csharpcode pre
    {
    font-size: small;
    color: black;
    font-family: consolas, "Courier New", courier, monospace;
    background-color: #ffffff;
    /*white-space: pre;*/
    }
    .csharpcode pre { margin: 0em; }
    .csharpcode .rem { color: #008000; }
    .csharpcode .kwrd { color: #0000ff; }
    .csharpcode .str { color: #006080; }
    .csharpcode .op { color: #0000c0; }
    .csharpcode .preproc { color: #cc6633; }
    .csharpcode .asp { background-color: #ffff00; }
    .csharpcode .html { color: #800000; }
    .csharpcode .attr { color: #ff0000; }
    .csharpcode .alt
    {
    background-color: #f4f4f4;
    width: 100%;
    margin: 0em;
    }
    .csharpcode .lnum { color: #606060; }

    在MyApplication中,我们就是简单地创建了这个类型的实例,然后调用方法。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks; namespace MyApplication
    {
    class Program
    {
    static void Main(string[] args)
    {
    var c = new MyLibrary.TestClass();
    c.SayHello(); Console.Read();
    }
    }
    }

    默认情况下,如果我们编译整个项目,那么MyLibrary.dll会被自动地复制到MyApplication的根目录,如下图所示

    再谈CLR查找和加载程序集的方式

    运行MyApplication.exe,我们能看到下面这样的输出

    再谈CLR查找和加载程序集的方式

    我们可以很清楚地看到,当前加载的MyLibrary.dll是来自于MyApplication的根目录的。

    2. 假如我们不想将MyLibrary.dll放在应用程序的根目录

    有时候,我们会希望单独存放MyLibrary.dll,那么第一种做法就是,直接在应用程序根目录下面建立一个与程序集同名的子目录,然后将程序集放进去。

    再谈CLR查找和加载程序集的方式

    我们注意到,根目录下面的MyLibrary.dll 被移动到了MyLibrary目录
    再谈CLR查找和加载程序集的方式

    然后,我们再次运行MyApplication.exe,能看到下面这样的输出:

    再谈CLR查找和加载程序集的方式

    3.假如我们有很多程序集,希望统一放在一个目录

    第二步的方法虽然不错,但有一个问题,就是如果我们引用的程序集很多的话,就需要在根目录下面建立很多子目录。那么,有没有办法统一地将这些程序集放在一个目录中呢?

    我们可以通过如下的方式,定义一个特殊的私有路径(PrivatePath)

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <probing privatePath="libs"></probing>
    </assemblyBinding>
    </runtime>
    </configuration>

    .csharpcode, .csharpcode pre
    {
    font-size: small;
    color: black;
    font-family: consolas, "Courier New", courier, monospace;
    background-color: #ffffff;
    /*white-space: pre;*/
    }
    .csharpcode pre { margin: 0em; }
    .csharpcode .rem { color: #008000; }
    .csharpcode .kwrd { color: #0000ff; }
    .csharpcode .str { color: #006080; }
    .csharpcode .op { color: #0000c0; }
    .csharpcode .preproc { color: #cc6633; }
    .csharpcode .asp { background-color: #ffff00; }
    .csharpcode .html { color: #800000; }
    .csharpcode .attr { color: #ff0000; }
    .csharpcode .alt
    {
    background-color: #f4f4f4;
    width: 100%;
    margin: 0em;
    }
    .csharpcode .lnum { color: #606060; }

    同时,我们将MyLibrary.dll 移动到libs这个子目录下面去

    再谈CLR查找和加载程序集的方式

    然后,我们再次运行MyApplication.exe,能看到下面这样的输出:

    再谈CLR查找和加载程序集的方式

    这也就是说,对于没有签名的程序集,CLR一般会按照如下的规则查找和加载程序集

    1. 程序的根目录
    2. 根目录下面,与被引用程序集同名的子目录
    3. 根目录下面被明确定义为私有目录的子目录

    但是,有一个例外

    4. codebase的设置是优先的,而且是排他的

    codebase是一个特殊的设置,我们可以在配置文件中明确地指定某个程序集的查找路径,这个规则具有最高的优先级,而且如果你做了设置,CLR就一定会按照你的设置去查找,如果找不到,它就报告失败,而不会继续查找其他路径。

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <probing privatePath="libs"/> <dependentAssembly>
    <assemblyIdentity name="MyLibrary"
    culture="neutral" />
    <codeBase version="1.0.0.0"
    href="CodeBase\MyLibrary.dll" />
    </dependentAssembly>
    </assemblyBinding>
    </runtime>
    </configuration>

    .csharpcode, .csharpcode pre
    {
    font-size: small;
    color: black;
    font-family: consolas, "Courier New", courier, monospace;
    background-color: #ffffff;
    /*white-space: pre;*/
    }
    .csharpcode pre { margin: 0em; }
    .csharpcode .rem { color: #008000; }
    .csharpcode .kwrd { color: #0000ff; }
    .csharpcode .str { color: #006080; }
    .csharpcode .op { color: #0000c0; }
    .csharpcode .preproc { color: #cc6633; }
    .csharpcode .asp { background-color: #ffff00; }
    .csharpcode .html { color: #800000; }
    .csharpcode .attr { color: #ff0000; }
    .csharpcode .alt
    {
    background-color: #f4f4f4;
    width: 100%;
    margin: 0em;
    }
    .csharpcode .lnum { color: #606060; }

    再谈CLR查找和加载程序集的方式

    请注意,我们保留了libs目录和Mylibrary目录,而且根目录下面也保留了一个MyLibrary.dll。 实际上,当前我们一共有4个dll. 那么到底会加载哪一个呢?

    再谈CLR查找和加载程序集的方式

    这种情况下,如果codebase下面找不到MyLibrary.dll 会怎么样呢?

    再谈CLR查找和加载程序集的方式

    我们发现他是会报告错误的,而不会查找其他目录的程序集。

    5.如果有强名称签名会怎么样呢?

    对程序集进行强名称签名的好处是,可以将其添加到全局全局程序集缓存中。这样既可以实现程序集的共享,又可以从一定程度上提高性能。

    再谈CLR查找和加载程序集的方式

    签名后,我们将其添加到全局程序集缓存中去

    再谈CLR查找和加载程序集的方式

    那么这种情况下,不管我们在应用程序根目录(或者下面的子目录)有没有MyLibrary.dll ,CLR都是尝试先从全局程序集缓存中查找和加载的。

    再谈CLR查找和加载程序集的方式

    需要注意的是,如果程序集是经过了强名称签名,则在定义codebase的时候,应该注明publicKeyToken

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <probing privatePath="libs"/> <dependentAssembly>
    <assemblyIdentity name="MyLibrary"
    publicKeyToken="4a77fca346941a6c"
    culture="neutral" />
    <codeBase version="1.0.0.0"
    href="CodeBase\MyLibrary.dll" />
    </dependentAssembly>
    </assemblyBinding>
    </runtime>
    </configuration>
     
     
     

    总结

    本文通过实例讲解了CLR在查找和加载程序集的时候所遵循的一些规则,针对有强名称和没有强名称的程序集,这些规则略有不同。本文范例代码可以通过这里下载 http://files.cnblogs.com/chenxizhang/AssemblyMatchDemoSolution.zip

    相关问题

    本文还没有涵盖到的另外两个特殊情况,在日常工作中不多见,大家有兴趣可以再找些资料研读。

    1.在目录中查找的时候,如果dll查找不到,则会尝试查找同名的exe

    2.如果程序集带有区域性,而不是语言中立的,则还会尝试查找以语言区域命名的子目录。

    通常情况下,我们都就是程序集设置为语言中立的,所以不存在这个问题

    再谈CLR查找和加载程序集的方式

     

    .csharpcode, .csharpcode pre
    {
    font-size: small;
    color: black;
    font-family: consolas, "Courier New", courier, monospace;
    background-color: #ffffff;
    /*white-space: pre;*/
    }
    .csharpcode pre { margin: 0em; }
    .csharpcode .rem { color: #008000; }
    .csharpcode .kwrd { color: #0000ff; }
    .csharpcode .str { color: #006080; }
    .csharpcode .op { color: #0000c0; }
    .csharpcode .preproc { color: #cc6633; }
    .csharpcode .asp { background-color: #ffff00; }
    .csharpcode .html { color: #800000; }
    .csharpcode .attr { color: #ff0000; }
    .csharpcode .alt
    {
    background-color: #f4f4f4;
    width: 100%;
    margin: 0em;
    }
    .csharpcode .lnum { color: #606060; }