CLR基础全面版-概念、执行模型、托管模块、程序集、FCL

CLR介绍

概念:CLR Common Language Runtime 公共语言运行时
  1. 顾名思义,是多编程语言共同使用的运行时

    微软创建了很多个面向CLR的语言编译器:C#,C++,F#等

  2. CLR不关心运用什么语言编写源代码,只需要编译器面向CLR

    编译器:检查语法,分析源代码确定含义。不管是什么语言都会把代码生成托管模块

    某种语言A➡️语言A面向CLR的编译器➡️托管模块

    当方法被调用时,CLR把具体的方法编译成适合本地计算机运行的机器码,并且将编译好的机器码缓存起来,以备下次调用使用

执行模型
  1. 将源代码编译成托管代模块

  2. 将托管代码合并成程序集

    PE32(+)文件包含一个名为清单(manifest)的数据块。清单也是元数据表的集合。

    这些表描述了构成程序集的文件、程序集中的文件所实现的公开导出的类型以及与程序集关联的资源或数据文件。
    在这里插入图片描述

  3. 加载公共语言运行库

    1. 生成的每个程序集既可以是可执行应用程序,也可以是DLL(其中含有一组由可执行程序使用的类型)。再由CLR管理这些程序集中进行代码的执行,所以目标机器(需要执行程序的机器)必须安装好.NET Framework。
    2. 执行可执行文件运行时,Windows检查EXE文件头,加载不同版本的MSCOREE.DLL
      1. 判断需要32位还是64位地址空间,还会检查头中嵌入的CPU架构信息,确保当前计算机的CPU符合要求

        1. 如果程序集文件只包含类型安全的托管代码。则程序集在32位和64位Windows上都能正常工作,源代码无需任何改动。程序支持设置目标运行平台,不设置则如果程序集文件只包含类型安全的托管代码在32位和64位Windows上都能正常工作,源代码无需任何改动。
          • 编译器最终生成的EXE/DLL文件在Windows的x86和x64版本上都能正常工作
          • WindowsStore应用或类库能在WindoWsRT机器(使用ARMCPU)上运行
          • 只要机器上安装了对应版本的.NET Framework,文件就能运行。
        2. 程序支持设置目标运行平台
          • 极少数情况下,开发人员希望代码只在一个特定版本的Windows上运行。
            1. 例如,要使用不安全的代码,或者要和面向一种特定CPU架构的非托管代码进行互操作
          • 为此C编译器提供了一个/platform命令行开关选项。可以在VS上进行设置
            1. 这个开关允许指定最终生成的程序集只能在运行32位Windows版本的x86机器上使用,只能在运行64位Windows的x64机器上使用,或者只能在运行32WindowsRT的ARM机器上使用。
            2. 不指定具体平台的话,默认选项就是anycpu,表明最终生成的程序集能在任何版本的Windows上运行
            3. VisualStudio用户要想设置目标平台,可以打开项目的属性页,从生成”选项卡的“目标平台”列表中选择一个选项,如图所示。
              在这里插入图片描述
      2. MSCOREE.DLL负责选择.NET版本、调用和初始化CLR、调用EXE程序集、进入程序主方法Main等工作

        • 非托管程序想要启动CLR也必须引用MSCOREE.DLL,利用它的导出函数加载托管代码和进行定制CLR等操作。
  4. 执行程序集的代码 JIT

    JIT:将IL转为本机CPU指令

    • 编译时

      1. Main方法执行之前,CLR会检测出Main的代码引用的所有类型,并分配
        内部数据结构来管理对引用类型的访问。

        例如以上代码:Console类型定义的每个方法都有对应的记录项,每个记录项都含有一个地址、根据此地址可找到方法的实现。

    • 运行时

      1. 首次调用时JIT 首先基于元数据找到调用的类型(Console)方法(WriteLine)
      2. JIT 会在定义程序集的元数据中搜索被调用方法的IL
      3. JIT 为本地CPU指令动态分布一个内存块
      4. JIT 把IL代码编译成本地CPU指令,存储在分配好的内存块中
      5. JIT 回到类型表中,找到与被调用的方法对于的那一条记录,替换成内存块的引用地址
      6. JIT 会跳转到内存块中的代码,及WriteLine方法的具体实现
      7. Main要第二次调用WriteLine。由于已对WriteLine的代码进行了验证和编译,会直接执行内存块中的代码,跳过JITCompiler函数。WriteLine方法执行完毕后,会再次回到Main
      • JIT 会对本机代码进行优化

        两个C#编译器开关会影响代码优化:/optimize和/debug。下面总结了这些开关对C#编译器生成的IL代码的质量的影响,以及对JIT编译器生成的本机代码的质量的影响。

        • 使用/optimize

          • NOP(no-operation,空操作)指令、跳转到下一行代码的分支指令
            1. 在C#编译器生成的未优化IL代码中,将包含许多NOP(no-operation,空操作)指令,还包含许多跳转到下一行代码的分支指令。
            2. VisualStudio利用这些指令在调试期间提供“编辑并继续”(edit-and-continue)功能
            3. 利用这些额外的指令,还可在控制流程指令(比如for,while,do,if,else,try,catch和finally语句块)上设置断点,使代码更容易调试。
            4. 相反,如果生成优化的IL代码,C#编译器会删除多余的NOP和分支指
              令。控制流程被优化之后,代码就难以在调试器中进行单步调试了。
            5. 另外,若在调试器中执行,一些函数求值可能无法进行。
            6. 优化的IL代码变得更小,结果EXE/DLL文件也更小。
            7. IL更易读。
        • 指定/debug(+/full/pdbonly)开关

          1. 有指定/debug(+/full/pdbonly)开关,编译器才会生成ProgramDatabase(PDB)文件

            PDB文件帮助调试器查找局部变量并将IL指令映射到源代码。

          2. /debug:full开关

            1. 告诉JIT编译器你打算调试程序集,JIT编译器会记录每条IL指令所生成的本机代码。

              这样可利用VisualStudio的“即时”调试功能,将调试器连接到正在运行的进程,并方便地对源代码进行调试。

          3. 不指定/debug:full开关

            1. JIT编译器默认不记录IL与本机代码的联系

              这使JIT编译器运行得稍快,用的内存也稍少

  5. 本机代码生成器:NGen.exe

    1. 作用:使用.NETFramework提供的NGen.exe工具,可以在应用程序安装到用户的计算机上时,将IL代码编译成本机代码。由于代码在安装时已经编译好,所以CLR的JIT编译器不需要在运行时编译IL代码,这有助于提升应用程序的性能。

    2. NGen.exe能在以下两种情况下发挥重要作用。

      1. 提高应用程序的启动速度

        运行NGen.exe能提高启动速度,因为代码已编译成本机代码,运行时不需要再花时间
        编译。

      2. 减小应用程序的工作集

        如果一个程序集同时加载到多个进程中,对该程序集运行NGen.exe可减小应用程序的
        工作集。NGen.exe将IL编译成本机代码,并将这些代码保存到单独的文件中。该文
        件可以通过“内存映射”的方式,同时映射到多个进程地址空间中,使代码得到了共 享,避免每个进程都需要一份单独的代码拷贝。

      3. 相较于JIT具有较差的执行时效

      4. 不适合服务端应用程序,更适合客户端

.NET框架的核心,它为.NET应用程序提供了一个托管的代码执行环境

CLR的核心功能 :程序集加载,即时编译、内存管理,线程管理、安全管理、异常处理,远程管理等
在这里插入图片描述

  1. 组成元素及其功能
    1. 类加载器:管理元数据,加载和在内存中布局类;
    2. Micorsoft 中间语言(MSIL)到本地代码编译器:通过即时编译把Micorsoft 中间语言转换为本地代码;
    3. 代码管理器:管理和执行代码;
    4. 垃圾回收器:为NET.Framework下的所有对象提供自动生命期管理,支持多处理器,可扩展;
    5. 类型检查器:不允许不安全的类型转换和未初始化变量MSIL可被校验以保证类型安全。待续🌟
    6. 安全引擎:提供基于证据的安全,基于用户身份和代码来源;
    7. 调试器:使开发者能够调试应用程序和根据代码执行;
    8. 线程支持:提供多线程编程支持;
    9. 异常管理器:提供和Windows结构化异常处理集成的异常处理机制;
    10. COM封送拆收器:提供和COM组件之间的封送转换;
    11. .NET Framwork类库支持:通过和运行时集成代码来支持.NET Framwork类库。
    12. CLR代理了一部分传统操作系统的管理功能,提供服务:内存管理,安全管理,线程管理,垃圾回收,类型检查等。
    13. 在CLR的控制下运行的代码称为托管代码,否则称为非托管代码。托管代码(Managed Code)待续🌟
    14. 非托管代码指不在CLR控制下的运行的代码,由操作系统直接运行。
    15. 托管代码不能直接写内存,是安全的,非托管代码是非安全代码,可以直接操作内存地址

托管模块

  1. CLR用对应的编译器检查和分析源代码,生成托管模块

  2. 托管模块是标准的32位/64位Windows PE可移值的执行体(PE32文件或32+文件)

    Windows PE(Portable Executable) 即可移植的执行体,使用了PE文件结构的可执行文件被称为PE文件,Win下包括EXE、DLL、SYS、OCX等

  3. 组成:PE32头部分或PE32+头部分,CLR头,元数据,IL中间语言代码

    • PE32或PE32+头部分

      这个就是32位程序或64位程序,就在这里运行的,相信大家都知道vs编译器里面有这个(看下图),其实里面主要就是包含这个东西。其实我们一般的程序选择的平台都是Any CPU,因为我们平时写的程序都是只包含类型安全的托管代码。但是在有些时候,或者要面向一种功能特定的CPU架构的非托管代码互操作的时候。可能就是需要选择其他平台了。

      在这里插入图片描述

    • CLR头

      包含使这个模块成为托管模块的信息(可以有CLR和一些使用程序进行介绍),说白了这个就是让我们的CLR能够认识它。

    • 元数据

      1. 元数据和IL代码完全对应,保持一致性

        1. 元数据总是与包含IL代码的文件关联。

          元数据总是嵌入和代码相同的EXE/DLL文件中。所以元数据和它描述的IL的代码永远不会失去同步

      2. 每个托管代码都包含元数据表

      3. 元数据是由几个表构成的二进制数据块,简单来说是数据表的集合。

        1. 主要有两种表:1. 描述源代码中定义类和成员,2. 描述代码中引用的类型和成员。模块内部的一些元数据表的大小和偏移量在clr头中会有包含。
        2. 三种表:定义表、引用表、清单表
        • 定义表

          1. ModuleDef : 一个记录项。包含模块名、扩展名和模块版本ID(编译器创建的GUID);
          2. TypeDef : 每个类型一个记录项。包含类型名称、基类型、一些标志(public,private等)以及一些索引(指向MethodDef表中该类型的方法、FieldDef表中该类型的字段、PropertyDef表中该类型的属性以及EventDef表中该类型的事件);元数据是由几个表构成的二进制数据块
          3. MethodDef : 每个方法一个记录项。包含方法的名称、一些标志(private,public,virtual,abstract,static,final等)、签名以及方法的IL代码在模块中的偏移量。每个记录项还引用了ParamDef表中的记录项;
          4. FieldDef : 每个字段一个记录项。包含标志(public,private等)、类型和名称;
          5. ParamDef : 每个参数一个记录项。包含标志(in,out,retval等)、类型和名称;
          6. PropertyDef : 每个属性一个记录项。包含标志、类型和名称。
          7. EventDef : 每个事件一个记录项。包含标志和名称。
        • 引用表

          1. AssemblyRef : 引用的每一个程序集有一个记录项。包含绑定该程序集所需的信息:程序集名称、版本号、语言文化以及公钥Token(根据发布者的公钥生成的一个小的哈希值,标识了所引用程序集的发布者)。另外还包含了一些标志以及一个被CLR忽略的但可以用于程序集的二进制数据的校验和的哈希值。
          2. ModuleRef : 引用类型的实现的每个PE模块有一个记录项。包含模块名和扩展名。
          3. TypeRef : 每个引用的类型有一个记录项。包含类型的名称和引用(指向类型的位置)。如果类型在另一个类型中实现,引用指向一个TypeRef记录项。如果类型在同一模块中实现,引用指向一个ModuleDef记录项。如果类型在调用程序集内的另一个模块中实现,引用指向一个ModuleRef记录项。如果类型在不同的程序集中实现,引用指向一个Assembly记录项;
          4. MemberRef : 引用的每个成员(字段、方法、属性方法和事件方法)有一个记录项。包含成员的名称和签名,并指向对成员进行定义的类型的TypeRef记录项。
        • 清单manifest

          清单表中主要包含作为程序集组成部分的那些文件的名称。此外,还描述了程序集的版本、语言文化、发布者、公开导出的类型以及构成程序集的所有文件。

          CLR总是首先加载包含“清单”元数据表的文件,再根据“清单”来获取程序集中的其他文件。清单包含在PE文件中。

          清单元数据表(程序集的):

          AssemblyDef : 如果模块标识的是程序集,就包含单一记录项来列出程序集名称、版本、语言文化、一些标志、哈希算法以及发布者公钥(可为null);

          FileDef : 每个PE文件和资源文件都有一个记录项(清单本身所在文件除外,该文件在AssemblyDef的单一记录项中列出)。包含文件名、扩展名、哈希值和一些标志。如果程序集只包含自己的文件,则该表无记录(VS中不能创建多文件程序集,只能通过命令行);

          ManifestResourceDef : 每个资源文件一个记录项。包含资源名称、一些标志(是否外部可见:public,private)以及FileDef表的一个索引(指出包含在哪个文件中)。如果资源不是独立文件,那么资源是包含在PE文件中的流。嵌入资源,记录项会包含一个偏移量,指出资源流在PE文件中的起始位置;

          ExportedTypesDef : PE模块中导出的每个public类型有一个记录项。包含类型名称、FileDef表的一个索引(指出类型由程序集的哪个文件实现)以及TypeDef表的一个索引。

          AssemblyDef {1}-------------------->{*} FileDef {1}<----------------------------->{1} ManifestResourceDef

    1. 作用
      1. 元数据避免了编译时对原生C/C++头和库文件的需求

        因为在实现类型/成员的IL代码文件中,已包含有关引用类型/成员的全部信息。编译器直接从托管模块读取元数据

      2. VS的智能感知(提供了那些方法、属性、事件、字段),自动补全;

      3. 代码验证保证类型安全;

      4. 序列化、反序列 待续🌟

        能将对象序列化到内存块中,也能在远程机器上重建对象状态

      5. 垃圾回收(从元数据得知哪些根引用了对象)

      6. 反射 待续🌟

        反射是一种动态分析程序集、模块、类型、字段等目标对象的机制,它的实现依托于元数据

    • IL中间语言
      1. 编译器编译源代码生成的代码。
      2. 面向对象的机器语言
        1. 基于栈

        2. IL能访问和操作对象类型

        3. 提供了指令来创建和初始化对象、调用对象上的虚方法以及直接操作数组元素

        4. 提供了抛出和捕提异常的指令来实现错误处理

        5. IL最大的优势——应用程序的健壮性与安全性

          IL被编译成本机CPU,CLR会执行验证的过程,确定代码的安全性

1.CLR-编译器-检查分析代码
2.编译器-程序集链接器
3.加载公共语言运行库
源代码
托管模块
程序集
4.执行程序集的代码
5.生成本地代码
自描述
直接依赖对象-方便部署
组成清单
可移植执行体
组成部分
1.PE32+头部分
2.CLR头
3.元数据
4.IL中间语言代码
托管模块的信息
编译器编译源代码生成的代码
1. 描述源代码中定义类和成员
2. 描述代码中引用的类型和成员

程序集

  1. 程序集是可重用、可版本控制、可保护的最小单元

  2. CLR是和程序集工作的

  3. 编译器将托管模块转换为程序集 assembly

    程序集:

    1. C#编译器,程序集链接器:通俗的讲——程序集就是把IL代码和元数据以及一些资源文件,通过C#编译器,程序集链接器组装而成。
    2. 组成清单:生成程序集之后,它有一张清单,告诉我们这个东西有哪些组成。
    3. 自描述:包含它引用的程序集相关信息,CLR可以判断出程序集的直接依赖对象,不需要在注册表里面保存额外的信息。因此程序集部署相较于非托管组件非常容易。
    4. 托管程序集总是利用Windows的数据执行保护(Data Execution Prevention,DEP)和地址控件布局随机化(Adress Space Layout Randomization ASLR),这两个功能旨在增强整个系统的安全性ToDo

FCL

原文链接:https://blog.csdn.net/qq_45767140/article/details/118738596

  1. .NET Framework 的组成主要由FCL(.NET Framework 类库)和CLR(公共语言运行库)两部分组成。

  2. FCL (Framework Class Library)

    是生成.NET Framework 应用程序、组件和控件的基础

  3. CLR是.NET Framework 核心组件,负责管理程序的执行。

    1. 公共语言规范(CLS)

      1. 定义了不同语言之间必须遵守的共同标准

        包括函数调用方式、参数调用方式、数据类型和异常处理方式。

        在这里插入图片描述

    2. 通用类型系统(CTS)

      1. 指定了规范来描述类型的定义和行为
      2. 用于解决不同编码语言的数据类型不同的问题,实现不同语言之间数据类型的统一
  4. 部分常规的FCL命名空间

    System 包含每个应用程序都要用到的所有基本类型
    System.Data 包含用于和数据库通信以及处理数据的类型
    System.10 包含用于执行流1/O以及浏览目录/文件的类型
    System.Net 包含进行低级网络通信,并与一些常用Intermet协议协作的类型
    System.Runtime.InteropServices 包含允许托管代码访问非托管操作系统的平台功能(比如COM组 件以及Win32或定制DLL中的函数)的类型
    System.Security 包含用于保护数据和资源的类型
    System.Text 包含处理各种编码(比如ASCII和Unicode)文本的类型
    System.Threading 包含用于异步操作和同步资源访问的类型
    System.Xml 包含用于处理XML架构(XMLSchema)和数据的类型

【参考文档】

《CLR via C#》

C# CLR简介 - GDOUJKZZ - 博客园 (cnblogs.com)

clr 元数据 - draculav - 博客园 (cnblogs.com)

(28条消息) windows - CRT 、API 、标准库、系统调用的关系_小泽的博客-CSDN博客

https://baike.baidu.com/item/公共语言运行库/2882128?fromtitle=CLR&fromid=10567215&fr=aladdin

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值