商城购物网站开发意义,住建厅报名考试入口,如何用云服务器建设网站,建设工程个人信息采集哪个网站最近在工作中牵涉到了.NET下的一个古老的问题#xff1a;Assembly的加载过程。虽然网上有很多文章介绍这部分内容#xff0c;很多文章也是很久以前就已经出现了#xff0c;但阅读之后发现#xff0c;并没能解决我的问题#xff0c;有些点写的不是特别详细#xff0c;让人… 最近在工作中牵涉到了.NET下的一个古老的问题Assembly的加载过程。虽然网上有很多文章介绍这部分内容很多文章也是很久以前就已经出现了但阅读之后发现并没能解决我的问题有些点写的不是特别详细让人看完之后感觉还是云里雾里。最后我决定重新复习一下这个经典而古老的问题并将所得总结于此然后会有一个实例对这个问题进行演示希望能够帮助到大家。.NET下Assembly的加载过程.NET下Assembly的加载最主要的一步就是确定Assembly的版本。在.NET下托管的DLL和EXE都称之为AssemblyAssembly由AssemblyName来唯一标识AssemblyName也就是大家所熟悉的Assembly.FullName它是由五部分名称、版本、语言、公钥Token、处理器架构组成的这一点相信大家都知道。有关Assembly Name的详细描述请参考https://docs.microsoft.com/en-us/dotnet/framework/app-domains/assembly-names。那么版本就是AssemblyName中的一个重要组成部分。其它四部分相同版本如果不同的话就不能算作是同一个Assembly。设计这样一个Assembly的版本策略微软本身就是为了解决最开始的DLL Hell的问题在维基百科上着关于这段黑历史的详细描述地址是https://en.wikipedia.org/wiki/DLL_Hell在此也就不多啰嗦了。Assembly版本的重定向和最终确定.NET下Assembly的加载过程其实也是Assembly版本的确定和Assembly文件的定位过程步骤如下在一个Assembly被编译的时候它所引用的Assembly的全名FullName就会被编译器强行写入Assembly的Metadata这个值是死的从ILSpy可以看到每个Reference都有它的全名信息例如上图System.Data依赖System.Xml它所需要的版本是4.0.0.0那么当CLR加载System.Data的时候就可以暂且认为接下来需要加载的System.Xml版本是4.0.0.0。这里强调“暂且认为”是因为这只是确定Assembly版本的第一步那么最终System.Xml到底是不是使用4.0.0.0的版本呢就需要看接下来这步的处理结果也就是Assembly版本的重定向首先检查应用程序的配置文件看是否存在Assembly版本重定向的设定。我们暂时先讨论应用程序配置文件就在AppDomain内的情况如果在AppDomain之外则需要首先下载配置文件再继续这里先不深入讨论。应用程序配置文件常见的有.exe.config和web.config两种。在配置文件中可以在runtime节点下的assemblyBinding中进行配置。例如在这个例子中asm6 Assembly的版本号被重定向到2.0.0.0。那么假设这就是asm6的最终版本号那么接下来当CLR开始加载asm6的时候如果2.0.0.0的版本没有找到则直接抛出FileLoadException即使3.0.0.0的版本是存在的整个Assembly加载过程结束。FileLoadException的详细信息类似于Could not load file or assembly asm6, Version3.0.0.0, Cultureneutral, PublicKeyTokenc0305c36380ba429 or one of its dependencies. The located assemblys manifest definition does not match the assembly reference如果在配置文件中找到了对应的版本重定向设定那么再接着查看Publisher Policy文件。Publisher Policy文件是一个仅包含配置文件的.NET Assembly被安装到GAC里。它的Assembly版本重定向配置内容跟上面的应用程序配置文件的配置内容相同不同的是它的作用域是所有使用了该Assembly的应用程序。这种做法对于开发系统级通用框架的Assembly升级非常有用比如.NET Framework。下面就是安装在GAC里的Publisher Policy文件的样本需要注意Publisher Policy会override应用程序配置信息中的版本重定向配置而不是相反。换言之假如asm6在上面这一步被确定为2.0.0.0而所对应的Publisher Policy文件又将其确定为2.5.0.0那么暂且认为CLR应该要加载2.5.0.0的版本。同理“暂且认为”这个词表示版本确定的过程还未结束接下来查找machine.config文件。同理如果machine.config文件中存在版本重定向的设定那么就会使用machine.config文件中的这个值作为CLR应该去加载的Assembly的版本至此Assembly的最终版本已被确定接下来就是搜索Assembly文件并进行加载的过程了。Assembly文件的搜索和加载过程现在CLR已经开始加载确定版本的Assembly了接下来就是搜索Assembly文件的过程。这个过程也叫作Assembly Probing。CLR会做以下事情首先查看所需的Assembly是否已经加载过如果已经加载了那就直接使用那个已经加载的Assembly的版本与当前所需的版本进行比对如果匹配则使用那个已经加载的Assembly如果不匹配则抛出FileLoadException执行结束然后看Assembly是否已被强签名Strongly Named如果是则去GAC里查找Assembly。如果找到则直接加载整个Assembly加载过程结束。如果没有找到那么就进行下一步继续搜索Assembly文件。当然如果Assembly没有进行强签名那么就跳过这一步直接继续接着CLR开始搜索Probing可能的Assembly位置这又要分多种情况首先查看文件中是否有指定codeBasecodeBase配置允许应用程序针对Assembly的不同版本指定装载地址遵循如下规律如果所指定的Assembly文件位于当前应用程序域的启动目录或其子目录下则使用相对路径指定href的值如果所指定的Assembly文件位于其它目录或任何其它地方则href必须给出全路径并且Assembly必须强签名的然后CLR对应用程序域的根目录以及相关的子目录进行探索假设Assembly的名字是abc.dll那么CLR会探索以下目录[appdomain_base]\abc.dll[appdomain_base]\abc\abc.dll假设abc.dll还有语言设置culture不是neutral那么CLR会探索以下目录[appdomain_base]\[culture]\abc.dll[appdomain_base]\[culture]\abc\abc.dll如果找到符合版本的Assembly则加载否则进入下一步最后CLR会查看应用程序配置文件中是否有probling节点如果有则按probling节点所指定的privatePath值进行逐一探索。这个过程也会考虑culture的因素类似于上面这步这样对相应的子目录进行搜索。如果找到对应的Assembly则加载否则抛出FileLoadException整个加载过程结束。注意这里“逐一探索”的过程不是遍历并找最佳匹配的过程。CLR仅根据Assembly的名字不带版本号的名字在privatePath下查找Assembly的文件找到第一个名字匹配但是版本不匹配的话就抛异常并终止加载了它不会继续搜索privatePath中余下的其它路径在加载Assembly文件失败的时候AppDomain会触发AssemblyResolve的事件在这个事件的订阅函数中允许客户程序自定义对加载失败的Assembly的处理方式比如可以通过Assembly.LoadFrom或者Assembly.LoadFile调用“手动地”将Assembly加载到AppDomain。fuslogvw Assembly绑定日志查看器在.NET SDK中带了一个fuslogvw.exe的应用程序通过它可以查看详细的Assembly加载过程。使用方法非常简单使用管理员身份启动Visual Studio 2017 Developer Command Prompt然后在命令行输入fuslogvw.exe即可启动日志查看器。启动之后点击Settings按钮以启用日志记录功能日志启动之后点击Refresh按钮然后启动你的.NET应用程序就可以看到当前应用程序所依赖的Assembly的加载过程日志了接下来我会做一个例子程序然后使用这个工具来分析Assembly的加载过程。插件系统的实现与Assembly加载过程的分析理论结合实际看看如何通过实际代码来诠释以上所述Assembly的加载过程。一个比较好的例子就是设计一个简单的插件系统并通过观察系统加载插件的过程来了解Assembly加载的来龙去脉。为了简单直观我把这个插件系统称为PluginDemo。这个插件很简单主体程序是一个控制台应用程序然后我们实现两个插件Earth和Mars在不同的插件的Initialize方法中会输出不同的字符串。整个应用程序的项目结构如下该插件系统包含4个C#的项目PluginDemo.Common它定义了AddIn抽象类所有的插件实现都需要继承于这个抽象类。此外AddInDefinition类是一个用来保存插件Metadata的类。为了演示插件的Metadata仅仅包含插件类型的Assembly Qualified NamePluginDemo.App插件系统的应用程序。这个程序执行的时候会扫描程序目录下Modules目录中的DLL并根据module.xml的Metadata信息加载相应的插件对象并执行Initialize方法PluginDemo.Plugins.Earth其中的一个插件实现PluginDemo.Plugins.Mars另一个插件实现注意除了PluginDemo.Common之外的其它三个项目都对PluginDemo.Common有引用关系。而PluginDemo.App项目仅仅在项目本身依赖于PluginDemo.Plugins.Earth和PluginDemo.Plugins.Mars它不会去引用这两个项目。目的就是为了当PluginDemo.App被编译时其余两个插件项目也会同时被编译并输出到指定位置。在Earth插件的CustomAddIn类中我们实现了Initialize方法并在此输出一个字符串public class CustomAddIn : AddIn{ public override string Name Earth AddIn; public override void Initialize() { Console.WriteLine(Earth Plugin initialized.); }}在Mars插件的CustomAddIn类中我们也实现了Initialize方法并在此输出一个字符串public class CustomAddIn : AddIn{ public override string Name Mars AddIn; public override void Initialize() { Console.WriteLine(Mars AddIn initialized.); }}那么在插件系统主程序中就会扫描Modules子目录下的module.xml文件然后解析每个module.xml文件获得每个插件类的Assembly Qualified Name然后通过Type.GetType方法获得插件类进而创建实例、调用Initialize方法。代码如下static void Main(){ var directory new DirectoryInfo(Modules); foreach(var file in directory.EnumerateFiles(module.xml, SearchOption.AllDirectories)) { var addinDefinition AddInDefinition.ReadFromFile(file.FullName); var addInType Type.GetType(addinDefinition.FullName); var addIn (AddIn)Activator.CreateInstance(addInType); Console.WriteLine(${addIn.Id} - {addIn.Name}); addIn.Initialize(); }}接下来修改App.config文件修改为?xml version1.0 encodingutf-8 ?configuration runtime assemblyBinding xmlnsurn:schemas-microsoft-com:asm.v1 probing privatePathModules\Earth;Modules\Mars; / /assemblyBinding /runtime/configuration此时运行程序可以得到目前没有什么问题。接下来对两个AddIn分别做一些修改。让这两个AddIn依赖于不同版本的Newtonsoft.Json比如Earth依赖于7.0.0.0的版本Mars依赖于6.0.0.0的版本然后分别修改两个CustomAddIn的Initialize方法在方法中各自调用一次JsonConvert.SerializeObject方法以触发Newtonsoft.Json这个Assembly的加载。此时再次运行程序你将看到下面的异常现在刷新fuslogvw.exe找到Newtonsoft.Json的日志双击打开日志可以看到如下信息从整个过程可以看出PluginDemo.App.exe正在试图加载PluginDemo.Plugins.Mars AssemblyPluginDemo.Plugins.Mars开始调用Newtonsoft.Json扫描应用程序配置文件、Host配置文件以及machine.config文件均无找到Newtonsoft.Json的重定向信息此时Newtonsoft.Json版本确定为6.0.0.0GAC扫描失败继续查找文件首先查找应用程序当前目录下有没有Newtonsoft.Json以及Newtonsoft.Json子目录下有没有Newtonsoft.Json.dll发现都没有继续然后通过App.config中的probing的privatePath设定首先查找Modules\Earth目录因为这个目录放在privatePath的第一个找到了一个叫做Newtonsoft.Json.dll的Assembly于是判断版本是否相同。结果找到的是7.0.0.0而它需要的却是6.0.0.0版本不匹配于是就抛出异常退出程序那么接下来改一改App.config文件将privatePath下的两个值换个位置呢再试试此时Earth AddIn又出错了。那么我们加上版本重定向的配置指定当程序需要加载7.0.0.0版本的Newtonsoft.Json时让它重定向到6.0.0.0的版本再次执行成功了看看日志版本已经被重定向到6.0.0.0并且在Mars目录下找到了6.0.0.0的Newtonsoft.Json加载成功了。这个案例的源代码可以点击此处下载。总结本文详细介绍了.NET下Assembly的版本确定和加载过程最后给出了一个实例对这个过程进行了演示。原文https://www.cnblogs.com/daxnet/p/8525249.html.NET社区新闻深度好文欢迎访问公众号文章汇总 http://www.csharpkit.com