简述C#中应用程序集的装载过程

了解程序集如何在C#.NET中加载

我们一直在处理库和NuGet软件包。不管是好是坏,高级.NET开发人员都需要了解.NET运行时如何加载程序集。

这些库依赖于其他流行的库,并且有很多共享的依赖项。有了足够大的依赖关系网络,您最终将陷入冲突或困境。处理此类问题的最佳方法是了解该机制在内部的工作方式。

在本文中,您将看到.NET进程如何以及何时加载引用的程序集。

您将了解加载了哪个库版本,当有多个可用版本时会发生什么,以及为什么有时由于版本冲突而出现问题。

您将看到如何调试这些类型的问题,查看程序集绑定日志(融合日志)以及一些解决冲突的方法。

程序集,模块和引用

让我们从围绕.NET流程的一些基本术语开始。

一个装配在.NET是一个DLL或EXE文件。Visual Studio解决方案中的每个项目都被编译为一个程序集。

每个程序集可以包含多个模块,但是实际上,我们几乎总是在一个程序集中有一个模块,该模块的名称与该程序集相同。

在Visual Studio中启动进程或单击F5时,将执行启动项目程序集。除了.NET Framework或.NET Core程序集之外,它将是第一个加载的程序集。

之后,该过程将根据需要在运行时加载其他程序集。仅当需要调用该程序集的方法或使用该程序集的类型时,它才会延迟加载程序集。

这里是为一个简单的“ Hello World” .NET Framework项目加载的模块(出于我们所有的意图和目的,模块和程序集都是相同的)。MyStartup.dll是此处的启动项目:

.NET Core项目启动时加载的模块

当您从另一个项目引用一个项目时,在构建时,被引用项目的DLL或EXE被复制到启动项目的Bin文件夹中。

通常是Bin \ DebugBin \ Release。在运行时,当您第一次使用引用的项目中的类型时,CLR在应用程序目录中查找具有与期望的名称和版本相同的DLL文件。然后将程序集加载到流程中。这也称为绑定到装配件。

这是一个例子:

假设我们有一个名为MyStartup的简单控制台应用程序,它引用了另一个名为Lib1的项目。MyStartup使用Lib1程序集中的某些类。

MyStartup中

class Program
{static void Main(string[] args){int a = int.Parse(Console.ReadLine());int b = int.Parse(Console.ReadLine());Console.WriteLine("A + B = " + Add(a, b));}private static int Add(int a, int b){var calculator = new Lib1.Calculator();return calculator.Sum(a, b);}
}

Lib1中

public class Calculator
{public int Sum(int a, int b){return a + b;}
}

输入Main方法时,尚未加载Lib1程序集。但是,在输入Add方法时,CLR尝试解析Calculator类型,找出它在引用的程序集Lib1中,然后尝试加载该程序集。

.NET中的程序集绑定

当CLR需要加载程序集时,逻辑实际上比在Bin文件夹中查找要复杂一些。这是执行的实际逻辑(有关详细说明,请参见Microsoft文档[1]):

1.根据配置文件(app.configweb.config)确定需要加载的程序集的版本。该配置文件的名称为(在生成之后) [executable name].exe.configweb.config。绑定重定向在这里发挥了作用(稍后会详细介绍)。2.查看程序集是否已加载。如果加载了其他版本,则将抛出FileLoadException,除非它是一个可以同时加载多个版本的强命名程序集。3.如果它是强名称程序集,请检查全局程序集缓存[2](GAC)。GAC是机器上共享多个应用程序部件的地方。如果需要的话,程序集会缓存。它只能存储强命名程序集。它可以存储同一程序集的不同版本。您可以使用gacutil.exe[3]自己将其安装到GAC 。4.如果它是一个强名称的程序集,并且配置文件包含<codeBase>节点,那么它将检查那里的程序集位置。如果该<codeBase>节点存在并且找不到程序集,FileNotFoundException则将引发a。5.根据启发式算法检查程序集DLL或EXE。此过程称为“探测”。算法如下:1.检查文件夹[application base] / [assembly name].dll。应用程序库是应用程序可执行文件所在的位置。通常,您的Bin \ Debug或Bin \ Release文件夹。2.检查一下 [application base] / [assembly name] / [assembly name].dll3.如果为引用的程序集指定了区域性信息,则仅检查以下目录: [application base] / [culture] / [assembly name].dll [application base] / [culture] / [assembly name] / [assembly name].dll4.如果该<probing>节点存在于配置文件中,则它将在该privatePath节点的属性指定的文件夹中查找程序集。

他们为什么要使所有事情变得如此困难,对吗?

实际上,这种逻辑非常有助于我们发展,而不会使事情变得困难。它的存在是为了实现一些重要目标:

•为了确保您引用的是特定的程序集和版本,则将加载该确切版本。否则,将引发异常。而且,如果您知道自己在做什么,则可以在配置文件中指定覆盖规则(绑定重定向)。•为了灵活地在您要加载的程序集中进行。例如,如果要根据不同的区域性(语言)加载不同的程序集,则可以轻松地做到这一点。或者,如果您要根据客户配置加载不同的程序集,那也可以。•为了安全起见,我们使用了全称的程序集。他们确保您不能“伪造”程序集。例如,如果某个进程希望加载Lib1 v4.5,那么您将无法加载具有相同名称和版本的恶意软件程序集。加载时会引发异常。这就是为什么在计算机上所有进程都共享的GAC只接受强名称程序集的原因。

在大多数应用程序中,您无需记住程序集加载和探测的复杂逻辑。您无需了解或考虑GAC,全名程序集或操作配置文件。

您几乎根本不需要考虑库的版本,因为可能的冲突通过称为“绑定重定向”的机制自动解决了。

绑定重定向

如果有一件事对于了解这笔交易非常重要,那就是绑定重定向。能够告诉运行时它将实际加载哪个版本,而不管其引用的版本如何。

这是一个示例:您的流程有两个项目(模块):项目A和项目B。项目A引用log4net.dll v1.1,项目B引用log4net.dll v1.2。两个log4net DLL文件都复制到输出文件夹,但是只能有一个log4net.dll文件。

假设复制到输出文件夹的文件是log4net.dll v1.2。假设到达的第一个代码是Project A中的代码,该代码引用了log4net v1.1。运行时将在输出文件夹中查找,找到不同版本的log4net,并失败FileLoadException

还有另一种可能。假设首先执行了项目B中的代码,并且在尝试使用log4net时,它成功加载了log4net.dll v1.2。片刻之后,Project A中的代码将尝试使用log4net v1.1,请参见该程序集已经加载了其他版本,并抛出FileLoadException

如果您知道哪个log4net版本将在输出文件夹中,在这种情况下可以做的就是告诉运行时应该使用哪个版本。只需app.config在该runtime部分的文件中添加以下几行:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>...<runtime>...<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" culture="neutral" /><bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="1.2.0" /></dependentAssembly></assemblyBinding></runtime>...
</configuration>

这意味着,只要运行时想绑定到版本范围为0.0.0.0to的程序集log4net 5.0.0.0,它就会尝试绑定到version 1.2.0

实际上,您不必手动添加这些重定向,因为它们是自动添加的。如果转到启动项目的“属性”,则会看到以下设置:

图片-20200711101325128

默认情况下选中此选项。它会自动检测版本冲突并在.config文件中生成绑定重定向。

当问题开始发生时

乍一看,绑定重定向可能看起来像是对所有问题的答案,但事实并非如此。使用绑定重定向时,基本上使用的库版本与预期不同。如果删除方法怎么办?或方法的签名已更改?在这种情况下,调用该方法时,程序将因运行时错误而失败。毕竟,创建版本是有原因的。

如果确实存在此类问题,则有解决方法。查看我的文章:如何解决.NET引用和NuGet软件包版本冲突[4]

故障排除

当您有一个FileLoadException或类似的东西时,我建议做的第一件事是查看Visual Studio中的“模块”窗口。在这里,您将看到所有已加载的模块,并确定您要加载的程序集是否已加载,使用哪个版本以及从哪个路径加载。

除此之外,您还可以查看程序集绑定日志,也称为融合日志。这些日志将显示在程序集绑定尝试过程中到底发生了什么。您将看到运行时查找的程序集版本,运行时查找的文件夹以及故障点。

有几种查看融合日志的方法。首先,您必须启用它们,因为默认情况下它们是禁用的。您可以通过将HKLM\Software\Microsoft\Fusion\ForceLog值设置为1并将HKLM\Software\Microsoft\Fusion\LogPath值设置为来在注册表中手动启用它们C:\FusionLogs。日志将自动出现。或者,您可以使用Fusion Log Viewer,该软件应以方式安装在PC上fuslogvw.exe。我建议使用“一切窗口”搜索之[5]类的程序来查找它。确保以管理员权限运行融合日志查看器,以便能够启用和禁用日志。最近更流行的一种更现代的工具是Fusion ++[6]

边注

也许您不需要,但是我以前讨厌不得不处理这类问题。例如一个逻辑上的问题,让我构建一些东西,甚至解决一个生产错误,但其他问题都好说,唯独这个……。

在这件事上别无选择,我不得不艰难地学习程序集绑定的内部工作。我发现,就像其他所有内容一样,一旦您理解了某些内容,它就会变得不那么可怕,甚至变得不再那么有趣了。

因此,我希望本文对您有意义,并会在我走过的道路上为您提供快速帮助。

References

[1] Microsoft文档: https://docs.microsoft.com/en-us/dotnet/framework/deployment/how-the-runtime-locates-assemblies
[2] 全局程序集缓存: https://docs.microsoft.com/en-us/dotnet/framework/app-domains/gac
[3] gacutil.exe: https://docs.microsoft.com/en-us/dotnet/framework/tools/gacutil-exe-gac-tool
[4] 如何解决.NET引用和NuGet软件包版本冲突: https://michaelscodingspot.com/how-to-resolve-net-reference-and-nuget-package-version-conflicts/
[5] 一切窗口”搜索之: https://www.voidtools.com/
[6] Fusion ++: https://github.com/awaescher/Fusion/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/306443.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Magicodes.IE 2.5版本发布

今天我们发布了2.5版本&#xff0c;这当然也离不开大家对Magicodes.IE的支持&#xff0c;今天我也是跟往常一样列举了该版本一些重要的更新内容。当然也要说一下&#xff0c;在这个版本中我们设计了全新的LOGO。Excel导出Excel导出支持HeaderRowIndex #164&#xff08;https://…

算法题目——最短路径问题(HDU—1007)

题目链接&#xff1a;HDU-1007 前序&#xff1a; 先看一维中的最短路径的计算方式 问题的简单描述&#xff1a;(题意) 在一个笛卡尔系中&#xff0c;n个点分布不一&#xff0c;在这N个点中&#xff0c;求出相距最短的两个点之间的距离 思路&#xff1a; 分治二分法 解题报告…

没有Kubernets,学习Docker还有用吗?

Docker容器化和Kubernetes容器编排&#xff0c;作为微服务和云原生的核心依赖&#xff0c;这几年已是大红大紫全民皆知。然而近日Kubernetes官方发布公告&#xff0c;宣布自 v1.20 起放弃对 Docker 的支持&#xff0c;惊呆了一众开发者。在这背后&#xff0c;究竟是人性的扭曲&…

算法题目——二次函数三分求极值(HDU-3714)

题目链接&#xff1a;HDU-3714 题目描述&#xff1a; 对于N个二次函数&#xff0c;求每个二次函数的最小值中的最大值 思路&#xff1a; 使用三分法求极值&#xff08;递归调用&#xff09; 对于这种在指定区间里只有一个极值点的函数&#xff08;凸函数凹函数都可以&#xff…

怎样才能去掉图片锁定纵横比_1分钟批量统一Word中300张图片的大小!无需插件、代码,超级简单...

很多时候我们需要将在Word中插入很多张图片&#xff0c;插入完成之后&#xff0c;一般我们还要统一它们的大小&#xff0c;如果是少量的图片的话&#xff0c;我们可以一个一个选择&#xff0c;然后再进行设置。或者先设置好一张图片后&#xff0c;再通过【F4】键&#xff0c;进…

我国火力发电站的大脑用上了国产系统

近年来&#xff0c;我国核电、水电、风电、光伏产业发展迅速&#xff0c;但从发电总量来看&#xff0c;火电依然占据7成以上的发电比例。水电、风电、光伏等清洁能源受自然条件限制&#xff0c;只能成为火电的有效补充&#xff0c;而不可能取代火电。而福岛核事故、切尔诺贝利核…

require 动态加载_require,exports,module.exports和import,export,export default

我们前端在开发过程中经常会遇到导入导出功能&#xff0c;在导入时&#xff0c;有时候是require&#xff0c;有时候是import在导出时&#xff0c;有时候是exports&#xff0c;module.exports&#xff0c;有时候是export&#xff0c;export default今天我们对这些内容进行简单的…

dotnet core 应用是如何跑起来的 通过AppHost理解运行过程

在 dotnet 的输出路径里面&#xff0c;可以看到有一个有趣的可执行文件&#xff0c;这个可执行文件是如何在框架发布和独立发布的时候&#xff0c;找到 dotnet 程序的运行时的&#xff0c;这个可执行文件里面包含了哪些内容在回答上面的问题之前&#xff0c;请大家尝试打开 C:\…

算法题目——整数划分(HRBUST-2004)

题目链接&#xff1a;HRBUST-2004 递归法: 1.当n1时&#xff0c;此时只有1种解{1}; 2.当m1时&#xff0c;此时也只有1种解{1,1,1, … 3.当n> m时&#xff0c;要分为最大数包含m和不包含m两种情况。 ●包含m:此时就是{mx1,2…}. 其中x1x23.… n-m, 就相当于求和为n-m,最…

dotnet core 应用是如何跑起来的 通过自己写一个 dotnet host 理解运行过程

在上一篇博客是使用官方提供的 AppHost 跑起来整个 dotnet 程序。本文告诉大家在 dotnet 程序运行到托管代码之前&#xff0c;所需要的 Native 部分的逻辑。包括如何寻找 dotnet 运行时&#xff0c;如何加载运行时和框架然后跑起来业务端的 dll 文件的逻辑在上一篇博客告诉大家…

算法题目——多米诺骨牌问题(POJ-2663)

题目链接&#xff1a;POJ-2663 设有形状一样的多米诺牌&#xff0c;每张牌恰好覆盖棋盘上相邻的两个方格&#xff0c;即一张多米诺牌是一张 1 行 2 列或者 2 行 1 列的牌。那么&#xff0c;是否能够把 32 张多米诺牌摆放到棋盘上&#xff0c;使得任何两张多米诺牌均不重叠&…

算法题目——求众数

在c中有两个关联容器&#xff0c; 第一种是map&#xff0c;内部是按照key排序的&#xff0c;从小到大 第二种是unordered_map&#xff0c;容器内部是无序的&#xff0c;使用hash组织内容的。 #include<iostream> #include<map> #include<algorithm> using n…

2020武汉dotNET俱乐部分享交流会圆满结束

经过长达2个多月的准备&#xff0c;终于在12月5日圆满的举行了武汉首次dotNET俱乐部线下分享交流活动。我们一共精心准备了3个目前比较热门的主题&#xff0c;分别如下&#xff1a;Jason分享的《ABP开发框架的扩展应用》Leo分享的《基于Kubernetes的DevOps平台应用》Eleven分享…

算法——常用的数据结构/模板/基础知识

常用的数据结构/模板/基础知识(一)c——优先队列(priority_queue)最大堆和最小堆的写法(二)c中的全排列函数next_permutation()(三)迭代器的使用(四)数学知识数列求和(五)求最大公约数和最小公倍数(六)将数值型数据转化为string 类型(一)c——优先队列(priority_queue) 最大堆…

对 精致码农大佬 说的 Task.Run 会存在 内存泄漏 的思考

一&#xff1a;背景 1. 讲故事这段时间项目延期&#xff0c;加班比较厉害&#xff0c;博客就稍微停了停&#xff0c;不过还是得持续的技术输出呀&#xff01;园子里最近挺热闹的&#xff0c;精致码农大佬分享了三篇文章&#xff1a;为什么要小心使用 Task.Run [https://www.c…

算法题目——爬楼梯(动态规划)

题目链接:70.爬楼梯 类似题目:1646.获取生成数组中最大值 本题大家如果没有接触过的话,会感觉比较难,多举几个例子,就可以发现其规律。 爬到第一层楼梯有一种方法,爬到二层楼梯有两种方法。 那么第一层楼梯再跨两步就到第三层 ,第二层楼梯再跨一步就到第三层。 所以到…

Kubernetes 1.20 发布:妙啊

喜欢就关注我们吧&#xff01;Kubernetes 1.20 发布&#xff0c;这是 2020 年的第三版也是最终版。Kubernetes 1.20 还是最近这段时间以来&#xff0c;功能最密集的版本之一&#xff0c;此版本包含了 42 个增强功能&#xff0c;其中 11 个增强功能已逐步升级为稳定版&#xff0…

算法题目——生成括号匹配

题目链接:剑指offer.85 生成 n 对括号共需要 2n 步,每一步都面临两个选项,即生成左括号或者右括号,题目要求返回所有的情况,所以本问题很适合采用回溯法。 在生成左右括号时存在两个限制条件,一个是左括号或者右括号的数量都不能超过 n 个,第二个是已经生成的右括号数…

C# WPF开源控件库:MahApps.Metro

❝其实站长很久之前就知道这个开源WPF控件库了&#xff0c;只是一直欣赏不了这种风格&#xff0c;但也star了该项目。每次浏览该仓库时&#xff0c;发现star越来越多&#xff0c;也看到很多网友对它的褒奖&#xff0c;所以今天就向大家推荐这款WPF控件库。1. 具体有多优秀&…

golang 切片 接口_Golang语言常用关键字之 make 和 new

上一章中对于golang的语言基础说明如下&#xff1a;1 函数调用2 接口3 反射接下来我们来对golang的常用关键字进行说明&#xff0c;主要内容有&#xff1a;1. for 和 range2. select3. defer4. panic 和 recover5. make 和 new— — — — — — — — — — — — — — — —…