.Net 程序在自定义位置查找托管/非托管 dll 的几种方法
原文:.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 }
- DEFAULT_IMPLEMENT - 这个算是比较“正统”的方式。通过 AssemblyResolve 事件将程序集 dll 文件读入内存后加载。以调用该方法时的目录状态为准,如果在调用方法之后目录或其内dll文件发生了变化,将导致加载失败。
- DEFAULT_IMPLEMENT2 - 关键细节与前一种方式相同,只是使用方式不同,并且在每一次事件调用中都会在文件系统中进行查找。
- 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 }
如果需要多个自定义目录:
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 查找目录。
四、代码中用到的其他代码
- 检测 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 } - 当前 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 } - 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 }
.Net 程序在自定义位置查找托管/非托管 dll 的几种方法的更多相关文章
- 托管非托管Dll动态调用
原文:托管非托管Dll动态调用 最近经常看到有人问托管非托管Dll调用的问题.对于动态库的调用其实很简单.网上很多代码都实现了Dll的静态调用方法.我主要谈论下动态库的动态加载. 对于托管动态库,实现 ...
- Chrome程序及数据位置变更到非系统盘
Chrome浏览器在Windows系统上安装过程,没有设置安装位置的步骤,所以默认是安装在C盘的.并且,若Chrome作为主要浏览器使用,随着时间的积累,数据文件会非常多.增加系统盘的负荷. Wind ...
- C# 托管非托管资源释放
1.C#几乎所有对象都为托管对象,不同点是有的对象封装了非托管资源. 2.C#大部分对象在进行垃圾回收时都可以回收,包括非托管资源,因为非托管资源都已经通过C#类进行了封装,会将非托管资源的释放放在析 ...
- oracle中查找和删除重复记录的几种方法总结
平时工作中可能会遇到当试图对库表中的某一列或几列创建唯一索引时,系统提示 ORA-01452 :不能创建唯一索引,发现重复记录. 下面总结一下几种查找和删除重复记录的方法(以表CZ为例): 表CZ的结 ...
- MFC程序执行后台操作时不允许操作界面的一种方法
在使用MFC编写界面程序时,有时候会遇到像点击按钮后,后台进行大量操作后才显示处理结果这种情况,在后台处理过程中,界面不应该被允许做任何操作,这里介绍一种方法. 解决办法 点击按钮后,弹出一个模态对话 ...
- [转载]SQL Server查找包含某关键字的存储过程3种方法
存储过程都写在一个指定的表中了,我们只要使用like查询就可以实现查询当前这台SQL Server中所有存储过程中包括了指定关键字的存储过程并显示出来,下面一起来看看我总结了几条命令. 例子1 代码如 ...
- [UE4]自定义函数,快速增加输入参数的一种方法
- C#的托管与非托管大难点
托管代码与非托管代码 众所周知,我们正常编程所用的高级语言,是无法被计算机识别的.需要先将高级语言翻译为机器语言,才能被机器理解和运行.在标准C/C++中,编译过程是这样的:源代码首先经过预处理器,对 ...
- C#的三大难点之二:托管与非托管
相关文章: C#的三大难点之前传:什么时候应该使用C#?C#的三大难点之一:byte与char,string与StringBuilderC#的三大难点之二:托管与非托管C#的三大难点之三:消息与事件 ...
随机推荐
- Laravel API 允许跨域访问
服务器A请求服务器B的接口,那么一般会出现跨域问题.全解跨域请求处理办法 XMLHttpRequest cannot load http://api.console.vms3.com/api/user ...
- Java基础学习总结(19)——Java环境变量配置
前言 学习java的第一步就要搭建java的学习环境,首先是要安装JDK,JDK安装好之后,还需要在电脑上配置"JAVA_HOME"."path"." ...
- ecnu 1244
SERCOI 近期设计了一种积木游戏.每一个游戏者有N块编号依次为1 ,2,-,N的长方体积木. 对于每块积木,它的三条不同的边分别称为"a边"."b边"和&q ...
- Dubbo源代码分析(三):Dubbo之服务端(Service)
如上图所看到的的Dubbo的暴露服务的过程,不难看出它也和消费者端非常像,也须要一个像reference的对象来维护service关联的全部对象及其属性.这里的reference就是provider. ...
- how to backup and restore database of SQL Server
Back up 1,右键选中需要备份的数据库,Tasks-->Backup 2.General中,Destination,先remove掉之前的,然后再Add 需要注意的是,add的文件,必须要 ...
- dotnet 命令的使用
dotnet --info PS E:\GitHub\KerryJiang\SuperSocket> dotnet --info.NET Command Line Tools (2.1.4) P ...
- 27.C语言宽字符操作
#include <locale.h> setlocale(LC_ALL, "zh-CN"); wchar_t wch = L'我'; putwchar(wch); # ...
- 快速定位java系统的线上问题--转
原文地址:http://m.blog.csdn.net/article/details?id=43376943 前言:我们的场景并没有像BAT等大型互联网公司里的系统那么复杂,但是基本上也有一定的规模 ...
- OPENCV(1)
VS 程序的默认路径是源码所在路径(所以图片应该放在此处),而不是Debug路径 OpenCV 模块结构: core--定义了基本数据结构,包括最重要的Mat和一些其他的模块 imgproc--该 ...
- Codefroces D2. Magic Powder - 2(二分)
http://codeforces.com/problemset/problem/670/D2 http://codeforces.com/problemset/problem/670/D1 time ...