添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
烦恼的跑步鞋  ·  如何在 WordPress ...·  3 天前    · 
活泼的小熊猫  ·  如何在 WordPress ...·  3 天前    · 
傻傻的生姜  ·  jsp 学习笔记-02-JSP ...·  6 天前    · 
打酱油的荒野  ·  EXCEPTION_ACCESS_VIOLA ...·  5 天前    · 
迷茫的领结  ·  KnowStreaming/docs/use ...·  4 月前    · 
伤情的豆腐  ·  paintEvent() not ...·  1 年前    · 
宽容的莲藕  ·  jquery ...·  1 年前    · 
  • Linux
  • Nginx
  • 数据库
  • Python
  • PHP
  • C#
  • .NET
  • WPF
  • MAUI
  • Blazor
  • WinForm
  • 帝国CMS
  • AKCMS
  • Typecho
  • 织梦CMS
  • Visual Studio
  • Windows
  • Mac
  • Web前端
  • 数据采集
  • 运营
  • 产品
  • 摩托车
  • 电影
  • 杂事
  • 我们一直在处理库和NuGet软件包。不管是好是坏,高级.NET开发人员都需要了解.NET运行时如何加载程序集。

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

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

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

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

    程序集,模块和引用

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

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

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

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

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

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

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

    通常是 Bin \ Debug Bin \ 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文档):

  • 根据配置文件( app.config web.config )确定需要加载的程序集的版本。该配置文件的名称为(在生成之后) [executable name].exe.config web.config 。绑定重定向在这里发挥了作用(稍后会详细介绍)。
  • 查看程序集是否已加载。如果加载了其他版本,则将抛出 FileLoadException ,除非它是一个可以同时加载多个版本的强命名程序集。
  • 如果它是强名称程序集,请检查全局程序集缓存(GAC)。GAC是机器上共享多个应用程序部件的地方。如果需要的话,程序集会缓存。它只能存储强命名程序集。它可以存储同一程序集的不同版本。您可以使用gacutil.exe [3] 自己将其安装到GAC 。
  • 如果它是一个强名称的程序集,并且配置文件包含 <codeBase> 节点,那么它将检查那里的程序集位置。如果该 <codeBase> 节点存在并且找不到程序集, FileNotFoundException 则将引发a。
  • 根据启发式算法检查程序集DLL或EXE。
  • 此过程称为“ 探测”

    算法如下:

  • 检查文件夹 [application base] / [assembly name].dll 。应用程序库是应用程序可执行文件所在的位置。通常,您的Bin \ Debug或Bin \ Release文件夹。
  • 检查一下 [application base] / [assembly name] / [assembly name].dll
  • 如果为引用的程序集指定了区域性信息,则仅检查以下目录: [application base] / [culture] / [assembly name].dll [application base] / [culture] / [assembly name] / [assembly name].dll
  • 如果该 <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.0 to的程序集log4net 5.0.0.0 ,它就会尝试绑定到version 1.2.0

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

    默认情况下选中此选项。它会自动检测版本冲突并在 .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]

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

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

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

    本文作者:michaelscode 信息来源: 公众号 DotNET技术圈 所属分类: C# Power by Typecho. Theme by Puma. Written by 董川民- 独立开发者 渝ICP备16006207号-2 渝公网安备 50011202503233号

    友情链接: