详细解析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的依赖加载项中,一定程度可以解决。