.Net Core利用反射动态加载DLL类库的方法(解决类库不包含Nuget依赖包的问题)
在.Net Framework时代,生成类库只需将类库项目编译好,然后拷贝到其他项目,即可引用或动态加载,相对来说,比较简单。但到了.Net Core时代,动态加载第三方类库,则稍微麻烦一些。
一、类库发布丢失Nuget依赖包
对于大部分类库来说,项目或多或少会引用第三方程序集,特别是Nuget程序包。通常编译类库项目生成的文件中,并不会包含引用的Nuget包相关类库,而是通过*.deps.json文件来描述类库所需的依赖。这样造成一种问题,如果该类库是通过动态加载的方式引用,则程序运行时,会提示“缺少某某dll”的问题。
解决上述问题,需要在该类库项目的.csproj文件中,在<PropertyGroup></PropertyGroup>标签中加入<EnableDynamicLoading>true</EnableDynamicLoading>标志,该属性将告诉编译器,该项目是动态加载的组件。相关链接:https://docs.microsoft.com/zh-cn/dotnet/core/project-sdk/msbuild-props#enabledynamicloading
二、类库编译生成多余的dll
对于类库项目来说,通常会引用解决方案中其他通用项目,而主体程序也会引用这些通用项目,所以对于类库来说,在编译生成的文件中,并不需要这些文件。比如主体程序A和动态类库项目B,共同引用通用项目C,C项目又有很多第三方类库的引用,那么在编译生成动态类库B的时候,会带上项目C及其相关依赖的文件,但实际上,对于动态类库项目B来说,是不需要的。这种情况下,也需要修改.csproj项目文件,如下图,生成的类库文件将不会包含pluginBase.csproj类库及其所有的依赖;
三、利用反射动态加载类库
如果按照文末参考文献中的教程,我尝试过仍会出现找不到“某某.dll”的问题,这边贴出我的代码,供参考:
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Reflection; 5 using System.Runtime.Loader; 6 7 namespace LoadDLL 8 { 9 /// <summary> 10 /// 程序集加载器 11 /// </summary> 12 public class AssemblyLoader 13 { 14 private string _basePath; 15 private AssemblyLoadContext context; 16 17 18 public AssemblyLoader(string basePath) 19 { 20 _basePath = basePath; 21 } 22 23 public Type Load(string dllFileName, string typeName) 24 { 25 context = new AssemblyLoadContext(dllFileName); 26 context.Resolving += Context_Resolving; 27 //需要绝对路径 28 string path = Path.Combine(_basePath, dllFileName); 29 if (File.Exists(path)) 30 { 31 try 32 { 33 using (var stream = File.OpenRead(path)) 34 { 35 Assembly assembly = context.LoadFromStream(stream); 36 Type type = assembly.GetType(typeName); 37 dicTypes.Add(typeName, type); 38 return type; 39 } 40 } 41 catch (Exception ex) 42 { 43 Console.WriteLine($"加载节点{dllFileName}-{typeName}发生异常:{ex.Message},{ex.StackTrace}"); 44 } 45 46 } 47 else 48 { 49 Console.WriteLine($"节点动态库{dllFileName}不存在:{path}"); 50 } 51 return null; 52 } 53 54 /// <summary> 55 /// 加载依赖文件 56 /// </summary> 57 /// <param name="context"></param> 58 /// <param name="assemblyName"></param> 59 /// <returns></returns> 60 private Assembly Context_Resolving(AssemblyLoadContext context, AssemblyName assemblyName) 61 { 62 string expectedPath = Path.Combine(_basePath, assemblyName.Name + ".dll"); ; 63 if (File.Exists(expectedPath)) 64 { 65 try 66 { 67 using (var stream = File.OpenRead(expectedPath)) 68 { 69 return context.LoadFromStream(stream); 70 } 71 } 72 catch (Exception ex) 73 { 74 Console.WriteLine($"加载节点{expectedPath}发生异常:{ex.Message},{ex.StackTrace}"); 75 } 76 } 77 else 78 { 79 Console.WriteLine($"依赖文件不存在:{expectedPath}"); 80 } 81 return null; 82 } 83 } 84 }
其中Context_Resolving(),是用于加载类库文件过程中,处理触发加载相关依赖文件的事件的方法,通过上述代码,可以保证将类库的完整地动态加载进程序,并且不会与其他动态加载类库项目发生程序集冲突的问题:比如A类库和B类库都有共同的依赖C,但两者的引用的C版本不同,通过AssemblyLoadContext可以保证A/B类库加载自己需要的版本。
四、调试DLL
在主体程序运行中,通常不会进入类库代码中的断点,这对类库调试带来一定麻烦了。这时候需要对类库的生成作进一步的设置,方法如下图,拷贝类库及其所有依赖文件时,注意要带上相应的.pdb文件
五、结尾
在.Net Core动态加载类库还是挺多坑的,以上是我踩过来,与大家分享~
参考文献: