《CLR Via C#》第一章

目录

一、书籍分类和全书重点(尽量简化)

  1.书籍分类

  2.全书重点

二、第一章 CLR的执行模型

  1.本章重点(尽量简化)

  2.章节联系

    2.1一之二:

    2.2一之三

三、本章详解

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

  2.将托管模块合并成程序集

  3.加载公共语言进行时

  4.执行程序集的代码

  5.IL和验证

  6.不安全的代码

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

  8.Framework类库

  9.通用类型系统

  10.公共语言规范

  11.与非托管代码的相互操作性

四、问题

  1.为什么要有CLR?

  2.不同语言有啥用?

  3.程序集到底是什么,项目中哪些会成为一个程序集。

  4.程序集的逻辑表示和物理表示区有什么作用。项目中这些区域应该怎么利用。

  5.程序集的自描述有什么作用。

  6.可执行文件和DLL文件是什么。

  7.进程是什么?

  8.健壮性和稳定性具体是什么?

五、思维导图


一、书籍分类和全书重点(尽量简化)

  1.书籍分类

        理论文,主要讲的是CLR、C#和.Net的基础。

  2.全书重点

        由浅入深,透视CLR和.NET精髓,知其然,鞭辟入里,探究CLR和.NET机理,知其所以然。

二、第一章 CLR的执行模型

  1.本章重点(尽量简化)

        大致介绍了CLR执行应用程序的流程,编译代码流程,介绍了IL、元数据和程序集的关系。

  2.章节联系

    2.1一之二:

        第一章简单介绍了元数据和程序集,第二章将更为详细的介绍。

    2.2一之三

        第一章为程序集基础介绍,第三章详细介绍了强命名程序集。

三、本章详解

        尽量用图解法、比喻法来实现记录。要用能教导别人的方式来记录。

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

        公共语言运行时(Common Language RuntimeCLR是一款面向多种编程语言的运行时。CLR的核心功能可以面向所有语言使用。只要编译器是面向CLR的就行。

        编译源代码过程:编译结果是托管模块(managed module,32位或者64位Windows可移植执行体,PE32或PE32+文件)

源代码编译过程

        托管模块包括PE32或PE32+头、CLR头、元数据和IL(中间语言)代码。

托管模块组成

        本机代码编译器(native code compliers生成的都是面向特定CPU架构(x86、x64或ARM)的代码。反过来的是,每个面向CLR编译器生成的都是IL代码,有时也会称为托管代码

        面对CLR的编译器会在每个托管模块生成完整的元数据(metedata,描述了定义和引用了什么。元数据是一些老技术(COM的“类型库”(Type Library)和“接口定义语言”(Interface Definition Language,IDL)文件)的超集。编译器会同时生成元数据和代码,把他们绑定在一起,并嵌入安装模块中,这样元数据和它描述的IL代码就永远不会失去同步

        C#编译器总是生成包含托管代码(IL)和托管数据(可进行垃圾回收的数据类型)的模块。为了执行包含托管代码和托管数据的模块,用户在自己的计算机上必须安装好CLR

  2.将托管模块合并成程序集

        CLR实际上是和程序集(assembly)工作。程序集是一个或多个模块/资源文件逻辑性分组,也是重用、安全性以及版本控制的最小单元。可以生成单或者多文件程序集。

        程序集生成流程:一些托管模块或资源文件交由生成程序集的工具处理。工具生成代表文件逻辑分组的PE32(+)文件。PE32(+)包含元数据表集合的清单(manifest)。元数据表描述了构成程序集的文件、程序集中的文件所实现的公开导出的类型(public类型)以及与程序集关联的资源或数据文件。

程序集生成流程

        编译器默认将生成的托管模块转成程序集。程序集将他的逻辑表示和物理表示区分开。程序集模块包含着与引用程序集有关的信息,让程序集能够自描述(self-describing)。这自描述信息能够让CLR判断程序集的直接依赖对象(immediate dependency)是什么。不用在注册表保存信息。

  3.加载公共语言进行时

        生成的每个程序集可以是可执行应用程序或者DLL(其中一组由可执行程序使用的类型)。都是由CLR管理程序集中的代码执行。这就意味着目标机器必须安装好.Net Framework

  4.执行程序集的代码

        托管程序集同时包含元数据和IL。IL是和CPU无关的机器语言,可以视为面向对象的机器语言。注意:高级语言只能使用CLR全部功能的一个子集,然而IL汇编语言允许开发人员访问CLR全部功能。

        将方法的IL转换成本机(native)CPU指令,这操作由CLR的JIT(just-in-time,即时)编译器的职责。

        执行方法流程:

  • 在Main方法执行之前,CLR检测出Main的代码引用的所有类型(Console类)。
  • CLR分配一个内部数据结构管理引用类型的访问(Console的结构,存着方法和对应的JITCompiler)。
  • 在这个结构里边,每个方法都会对应一个记录项记录项含有一个地址,指向方法的实现(初始化的时候,记录项都设置成指向CLR内部的一个未编裆函数JITCompiler)。
  • Main方法调用WriteLine,JITCompiler被调用。JITCompiler负责将方法的IL代码编译成本机CPU指令
  • JITCompiler将本机CPU指令保存到动态分配的内存块中,并且回到CLR为类型创建的数据结构,修改此方法对JITCompiler引用,让其指向内存块
  • 第二次调用WriteLine就会直接执行内存块的代码(WriteLine第一次执行就已经进行了验证和编译)。

调用方法流程-第一次调用WriteLine

调用方法流程-第二次调用WriteLine

        JIT编译器保存本机代码内存是动态内存程序关闭就会丢掉编译的代码。只有再次运行程序,JIT编译器才会再次编译。

        CLR的JIT编译器可以对本机代码进行优化

  5.IL和验证

        IL是基于栈的,它的所有指令都要将操作数压入(push)一个执行栈,并从栈弹出(pop)结果。IL最大的优势是应用程序的健壮性和安全性,IL编译成本机CPU指令的时候,会有一个验证(verification)过程。检查高级IL代码,确定一切都是安全的(每个方法有正确的参数、类型之类的)。

        Windows每个进程都有自己的虚拟地址空间。独立地址空间能获得健壮性与稳定性,一个进程干扰不到另一个进程(不能简单的信任一个进程,万一有进程中间有狼人呢?)。

        CLR提供了一个操作系统进程中执行多个托管应用程序的能力,每个托管应用程序都在一个AppDomain中执行。每个托管EXE文件默认在他自己的独立地址空间中运行,这个地址空间只有一个AppDomain。

  6.不安全的代码

        Microsoft C#默认生成安全的代码。JIT编译器编译unsafe的方法时,会检查方法所在程序集是否被授予了System.Security.Permissions.SecurityPermission权限,而且System.Security.Permissions.SecurityPermissionFlag的SkipVerification标志是否设置。成功设置后,JIT会编译不安全的代码,要不然会抛出System.InvalidProgramException或者System.Security.VerificationException异常。

        Microsoft提供了PEVerify.exe的程序检查程序集所有方法,并报告其中含有不安全代码的方法。

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

        .NET Framework提供的NGen.exe工具可以在应用安装到用户计算机上的时候,就将IL代码编译成本机代码。JIT编译器无须运行的时候再编译IL代码,有助于提升应用程序的性能和启动速度。CLR加载程序集文件的时候,先会查看是否有NGen生成的本机文件,之后才会对IL进行JIT编译。

        NGen.exe优势:

  1. 提高应用程序的启动速度
  2. 减少应用程序的工作集(在进程的所有内存中,已经映射到物理内存那一部分),NGen.exe编译IL代码成本机代码,并将其保存在单独文件夹中,区分开来,这样的话如果有多个进程要用,可以通过内存映射的方式,共同利用,无须都分别拷贝一个单独的代码。

        缺点:

  1. 没有知识产权保护,如果只发布NGen生成的文件不包括IL代码是不可能的。CLR运行的时候就要求访问程序集的元数据,用于反射和序列化等功能,这回要求发布包含IL和元数据的程序集。防止CLR不能使用NGen.exe生成的文件。
  2. NGen生成的文件可能失去同步,CLR加载NGen文件的时候,会将预编译代码的许多特征与当前环境进行比较,但凡有一个不匹配,就得用JIT编译。
  3. 较差的执行时性能,NGen无法和JIT编译器一样对执行环境进行假设,NGen不能优化地使用特定CPU指令。

        使用NGen要谨慎

  8.Framework类库

        .Net Framework包含Framework类库(Framework Class Library,FCL)。FCL是一组DLL程序集的统称。有巨多的类型定义。例如:

  1. Web服务(Web service)
  2. 基于HTML的Web窗体/MVC应用程序(网站)
  3. “富”Windows GUI应用程序
  4. Windows控制台应用程序
  5. Windows服务
  6. 数据库存储过程
  7. 组件库

        FCL包含的类型数量巨多,所以相关类要放入单独的命名空间,例如Object、整数、字符、字符串、异常处理等放在的是System命名空间。

        使用Framework的任何功能,都得知道这个功能是哪个类型提供的,以及这个类型包含在哪个命名空间中

  9.通用类型系统

        CLR一切都围绕类型展开。通过类型,可以用一种编程语言写的代码能和另一种编程语言写的代码沟通。Microsoft定义了一个规范描述类型的定义和行为“通用类型系统”(Common Type System,CTS)。

  1. 字段(Field):作为对象状态一部分的数据变量。
  2. 方法(Method):针对对象执行操作的函数。
  3. 属性(Property):属性允许在访问值之校验输入参数和对象状态,或者在必要的时候才计算某个值。
  4. 事件(Event):事件在对象以及其他相关对象之间实现了通知机制。

        CTS还制定了类型可见性规则和类型成员访问规则(public、private、protected、internal等)。

        不用太在意CTS规则,因为学习语言的时候就会学会这种语法

  10.公共语言规范

        不同语言创建的对象可以通过COM相互通信。“公共语言规范”(Common Language Specification,CLS)定义了最小功能集,所有语言都包含这个功能集。

  11.与非托管代码的相互操作性

        不看,要用再看。

四、问题

  1.为什么要有CLR?

回答:

        简单来说,运行时就是一套完整的规范,它规定了创建和运行一个程序所需要的方方面面CLR 的目标是让编程变得简单

        编程肯定需要和硬件进行交互,例如接收用户输入、网络通信等。然后需要编译才能被硬件执行。例如,C++语言是通过对对应硬件架构(x86)、操作系统(Windows、macOS)绑定才能编译运行。并且形成的可执行程序只提供了运行一个程序所需要的信息。并没有提供引用库之类的东西来让里面的方法执行(类似printf)。举例来说,C++ 程序通常都会使用标准库(在 Windows 上叫做 msvcrt.dll),它包含了大多数常用的功能(例如 printf),但只有这一个库文件是不行的。程序员如果想使用这个库,必须还要有与它相匹配的头文件(例如 stdio.h)才可以。由此可见,现有的可执行文件格式标准无法同时做到1、满足运行程序的需求;2、提供使程序完整所必须的其他信息或二进制文件。

CLR 能够解决这些问题。因为它制定了一套非常完整的规范。这套规范描述了一个程序的完整生命周期中所需要的所有细节,从构建、绑定一直到部署和执行。并且支持多语言交互,语言之间的交互难点在于,它们只能使用操作系统所提供的基本功能与其他语言进行交互。由于操作系统的抽象层次太低(例如操作系统并不知道支持垃圾回收的堆内存是什么),因此跨语言交互通常都很复杂。通过提供公共语言运行时,CLR 允许语言之间使用更高层次的结构进行交互(例如支持 GC 的结构),极大简化了交互的复杂性。

  2.不同语言有啥用?

        回答:不同语言是针对不同需求的,可以节省开发时间。

  3.程序集到底是什么,项目中哪些会成为一个程序集。

        回答:在VS开发环境中,一个解决方案可以包含多个项目,而每个项目就是一个程序集

        应用程序包含应用程序域(AppDomain),程序集(Assembly),模块(Module),类型(Type),成员(EventInfo、FieldInfo、MethodInfo、PropertyInfo)

        他们之间是一种从属关系,也就是说,一个AppDomain能够包括N个Assembly,一个Assembly能够包括N个Module,一个Module能够包括N个Type,一个Type能够包括N个成员。他们都在System.Reflection命名空间下。

        CLR管理这个应用程序域,包括将每个程序集加载到相应的应用程序域以及  控制每个程序集中类型层次结构的内存布局

        我们所写的所有代码都会编译到程序集文件(.exe .dll)中,并在运行时以Assembly对象方式加载到内存中运行,每个类(Class  InterfaceType对象方式加载到内存,类的成员(方法,字段,属性,事件,构造器)加载到内存也有相应的对象。

  4.程序集的逻辑表示和物理表示区有什么作用。项目中这些区域应该怎么利用。

        回答:

        逻辑结构:实体数据元素间逻辑关系即实体性质理解基础进行抽象模型

        物理结构:数据元素计算机存储即计算机数据理解逻辑结构计算机语言映射

        把程序集的物理表示和逻辑表示区分开,将很少用到的类型和文件放到单独的文件中,并将这些文件作为程序集的一部分,如果运行时需要,则去下载,这样不仅节省了磁盘空间,还节省了安装时间.通过程序集,可以在不同的地方部署,同时任然将所有的文件当作一个整体来看待.

  5.程序集的自描述有什么作用。

        回答: 能让CLR判断直接依赖对象,以此来执行代码,不用通过注册表注册额外的信息。

  6.可执行文件和DLL文件是什么。

        回答: 可执行文件(.exe文件)和 类库文件(.dll文件),前者可以直接执行的文件,后者是供前者调用的文件

  7.进程是什么?

        回答:狭义定义:进程是正在运行的程序的实例

        广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

  8.健壮性和稳定性具体是什么?

回答: 健壮性主要描述系统对于参数变化的不敏感性,当你固定提供一个参数的时候,他能产生稳定的、能预测的输出。可靠性描述的是系统的正确性。例如你给了工人钱装修,他跑了,不可靠;他装修了,可靠,但是装修完赖在你家不走了,你没有赶他走(释放他占用的内存),就说这个程序不健壮。

五、思维导图

猜你喜欢

转载自blog.csdn.net/weixin_51374560/article/details/128746575