1 CLR的执行模型
术语:
ü CLR:Common Language Runtime(程序exe或者dll执行时告诉Cpu做神马)
ü IL:Intermediate Language
ü Managed Module:托管模块
ü GUI:Graphical User Interface图形用户界面Windows下
ü CUI:Command line User Interface命令行模式的人机接口DOS下
ü FCL:Framework Class Library
ü CTS:Common Type System通用类型系统
ü CLS:Common Language Specification公共语言规范
1.1将源代码编译成托管模块
- C#编译过程:C#语言编写的源码——C#编译器编译——托管模块(IL和元数据)
- Microsoft面向CLR创建的编译器:C++,C#,VB,Jscript,J#,IL汇编器(其他部门也面向CLR创建了自己的编译器)
- 托管模块:IL代码:编译器编译源代码时生成的代码
元数据:源代码中及其引用的类和成员
PE32头或PE32+头:
1、标识在32位还是在64位Windows上运行
2、 标识文件类型:CUI,GUI,DLL
3、 记录文件生成时间
4、 (解释:PE等是指可执行程序(EXE或DLL)文件格式,每种文件格式都有一些该可执行文件加载到内存中所需要的信息(如程序入口点等),这些信息被组织 为特定的数据结构,放在该可执行文件的开头部分,所以叫文件“头”。)
CLR头:所要求的CLR版本,入口方法(Main),模块元数据、资源、强名称
- 默认情况下C++不托管,但是可以开启托管开关
1.2将托管代码合并成程序集
- 程序集:程序集是一个或多个模块\资源文件的逻辑性分组;是最小的重用、安全性及版本控制单元;在CLR的世界中,我们称之为“组件”。(比如一个项目有几个cs文件,最后生成一个dll这就是一个程序集,exe也可以)
- CLR和程序集工作而不是模块
- 项目只有一个文件(一个托管模块),且没有资源文件时,由C#编译器生成程序集。如果有多文件要合并到一个程序集,则要用到别的工具:AL.exe程序集连接器
1.3加载公共语言运行库
- 补充知识:32位Windows与64位Windows
简单的说x86代表32位操作系统 x64代表64位操作系统,所谓的32位64位应该是指表示每一个地址的位数比如000…..(共32位)00001表示一个地址
- 程序集可以是Dll也可以是Exe,最终由CLR管理在这些程序集中代码的运行
- 常规情况下,编写的代码在32位和64位操作系统上皆可正常运行,有些情况开发人员编写的代码需要在特定的windows版本上运行,通过在项目属性中设置目标平台进行,最终这个信息会被写入PE32或PE32+头中。编写32位程序可以在64位windows上运行,64位程序不可以在32位系统上运行。
- 过程:
1、 Windows检查EXE文件头,判断是创建32位进程还是64位进程还是WoW64进程
2、 Windows在进程的地址空间中加载MSCorEE.dll的对应版本x86、x64、IA64版本
3、 主线程调用MSCoreEE.dll内部定义的一个方法,此方法初始化CLR,加载EXE程序集然后调用其入口方法(Main)
4、 被托管的应用程序启动并运行
1.4执行程序集的代码
- 程序集的代码是托管类型的代码,是托管的exe或者dll,其代码是IL形式的。IL能访问和操作对象类型,能初始化对象。可以将IL想象成面向对象的机器语言。
- CLR的一个特性:允许在不同编程语言间方便的切换,“混合语言编程”
- 高级语言(C#)只是CLR的一个子集,IL则允许访问CLR的所有功能。
- 执行程序集代码过程调用一个方法的流程:
1、 在入口方法Main执行之前,CLR检测出Main代码引用的所有类型。CLR分配一个内部数据结构用于管理引用类型的访问,类型在Main中定义的每个方法都有一条对应记录 (地址),根据地址可以找到该方法的实现。
2、 将每一个要执行的函数记录对应到一个函数,这个函数就叫JITCompiler
3、 Main方法首次调用记录的一个方法时,调用JITCompiler函数
4、 JITCompiler函数随后将IL代码编译成本地CPU指令(告诉CPU干嘛),本地CPU指令保存在一个动态分配的内存块中
5、 JITCompiler回到CLR为类型创建的内部数据结构,找到与被调用方法对应的那条记录,将调用它的引用转换为内存块的地址
6、 JITCompiler跳到内存块中的代码。这些代码是方法的具体实现
7、 返回Main中代码
8、 第二次执行同一个方法时则不调用JITCompiler了而直接调用内存块中的(已经编译好的CPU指令)指令。
- C++编译好的代码是非托管代码,C#编译好的代码是托管代码。区别就是C++代码直接就是CPU指令了,二C#代码是IL中间代码,还要在运行时经过JIT编译器编译成CPU指令。这样势必会影响C#程序运行的速度,由于其JIT编译时还会分配动态内存所以还会占用一些内存。但是作者说JIT编译器做了很多优化,而且有一些非托管代码没有的优势如
1、 JIT能判断CPU,所以可以利用CPU的特殊指令
2、 JIT可以判断一个特定的测试在运行它的机器上是否总是失败,从而减少编译。??
3、 CLR可以评估代码的执行,减少不正确的分支??
- NGen.exe将程序集的IL代码编译成本地代码
- 将IL编译成本地CPU指令时,CLR会执行一个名为“验证”的过程。这个过程会检验高级IL代码,确定代码所做的一切都是安全的。如:参数,返回值,等
- 托管代码的另一个好处:因为有验证过程,一个地址空间可以运行多个托管程序,所以能在单个OS进程中运行多个应用程序,从而减少系统进程数提升性能。
- 每个多团的应用程序都叫做一个AppDomain。
- 不安全的代码允许直接操作内存地址,而且允许在这些地址处操作多个字节。
- C#要求不安全代码加入unsafe关键字,而且要打开/unsafe编译器开关
- IL与知识产权保护:
1.4.1 IL和验证
-
- 将IL编译成本地CPU指令时,CLR会执行一个名为“验证”的过程。这个过程会检验高级IL代码,确定代码所做的一切都是安全的。如:参数,返回值,等
- 托管代码的另一个好处:因为有验证过程,一个地址空间可以运行多个托管程序,所以能在单个OS进程中运行多个应用程序,从而减少系统进程数提升性能。
- 每个多团的应用程序都叫做一个AppDomain。
1.4.2 不安全的代码
- 不安全的代码允许直接操作内存地址,而且允许在这些地址处操作多个字节。
- C#要求不安全代码加入unsafe关键字,而且要打开/unsafe编译器开关
- IL与知识产权保护:
1、 第三方混淆器
2、 非托管模块实现保密算法,CLR实现托管与非托管之间的通信
3、 微软未来:数字资产管理铲平
*1.5本地代码生成器
- NGen.exe工具好处:
1、 加快应用程序启动速度
2、 减小应用程序的工作集
- 缺点:
1、 没有知识产权保护
2、 NGen生成的文件可能失去同步
3、 交叉的加载时性能
4、 较差的执行性能
1.6 Framework类库入门
- FCL:一系列程序集的统称
1.7通用类型系统
- CLS:描述类型的定义以及其行为方式
1、 一个类型可以包括另个或者多个成员
2、 指定了类型可视性规则和类型成员的访问规则
3、 还定义了对类继承、虚方法、对象生存周期等进行管理的规则
4、 所有类型最终必须从预定义类型System.Object继承
1.8 公共语言规范
- 微软定义了一个最小特征集:CLS,这个特征集实际上就是所有语言规范的一个交集。
- Private成员可以不符合CLS因为外面不可访问,如果想在程序集外用另一种语言访问程序及内成员,则该类型的public和protected成员必须符合CLS
- 使用[assembly: CLSCompliant(true)]标志程序集,告诉编译器检查该程序集的CLS相容性。
- CLS规则精简:CLR中一个类型的每个成员要么是字段(数据),要么是方法(行为),高级语言为了简化编程会提供如枚举,数组,属性,索引,委派,事件,构造器,终结器,操作符重载,转换操作符等概念,但是编译器会在编译源代码为IL时把他们一致转换为方法和字段。可以在ILDasm.exe中查看。这样一种归纳可以使得各种语言都能访问这种结构。
- 不符合CLS的代码,在另一种语言里面是没法使用的。
1.9 与非托管代码的互操作性
- CLR支持托管与非托管代码间三种类型的互操作:
1、 托管代码能调用DLL中的一个非托管函数
2、 托管代码可以使用现有的COM组件
3、 非托管代码可以使用托管类型