CLR查找和加载程序集的方式(一)

原文:CLR查找和加载程序集的方式(一)

  C#开发者在开发WinForm程序、Asp.Net Web(MVC)程序等,不可避免的在项目中引用许多第三方的DLL程序集,

编译后引用的dll都放在根目录下。以我个人作品nbsp;AutoProject Studio 自动化项目生成器 为例,由于需要支持SQL Server、

Oracle、MySQL、PostgreSQL、DB2、Sybase、Infomix、SQLite、Access等多种数据库,所以引用了DmProvider.dll、

IBM.Data.DB2.dll、IBM.Data.Informix.dll、MySql.Data.dll、Npgsql.dll、SQLite.Interop.dll、System.Data.dll、

System.Data.OracleClient.dll、Sybase.AdoNet4.AseClient.dll 等DLL,参考下图:

CLR查找和加载程序集的方式(一)

nbsp;随着项目的日益增大,根目录下充满了各种各样的dll,非常的不美观。如果能够把dll按照想要的目录来存放,那么系统就美观多了。

此问题就涉及到nbsp;CLR查找和加载程序集的方式

nbsp;

系统搜索dll的目录以及顺序

  CLR解析一个程序集会在一个根目录内进行搜索,整个探索过程又称Probing,这个根目录很显然就是当前包含当前程序集的目录。

  AppDomainSetup这个类存储着探索目录的信息,其成员包括: ApplicationBase 、 PrivateBinPath 。

程序搜索DLL的顺序如下(区分强名称签名、没有强名称签名的程序集)

nbsp;

没有做强名称签名的程序集
  1. 程序的根目录
  2. 根目录下面,与被引用程序集同名的子目录
  3. 根目录下面被明确定义为私有目录的子目录
  4. 在目录中查找的时候,如果dll查找不到,则会尝试查找同名的exe
  5. 如果程序集带有区域性,而不是语言中立的,则还会尝试查找以语言区域命名的子目录nbsp;

nbsp;

强名称签名的程序集
  1. 全局程序集缓存
  2. 如果有定义codebase,则以codebase定义为准,如果 codebase指定的路径找不到,则直接报告错误
  3. 程序的根目录
  4. 根目录下面,与被引用程序集同名的子目录
  5. 根目录下面被明确定义为私有目录的子目录
  6. 在目录中查找的时候,如果dll查找不到,则会尝试查找同名的exe
  7. 如果程序集带有区域性,而不是语言中立的,则还会尝试查找以语言区域命名的子目录

nbsp; nbsp;nbsp;CLR查找和加载程序集的方式(一)CLR查找和加载程序集的方式(一)

nbsp;

如何让程序识别不同目录下的dll?
我们看到,上面的顺序无论是否有强名称签名看,都提到了一个名词 ldquo;私有目录rdquo;。

nbsp;

方法一:配置App.config(web.config)文件的 privatePath mdash;mdash;【推荐】

针对该问题,微软提供了lt;probinggt; 元素, 在配置文件中自定义存储目录。

 1 lt;?xml version="1.0" encoding="utf-8" ?gt;
 2 lt;configurationgt;
 3   lt;runtimegt;
 4     lt;!--双核以上计算机请开启下面的选项为 true--gt;
 5     lt;gcServer enabled="false" /gt;
 6     lt;assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"gt;
 7       lt;probing privatePath="bin;bin2\subbin;bin3" /gt;
 8     lt;/assemblyBindinggt;
 9   lt;/runtimegt;
10 lt;/configurationgt;
  • configuration:每个配置文件中的根元素,常用语言 runtime 和 .NET Framework 应用程序会使用这些文件。
  • runtime:包含程序集绑定和垃圾回收的相关信息。
  • assemblyBinding:包含有关程序集版本重定向和程序集位置的信息。
  • privatePath:必选特性。指定可能包含程序集的应用程序基目录的子目录。 用分号分隔每个子目录。

上述nbsp;privatePath中的"bin;bin2\subbin;bin3",其中bin是默认编译输出目录,bin2、bin3 是自定义目录,subbin是bin2下的子目录。

把DLL分别放入上述目录中,程序运行正常。

这是最简单的方法,当然也有一定的局限性,就是没法对DLL做控制。另外,无法解决第三方nbsp;DllImportnbsp;中引入的程序集不在根目录下的问题。

但是该方法基本解决了分目录存储的问题。

nbsp;

方法二:订阅程序集解析事件 AssemblyResolve 在代码中解析

应用程序集域中支持在程序集解析时的处理: AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; 。

通过这个事件,我们可以在程序集解析时,根据不同的程序集做不用的处理,比如加载x86的程序集还是64位的程序集,当然也就可以指定程序集目录了

这也正是 Assembly.Load 和 Assembly.LoadFrom 等方法的用武之地。

 1 using System;
 2 using System.IO;
 3 using System.Reflection;
 4 using System.Windows.Forms;
 5 
 6 namespace WindowsFormsApplication1
 7 {
 8     static class Program
 9     {
10         /// lt;summarygt;
11         /// 应用程序的主入口点。
12         /// lt;/summarygt;
13         [STAThread]
14         static void Main()
15         {
16             Application.EnableVisualStyles();
17             Application.SetCompatibleTextRenderingDefault(false);
18 
19             AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
20 
21             Application.Run(new Form1());
22         }
23 
24         /// lt;summarygt;
25         /// 解析当前应用程序域内指定目录下的DLL
26         /// lt;/summarygt;
27         /// lt;param name="sender"gt;lt;/paramgt;
28         /// lt;param name="args"gt;lt;/paramgt;
29         /// lt;returnsgt;lt;/returnsgt;
30         private static Assembly CurrentDomain_AssemblyResolve(object sender,ResolveEventArgs args)
31         {
32             string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,@"xx目录\");
33             path = Path.Combine(path,args.Name.Split(',')[0]);
34             path = string.Format(@"{0}.dll",path);
35             return Assembly.LoadFrom(path);
36         }
37     }
38 }

nbsp;

方法三:设置nbsp;AppDomainSetup类的PrivateBinPath属性
 1 using System;
 2 using System.IO;
 3 using System.Reflection;
 4 using System.Windows.Forms;
 5 
 6 namespace WindowsFormsApplication1
 7 {
 8     static class Program
 9     {
10         /// lt;summarygt;
11         /// 应用程序的主入口点。
12         /// lt;/summarygt;
13         [STAThread]
14         static void Main()
15         {
16             Application.EnableVisualStyles();
17             Application.SetCompatibleTextRenderingDefault(false);
18 
19             SetPrivateBinPath();//设置程序集所在的指定目录并解析DLL
20 
21             Application.Run(new Form1());
22         }
23 
24       
25         /// lt;summarygt;
26         /// 设置程序集所在的指定目录并解析DLL
27         /// lt;/summarygt;
28         private static void SetPrivateBinPath()
29         {
30             if(AppDomain.CurrentDomain.IsDefaultAppDomain())
31             {
32                 string appName = AppDomain.CurrentDomain.FriendlyName;
33                 var currentAssembly = Assembly.GetExecutingAssembly();
34 
35                 AppDomainSetup setup = new AppDomainSetup();
36                 setup.ApplicationBase = Environment.CurrentDirectory;
37                 setup.PrivateBinPath = "xx目录";
38                 setup.ConfigurationFile = setup.ApplicationBase +  string.Format("\\Config\\{0}.config",appName);
39 
40                 AppDomain newDomain = AppDomain.CreateDomain("NewAppDomain",null,setup);
41                 int ret = newDomain.ExecuteAssemblyByName(currentAssembly.FullName);
42                 
43                 AppDomain.Unload(newDomain);
44                 Environment.ExitCode = ret;
45                 Environment.Exit(0);
46                 return;
47             }
48         }
49     }
50 }

nbsp;

方法四:在加载使用到的DLL代码之前重置当前环境的目录

通过 Environment.CurrentDirectory=customPath ,切换目录后,在调用dll方法时运行正常。

处理 [DllImport] 中的程序集的加载,此处提供用一种方式来处理:增加环境变量。

C#代码如下:

static void AddEnvironmentPaths(IEnumerablelt;stringgt; paths)
{
    var path = new[] { Environment.GetEnvironmentVariable("PATH") ?? string.Empty };

    string newPath = string.Join(Path.PathSeparator.ToString(), path.Concat(paths));

    Environment.SetEnvironmentVariable("PATH", newPath);
}

nbsp;

上一篇:计数器——Verilog HDL语言


下一篇:C#基础之