目前(.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 }
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 不会对同一个程序集触发两次该事件,因此在事件代码当中没有必要手动去做一些额外的防止多次载入同一程序集的措施,也不需要手动缓存从磁盘读取的程序集二进制数据。
如果只需要一个自定义目录:
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 }
如果需要多个自定义目录:
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 }
针对非托管 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 }
最合适的地方是放在【启动类】的【静态构造】函数里面,这样可以保证在进入 Main 入口点之前已经设置好了自定义的 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 }
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 }
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 }
.Net 程序在自定义位置查找托管/非托管 dll 的几种方法
原文:https://www.cnblogs.com/lonelyxmas/p/10493098.html