zoukankan      html  css  js  c++  java
  • 详细解析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 et20lib et45 文件夹含 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的依赖加载项中,一定程度可以解决。

  • 相关阅读:
    安装AD域时,出现NetBIOS名称冲突?
    SharePoint Server 2010 安装图解
    Visual C++ 2011519
    Visual C++ 2011520
    Visual C++ 2011518
    Visual C++ 2011520
    设计模式创建型 C++版本
    线程条件变量,一次性初始化
    Visual C++ 2011526
    Visual C++ 2011512
  • 原文地址:https://www.cnblogs.com/smartisn/p/15374768.html
Copyright © 2011-2022 走看看