详细解析DLL构建CLR版本冲突问题

详细解析DLL构建CLR版本冲突问题

本文将从:

1,.net执行模型——CLR托管运行过程(.net如何实现从代码到运行)

2,Runtime中如何定位程序集

3,.net执行模型——定位程序集依赖项

4,.net执行模型——如何加载程序集依赖项

5,AnyCPU?x86?x64?platform选择不对导致的情况

6,Nuget错误处理机制

7,CPU版本问题会什么会绕过Nuget的错误处理机制?

8,总结

.net执行模型——CLR托管运行过程(.net如何实现从代码到运行)

首先介绍下什么是CLR:

CLR全名应当是Common language runtime——公共语言运行时。它的作用类似java中的java虚拟机(JVM),提供一个运行环境,运行代码并且提供能使开发更轻松的服务(C#,F#,C++等不同语言对象可以相互通信),同时还含有垃圾回收等机制。

那么CLR是如何实现这一功能的?

1,选择编译器,用面向Runtime的语言编译器(C#,VB,C++等)。

2,编译器编译

生成MSIL文件:将代码转换成Microsoft Intermediate language(MSIL)中间语言,类似Java中的.class

生成元数据(包含每种类型成员的签名,代码引用的成员等)

MSIL和元数据共同包含在一个可移植可执行的文件(PE文件),例如dll文件

3,将MSIL编译位本机代码

运行前必须根据CLR将MSIL编译位目标计算机基础架构的本机代码(特定CPU),通常采用.NET实时(JIT)编译器。

CLR为每个CPU基础架构提供JIT编译器。MSIL可通过JIT编译在具有不同计算机基础架构的不同计算机上运行。

JIT编译特点:根据需要在执行期间转换MSIL。通常流程:第一次调用某个方法,存根将控件传递给JIT编译器,JIT编译器将该方法的MSIL转换成本机代码,并将存根修改为直接指向生成的本机代码。

4,安全验证

主要验证检查MSIL及元数据,是否类型安全(仅访问有权访问的内存位置),有助于将对象相互隔离,可靠对代码强制执行安全限制。

5,运行

必须检查本机代码是否特定于处理器的代码。第一次调用,runtime生成MSIL的每种方法是JIT编译,下次运行该方法,运行现有本机代码。

同时有托管接受服务(垃圾筹集,安全性,版本控制支持)。

总结

,NET通过借助CLR实现将代码àMSILà特定CPU的本机代码à运行,CPU的版本是否匹配直接关系到Runtime阶段(因为JIT编译器按需执行,所以会推移到Runtime)

Runtime如何定位程序集

Runtime时,尝试解析其他程序集引用,开始查找,绑定程序集。引用可以为静态,编译器在生成时,记录程序清单的元数据中静态引用。由于Assembly.Load,可以及时构造动态引用。

1,检查配置文件

应用程序配置文件、发布服务器策略文件、计算机配置文件。每个文件均包含重定向绑定的元素,确定程序集版本等信息。

2,检查以前引用的程序集

如果该程序集先前调用请求过,CLR将使用已经加载的程序集,若之前失败,则立即失败,从.NET Framework2.0,将缓存程序集绑定故障。

3,检查GAC(全局程序集缓存)

GAC中存储拥有强名称的程序集

4,通过基本代码、探测定位程序集

其一:配置文件中检查<codeBase>。

其二:探测应用程序集,区域性目录:

  • [application base] / [assembly name].dll
  • [application base] / [assembly name] / [assembly name].dll

如果指定了被引用程序集的区域性信息,则只探测以下目录:

  • [application base] / [culture] / [assembly name].dll
  • [application base] / [culture] / [assembly name] / [assembly name].dll

.net执行模型——定位程序集依赖项

AssemblyLoadContext.Default负责定位程序集的依赖项。

 

 


运行时,主机提供一组命名的探测属性

1,使用与应用程序程序集文件路径中查找程序集名称匹配文件。

2,公共扩展名的app.path的程序集文件

.net执行模型——加载程序集依赖项

每个 .NET Core 应用程序均隐式使用 AssemblyLoadContext。 它是运行时的提供程序,用于定位和加载依赖项。只要加载了依赖项,就会调用 AssemblyLoadContext 实例来定位该依赖项。

何时加载:当代码使用另一个程序集中定义类型,编译器再插入这些引用,根据runtime需要进行加载程序集。

通常加载程序集不会立即解析其依赖项,依赖项在需要时加载:

1,          当代码分支到依赖程序集时

2,          当代码加载资源时

3,          当代码显示加载程序集时

因此.net执行模型加载依赖项也会推移到runtime时刻。

具体加载策略,在这里介绍托管程序集加载(静态程序集引用,每当代码使用在另一个程序集中定义的类型,编译器都会插入这些引用,根据运行时需要加载这些程序集

1,          确定(AssemblyLoadContext)参数输入类型

2,          根据assembly名称引入:调用load方法、检查AssemblyLoadContext.Default实例缓存并托管程序集默认探测逻辑。

3,          其他:查找缓存,从指定路径或原始程序集对象加载。

只要调用引入程序集实践,则将引用的AssemblyLoadContext实例添加到缓存中。

AnyCPU?x86?x64?platform选择不对导致的情况

选项在PlatformTarget其指定CLR的哪个版本运行程序集。

l  anycpu(默认值)将程序集编译成可在任意平台上运行。 您的应用程序将尽可能作为 64 位进程运行;当只有 32 位模式可用时,才会回退到 32 位。

l  anycpu32bitpreferred 将程序集编译成可在任意平台上运行。 在同时支持 64 位和 32 位应用程序的系统上,您的应用程序将以32 位模式运行。 只能为面向 .NET Framework 4.5 或更高版本的项目指定此选项。

l  ARM 将程序集编译成可以在具有高级 RISC 计算机 (ARM) 处理器的计算机上运行。

l  ARM64 编译程序集以在由 64 位 CLR 在具有支持 A64 指令集的高级 RISC 计算机 (ARM) 处理器的计算机上运行。

l  x64 将程序集编译成可由支持 AMD64 或 EM64T 指令集的计算机上的 64 位 CLR 运行。

l  x86 将程序集编译成可由 32 位、x86 可兼容 CLR 运行。

l  Itanium 将程序集编译成可由配有 Itanium 处理器的计算机上的 64 位 CLR 运行。

在 64 位 Windows 操作系统上:

l  用 x86 编译的程序集将在 WOW64 下运行的 32 位 CLR 上执行。

l  用 anycpu 编译的 DLL 将在加载它的进程所在的同一 CLR 上执行。

l  用 anycpu 编译的可执行文件将在 64 位 CLR 上执行。

l  用 anycpu32bitpreferred 编译的可执行文件将在 32 位 CLR 上执行。

版本不同导致错误的原因:

很多程序集可在 32 位 CLR 和 64 位 CLR 上同样运行。 然而,因为包含下列一个或多个原因,对于不同的 CLR,有些程序可能会有不同表现:

l  结构中包含大小随平台而改变的成员,例如任何指针类型。

l  指针算术包含固定大小。

l  平台调用错误,或使用句柄的 Int32 而非 IntPtr 的 COM 声明不正确。

l  将 IntPtr 转换到 Int32 的代码。

由于platform不匹配常常导致BadImageFormatException、DllNotFoundException.

Nuget错误处理机制

概念介绍:

“传递还原”:当将包安装到使用 PackageReference 格式的项目中时,NuGet 将添加对相应文件中的平面包关系图的引用提前解决冲突。 

重新安装或还原包:下载关系图中列出的包的过程。

nuget分析依赖项的流程:Nuget还原进程在生成之前运行,首先解析内存中的依赖项,然后将生成的关系图名为 project.assets.json 的文件,后MSBuild读取此文件,并将其转换成一组文件夹(可以在其中找到潜在引用),然后将其添加到内存的项目树中。

貌似Nuget有着一定的错误处理机制,并且在运行之前,那么为什么platform问题还是存在?先看下Nuget的错误处理机制都有哪些(可能不全)。

1,依赖项解析规则:最低适用版本、可变版本、选择最近项、等距依赖项。

2,排除引用

     项目可能多次引用具有相同名称的程序集,并且因此生成设计时、生成时错误:

 

 

在这种情况下
NuGet无法确定使用哪一个c.dll,但是不能直接删除包C的项目依赖项,因为包B也在依赖他。

解决方案:直接引用需要的C.dll,包C中添加不包括其所有asset的依赖项:<PackageReference Include="PackageC" Version="1.0.0" ExcludeAssets="All" />

3,解决包不兼容

包还原期间会看到error "One or more packages are not compatible..." or that a package "is not compatible" 。

如果项目引用未指示其支持包的目标框架,会出现:the package does not contain a suitable DLL in its lib folder for a target framework

例如,如果项目面向 netstandard1.6,并且你尝试安装仅在lib\net20\lib\net45 文件夹含 DLL 的包。

解决方案:将项目重定向到使用包所支持的框架。

CPU版本问题会什么会绕过Nuget的错误处理机制?

这个问题在Nuget支持多个目标框架中有涉及(特定于体系结构的文件夹)。

如果具有特定于体系结构的程序集,即面向ARM、x86、x64的单独程序集
(这些程序及仅在runtime可用),文件路径:

 

 

Nuget对于特定于体系结构的程序集只在运行时可用,因此绕过了处理机制。

总结

对于DLL处理器版本冲突问题,有下面几个阶段可能导致问题出现。

1,MSIL通过JIT编译器转换成特定于CPU的本机代码时:JIT编译器按需转换,可推移到runtime。且生成的本机代码是特定于CPU

2,DLL根据版本创建的是对于不同版本的CLR,整个CLR阶段都可能发生错误。

3,程序集依赖项按需加载,所以依赖项也会推移到runtime过程。

4,Nuget虽然在构建过程中,有着多个错误处理机制,但是大多在文件整体层面,没有下沉到dll的本身构成层面,例如排除引用处理机制,只是针对c.dll但是没有讨论包C的c.dll与项目的c.dll是否是同一个c.dll。因此处理器版本不同问题解决方案,让开发者进行考虑(生成的dll需要包含ARM、X86,X64多个版本),且这些程序集会到运行时可用。

对于处理器版本不同DLL导致的错误大体是更换DLL的处理器版本。本文认为也可以通过添加相应处理器版本的DLL到nuget的依赖加载项中,一定程度可以解决。

posted @ 2021-10-07 00:19  博二爷  阅读(378)  评论(0编辑  收藏  举报