首页 > Web开发 > 详细

.Net 程序在自定义位置查找托管/非托管 dll 的几种方法

时间:2019-03-07 22:43:43      阅读:175      评论:0      收藏:0      [点我收藏+]
原文:.Net 程序在自定义位置查找托管/非托管 dll 的几种方法

一、自定义托管 dll 程序集的查找位置

目前(.Net4.7)能用的有2种:

技术分享图片
  1 #define DEFAULT_IMPLEMENT
  2 //#define DEFAULT_IMPLEMENT2
  3 //#define HACK_UPDATECONTEXTPROPERTY
  4 
  5 namespace X.Utility
  6 {
  7     using System;
  8     using System.Collections.Generic;
  9     using System.IO;
 10     using System.Linq;
 11     using System.Reflection;
 12     using X.Linq;
 13     using X.Reflection;
 14 
 15     public static partial class AppUtil
 16     {
 17         #region Common Parts
 18 #if DEFAULT_IMPLEMENT || DEFAULT_IMPLEMENT2
 19         public static string AssemblyExtension { get; set; } = "dll";
 20         private static IEnumerable<Tuple<AssemblyName, string>> ScanDirs(IList<string> dirNames)
 21             => (0 == dirNames.Count ? new[] { "dlls" } : dirNames)
 22             .SelectMany(dir => Directory
 23             .GetFiles(Path.IsPathRooted(dir) ? dir : AppExeDir + dir, "*." + AssemblyExtension)
 24             .SelectIfCalc(f => f.GetLoadableAssemblyName(), a => null != a, (f, a) => Tuple.Create(a, f))
 25             );
 26         private static Assembly LoadAssemblyFromList(AssemblyName an, IEnumerable<Tuple<AssemblyName, string>> al)
 27         {
 28             foreach (var a in al.Where(aa => aa.Item1.Name == an.Name && aa.Item1.Version == an.Version && aa.Item1.CultureName == an.CultureName))
 29                 return LoadAssembly(a.Item2);
 30             foreach (var a in al.Where(aa => aa.Item1.Name == an.Name && aa.Item1.Version == an.Version))
 31                 return LoadAssembly(a.Item2);
 32 
 33             foreach (var a in al.Where(aa => aa.Item1.Name == an.Name && aa.Item1.Version > an.Version && aa.Item1.CultureName == an.CultureName).OrderBy(aa => aa.Item1.Version))
 34                 return LoadAssembly(a.Item2);
 35             foreach (var a in al.Where(aa => aa.Item1.Name == an.Name && aa.Item1.Version > an.Version).OrderBy(aa => aa.Item1.Version))
 36                 return LoadAssembly(a.Item2);
 37 
 38             foreach (var a in al.Where(aa => aa.Item1.Name == an.Name && aa.Item1.Version < an.Version && aa.Item1.CultureName == an.CultureName).OrderByDescending(aa => aa.Item1.Version))
 39                 return LoadAssembly(a.Item2);
 40             foreach (var a in al.Where(aa => aa.Item1.Name == an.Name && aa.Item1.Version < an.Version).OrderByDescending(aa => aa.Item1.Version))
 41                 return LoadAssembly(a.Item2);
 42 
 43             return null;
 44         }
 45         private static Assembly LoadAssembly(string path)
 46             => Assembly.Load(File.ReadAllBytes(path));
 47 #endif
 48         #endregion
 49 
 50         #region DEFAULT_IMPLEMENT
 51 #if DEFAULT_IMPLEMENT
 52         private static IEnumerable<Tuple<AssemblyName, string>> dlls;
 53         /// <summary>
 54         /// 以调用该方法时的目录状态为准,如果在调用方法之后目录或其内dll文件发生了变化,将导致加载失败。
 55         /// 不传入任何参数则默认为 dlls 子目录。
 56         /// </summary>
 57         /// <param name="dirNames">相对路径将从入口exe所在目录展开为完整路径</param>
 58         public static void SetPrivateBinPath(params string[] dirNames)
 59         {
 60             if (null != dlls) return;
 61             dlls = ScanDirs(dirNames);
 62             AppDomain.CurrentDomain.AssemblyResolve += AppDomain_AssemblyResolve_DEFAULT_IMPLEMENT;
 63         }
 64         private static Assembly AppDomain_AssemblyResolve_DEFAULT_IMPLEMENT(object sender, ResolveEventArgs args)
 65             => LoadAssemblyFromList(new AssemblyName(args.Name), dlls);
 66 #endif
 67         #endregion
 68 
 69         #region DEFAULT_IMPLEMENT2
 70 #if DEFAULT_IMPLEMENT2
 71         public static List<string> PrivateDllDirs { get; } = new List<string> { "dlls" };
 72         private static bool enablePrivateDllDirs;
 73         public static bool EnablePrivateDllDirs
 74         {
 75             get => enablePrivateDllDirs;
 76             set
 77             {
 78                 if (value == enablePrivateDllDirs) return;
 79                 if (value) AppDomain.CurrentDomain.AssemblyResolve += AppDomain_AssemblyResolve_DEFAULT_IMPLEMENT2;
 80                 else AppDomain.CurrentDomain.AssemblyResolve -= AppDomain_AssemblyResolve_DEFAULT_IMPLEMENT2;
 81                 enablePrivateDllDirs = value;
 82             }
 83         }
 84         private static Assembly AppDomain_AssemblyResolve_DEFAULT_IMPLEMENT2(object sender, ResolveEventArgs args)
 85             => LoadAssemblyFromList(new AssemblyName(args.Name), ScanDirs(PrivateDllDirs));
 86 #endif
 87         #endregion
 88 
 89         #region HACK_UPDATECONTEXTPROPERTY
 90 #if HACK_UPDATECONTEXTPROPERTY
 91         public static void SetPrivateBinPathHack2(params string[] dirNames)
 92         {
 93             const string privateBinPathKeyName = "PrivateBinPathKey";
 94             const string methodName_UpdateContextProperty = "UpdateContextProperty";
 95             const string methodName_GetFusionContext = "GetFusionContext";
 96 
 97             for (var i = 0; i < dirNames.Length; ++i)
 98                 if (!Path.IsPathRooted(dirNames[i]))
 99                     dirNames[i] = AppExeDir + dirNames[i];
100 
101             var privateBinDirectories = string.Join(";", dirNames);
102             var curApp = AppDomain.CurrentDomain;
103             var appDomainType = typeof(AppDomain);
104             var appDomainSetupType = typeof(AppDomainSetup);
105             var privateBinPathKey = appDomainSetupType
106                 .GetProperty(privateBinPathKeyName, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetProperty)
107                 .GetValue(null)
108                 .ToString();
109             curApp.SetData(privateBinPathKey, privateBinDirectories);
110             appDomainSetupType
111                 .GetMethod(methodName_UpdateContextProperty, BindingFlags.NonPublic | BindingFlags.Static)
112                 .Invoke(null, new[]
113                 {
114                     appDomainType
115                         .GetMethod(methodName_GetFusionContext, BindingFlags.NonPublic | BindingFlags.Instance)
116                         .Invoke(curApp, null),
117                     privateBinPathKey,
118                     privateBinDirectories
119                 });
120         }
121 #endif
122         #endregion
123     }
124 }
View Code
  1. DEFAULT_IMPLEMENT - 这个算是比较“正统”的方式。通过 AssemblyResolve 事件将程序集 dll 文件读入内存后加载。以调用该方法时的目录状态为准,如果在调用方法之后目录或其内dll文件发生了变化,将导致加载失败。
  2. DEFAULT_IMPLEMENT2 - 关键细节与前一种方式相同,只是使用方式不同,并且在每一次事件调用中都会在文件系统中进行查找。
  3. HACK_UPDATECONTEXTPROPERTY - 来源于 AppDomain.AppendPrivatePath 方法的框架源码,其实就是利用反射把这个方法做的事做了一遍。该方法已经被M$废弃,因为这个方法会在程序集加载后改变程序集的行为(其实就是改变查找后续加载的托管dll的位置)。目前(.Net4.7)还是可以用的,但是已经被标记为“已过时”了,后续版本不知道什么时候就会取消了。

M$ 对 AppDomain.AppendPrivatePath 的替代推荐是涉及到 AppDomainSetup 的一系列东西,很麻烦,必须在 AppDomain 加载前设置好参数,但是当前程序已经在运行了所以这种方法对自定义查找托管dll路径的目的无效。

 

通常来说,不推荐采用 Hack 的方法,毕竟是非正规的途径,万一哪天 M$ 改了内部的实现就抓瞎了。

DEFAULT_IMPLEMENT 的方法可以手动加个文件锁,或者直接用 Assembly.LoadFile 方法加载,这样就会锁定文件。

 

 

注意:这些方法只适用于托管dll程序集,对 DllImport 特性引入的非托管 dll 不起作用。

 

.Net 开发组关于取消 AppDomain.AppendPrivatePath 方法的博客,下面有一些深入的讨论,可以看看:
https://blogs.msdn.microsoft.com/dotnet/2009/05/14/why-is-appdomain-appendprivatepath-obsolete/
在访客评论和开发组的讨论中,提到了一个关于 AssemblyResolve 事件的细节:.Net 不会对同一个程序集触发两次该事件,因此在事件代码当中没有必要手动去做一些额外的防止多次载入同一程序集的措施,也不需要手动缓存从磁盘读取的程序集二进制数据。

二、自定义非托管 dll 查找位置

如果只需要一个自定义目录:

技术分享图片
 1 namespace X.Utility
 2 {
 3     using System;
 4     using System.IO;
 5     using System.Runtime.InteropServices;
 6 
 7     public static partial class AppUtil
 8     {
 9         [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
10         private static extern bool SetDllDirectory(string dir);
11 
12         public static void Set64Or32BitDllDir(string x64DirName = @"dlls\x64", string x86DirName = @"dlls\x86")
13         {
14             var dir = IntPtr.Size == 8 ? x64DirName : x86DirName;
15             if (!Path.IsPathRooted(dir)) dir = AppEntryExeDir + dir;
16             if (!SetDllDirectory(dir))
17                 throw new System.ComponentModel.Win32Exception(nameof(SetDllDirectory));
18         }
19     }
20 }
View Code

如果需要多个自定义目录:

技术分享图片
 1 //#define ALLOW_REMOVE_DLL_DIRS
 2 
 3 namespace X.Utility
 4 {
 5     using System;
 6     using System.Collections.Generic;
 7     using System.IO;
 8     using System.Runtime.InteropServices;
 9 
10     public static partial class AppUtil
11     {
12         [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
13         private static extern bool SetDefaultDllDirectories(int flags = 0x1E00);
14         [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
15         private static extern IntPtr AddDllDirectory(string dir);
16 #if ALLOW_REMOVE_DLL_DIRS
17         [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
18         private static extern bool RemoveDllDirectory(IntPtr cookie);
19 
20         public static Dictionary<string, IntPtr> DllDirs { get; } = new Dictionary<string, IntPtr>();
21 #endif
22 
23         public static readonly string[] x64DefaultDllDirs = new[] { @"dlls\x64" };
24         public static readonly string[] x86DefaultDllDirs = new[] { @"dlls\x86" };
25 
26         public static void Set64Or32BitDllDirs(IEnumerable<string> x64DirNames, IEnumerable<string> x86DirNames)
27         {
28             if (null == x64DirNames && null == x86DirNames)
29                 throw new ArgumentNullException($"Must set at least one of {nameof(x64DirNames)} or {nameof(x86DirNames)}");
30 
31             if (!SetDefaultDllDirectories())
32                 throw new System.ComponentModel.Win32Exception(nameof(SetDefaultDllDirectories));
33 
34             AddDllDirs(IntPtr.Size == 8 ? x64DirNames ?? x64DefaultDllDirs : x86DirNames ?? x86DefaultDllDirs);
35         }
36 
37         public static void AddDllDirs(IEnumerable<string> dirNames)
38         {
39             foreach (var dn in dirNames)
40             {
41                 var dir = Path.IsPathRooted(dn) ? dn : AppExeDir + dn;
42 #if ALLOW_REMOVE_DLL_DIRS
43                 if (!DllDirs.ContainsKey(dir))
44                     DllDirs[dir] =
45 #endif
46                 AddDllDirectory(dir);
47             }
48         }
49         public static void AddDllDirs(params string[] dirNames) => AddDllDirs(dirNames);
50 
51 #if ALLOW_REMOVE_DLL_DIRS
52         public static void RemoveDllDirs(IEnumerable<string> dirNames)
53         {
54             foreach (var dn in dirNames)
55             {
56                 var dir = Path.IsPathRooted(dn) ? dn : AppExeDir + dn;
57                 if (DllDirs.TryGetValue(dir, out IntPtr cookie))
58                     RemoveDllDirectory(cookie);
59             }
60         }
61         public static void RemoveDllDirs(params string[] dirNames) => RemoveDllDirs(dirNames);
62 #endif
63     }
64 }
View Code

针对非托管 dll 自定义查找路径是用 Windows 原生 API 提供的功能来完成。

#define ALLOW_REMOVE_DLL_DIRS //取消这行注释可以打开【移除自定义查找路径】的功能

三、比较重要的是用法

技术分享图片
 1 public partial class App
 2 {
 3     static App()
 4     {
 5         AppUtil.SetPrivateBinPath();
 6         AppUtil.Set64Or32BitDllDir();
 7     }
 8     [STAThread]
 9     public static void Main()
10     {
11         //do something...
12     }
13 }
View Code

最合适的地方是放在【启动类】的【静态构造】函数里面,这样可以保证在进入 Main 入口点之前已经设置好了自定义的 dll 查找目录。

四、代码中用到的其他代码

  1. 检测 dll 程序集是否可加载到当前进程
    技术分享图片
     1 namespace X.Reflection
     2 {
     3     using System;
     4     using System.Reflection;
     5 
     6     public static partial class ReflectionX
     7     {
     8         private static readonly ProcessorArchitecture CurrentProcessorArchitecture = IntPtr.Size == 8 ? ProcessorArchitecture.Amd64 : ProcessorArchitecture.X86;
     9         public static AssemblyName GetLoadableAssemblyName(this string dllPath)
    10         {
    11             try
    12             {
    13                 var an = AssemblyName.GetAssemblyName(dllPath);
    14                 switch (an.ProcessorArchitecture)
    15                 {
    16                     case ProcessorArchitecture.MSIL: return an;
    17                     case ProcessorArchitecture.Amd64:
    18                     case ProcessorArchitecture.X86: return CurrentProcessorArchitecture == an.ProcessorArchitecture ? an : null;
    19                 }
    20             }
    21             catch { }
    22             return null;
    23         }
    24     }
    25 }
    View Code
  2. 当前 exe 路径和目录
    技术分享图片
     1 namespace X.Utility
     2 {
     3     using System;
     4     using System.IO;
     5     using System.Reflection;
     6     public static partial class AppUtil
     7     {
     8         public static string AppExePath { get; } = Assembly.GetEntryAssembly().Location;
     9         public static string AppExeDir { get; } = Path.GetDirectoryName(AppExePath) + Path.DirectorySeparatorChar;
    10 
    11 #if DEBUG
    12         public static string AppExePath1 { get; } = Path.GetFullPath(Assembly.GetEntryAssembly().CodeBase.Substring(8));
    13         public static string AppExeDir1 { get; } = AppDomain.CurrentDomain.BaseDirectory;
    14         public static string AppExeDir2 { get; } = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
    15 
    16         static AppUtil()
    17         {
    18             System.Diagnostics.Debug.Assert(AppExePath == AppExePath1);
    19             System.Diagnostics.Debug.Assert(AppExeDir == AppExeDir1);
    20             System.Diagnostics.Debug.Assert(AppExeDir1 == AppExeDir2);
    21         }
    22 #endif
    23     }
    24 }
    View Code
  3. SelectIfCalc
    技术分享图片
     1 namespace X.Linq
     2 {
     3     using System;
     4     using System.Collections.Generic;
     5 
     6     public static partial class LinqX
     7     {
     8         public static IEnumerable<TResult> SelectIfCalc<TSource, TCalculated, TResult>(this IEnumerable<TSource> source, Func<TSource, TCalculated> calculator, Func<TCalculated, bool> predicate, Func<TCalculated, TResult> selector)
     9         {
    10             foreach (var s in source)
    11             {
    12                 var c = calculator(s);
    13                 if (predicate(c)) yield return selector(c);
    14             }
    15         }
    16         public static IEnumerable<TResult> SelectIfCalc<TSource, TCalculated, TResult>(this IEnumerable<TSource> source, Func<TSource, TCalculated> calculator, Func<TCalculated, bool> predicate, Func<TSource, TCalculated, TResult> selector)
    17         {
    18             foreach (var s in source)
    19             {
    20                 var c = calculator(s);
    21                 if (predicate(c)) yield return selector(s, c);
    22             }
    23         }
    24         public static IEnumerable<TResult> SelectIfCalc<TSource, TCalculated, TResult>(this IEnumerable<TSource> source, Func<TSource, TCalculated> calculator, Func<TSource, TCalculated, bool> predicate, Func<TCalculated, TResult> selector)
    25         {
    26             foreach (var s in source)
    27             {
    28                 var c = calculator(s);
    29                 if (predicate(s, c)) yield return selector(c);
    30             }
    31         }
    32         public static IEnumerable<TResult> SelectIfCalc<TSource, TCalculated, TResult>(this IEnumerable<TSource> source, Func<TSource, TCalculated> calculator, Func<TSource, TCalculated, bool> predicate, Func<TSource, TCalculated, TResult> selector)
    33         {
    34             foreach (var s in source)
    35             {
    36                 var c = calculator(s);
    37                 if (predicate(s, c)) yield return selector(s, c);
    38             }
    39         }
    40     }
    41 }
    View Code

.Net 程序在自定义位置查找托管/非托管 dll 的几种方法

原文:https://www.cnblogs.com/lonelyxmas/p/10493098.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!