本章目的:
如何把源代码生成为一个应用程序并且运行?
1.将源代码编译成托管代码
CLR(Common Language Runtime):可由多种编程语言使用的运行时
核心功能:内存管理、程序集加载、安全性、异常处理、线程同步
就是说:不关心语言是啥,都可以编译成某种格式运行到CLR上
(包括:C++、c#、VB、F#、等等。。。。)
这个格式叫做 托管模块(managed module),包括中间语言和元数据
托管模块的组成:
a.PE32 或 PE32+ 头:区分多少位,功能是标识文件类型以及生成时间
b.CLR头:CLR版本,标识,托管模块入口 Main 方法的 MethodDef 元数据,以及模块的元数据、资源、强名称、一些标识以及其他不太重要的数据项的位置/大小
c.元数据:两种表,一种:源代码中定位的类型和成员,另一种:描述源代码引用的类型和成员
(智能感知系统用,比如 Rider 或者 VS 代码编辑器方便写代码)
d.IL代码:编译源代码生成的代码,运行时 CLR 将 IL 编译成本机 CPU 指令
2.将托管模块合并成程序集
CLR 不和模块工作,和程序集(Assembly)工作
程序集有一个主要的清单(manifest)数据块,也是元数据表的集合
作用:描述了构成程序的文件、公开类型、以及与程序集关联的资源或者数据文件
比如说C#,通过c#编辑器(CSC.exe)可以将多个托管模块和资源文件合并成一个程序集
程序集可以事:可执行的应用程序 或者 DLL
3.加载公共语言运行时
CLR 管理程序集中的代码执行,要求目标及其必须装好 .Net Framework
Windows有32位和94位的版本,可以通过 -platform 命令控制编译平台
PE32/x86、PE32+/x64
Windows检查EXE文件头,决定创建32位还是64位进程,在进程地址空间加载 MSCorEE.dll 的不同版本
这个 DLL 里面会先调用初始化方法,会初始化 CLR ,然后调用入口 Main 方法
至此,程序启动并运行
4.执行程序集的代码
托管程序及包括元数据和 IL
IL 是和CPU无关的机器语言,可以提供指令创建和初始化对象、调用对象上的虚方法以及直接操作数据元素
也可以抛出和捕捉异常的指令实现错误处理
IL 就是面向对象的机器的语言
IL 基于栈,所有指令都要将操作数 push 执行栈,并从栈弹出 pop 结果
用 C# 等高级语言进行编程,编译器最后都会生成 IL(也可以用汇编去写)
微软有 ILAsm.exe 的IL汇编器,和 ILDasm.exe 的反汇编器
那如何执行 IL 里面的代码呢?
首先需要把 IL 转换成本机 CPU 指令,这是 JIT (just-int-time)即时编辑器的职责
比如说要执行下面的方法
static void Main()
{Console.WriteLine("Hello");Console.WriteLine("World");
}
在 Main 执行之前,CLR 会检测出 Main 的代码引用的所有类型,需要分配一个内部数据结构来管理对引用类型的访问
比如说这里面 引用了 Console 类型,CLR 会产生一个 Console 结构,Console 的每个方法都有一个 Entry ,每个 Entry 都有一个地址,根据地址可以找到方法的实现
CLR 将每个 Entry 都指向内部的一个未编档的函数, 也叫 JITCompiler (即时编辑器)
当首次调用 WriteLine 方法时,JITCompiler 函数会被调用,负责将方法的 IL 代码编译成本机的 CPU 指令
过程如下:
a.负责实现 Console 的程序集的元数据查找被调用的方法 WriteLine
b.从元数据中获取该方法的 IL
c.分配内存块
d.将 IL 编译成本机的 CPU 指令,将本地代码保存到刚才分配的内存中
e.在 Console 表中修改 Entr 的地址,让他指向 刚才分配的内存块
f.跳转到内存中的本地代码
当第二次调用 WriteLine 方法时,直接执行内存块中的代码,并不会经过 JITCompiler 函数
方法仅在首次调用时才会有一些性能损失,以后对该方法的所有调用都是以本机代码的形式全速运行
JIT 编辑器只会讲本机 CPU 指令存到动态内存中。程序一旦停止,编译好的代码就会被丢弃,再次运行,还会重新编译
编译器可以优化对本地代码进行一定优化
通过 -optimize 指令
未优化的 IL 代码会有许多 NOP(no-operation)空操作指令,还包含许多跳转到下一行代码的分支指令
在 VS 里面可以用这些指令调试,或者在 for、while、if 等流程指令上加断点
通过 -debug 指令
如果是 debug 会生成 Program Database(PDB)文件,帮助 VS 调试器查找局部变量并将 IL 指令映射到源代码
PS:
Windows每个进程都有独立的虚拟地址空间。防止应用程序读写无效的内存空间,不会干扰到另一个程序的代码
CLR 提供了在操作系统进程中执行多个托管应用程序的能力。每个托管引用程序都在一个 AppDomain(应用程序域)执行的
c# 编辑器默认生成安全代码,可以验证安全性。也可以写不安全代码(unsafe)
一般只有在对效率比较高的性能算法时,才需要这么做
微软提供了 PEVerify.exe 的程序,可以检查程序集的所有方法,报告其中是否有不安全代码
使用 NGen.exe 工具,可以提前将IL代码编译成本机代码,可以提高程序的性能
但是也有额外的问题,可以反编译,可能失去同步等
额外的 CLR 知识
Framework类库
.Net Framework 包括 Framework 类库(Framework calss library,FCL)
是一组 DLL 程序集的统称,包括了很多功能,可以利用这些创建应用程序
a.Web Service
b.基于 HTML 的 Web 窗体/ MVC 应用层序
c.Windows GUI 应用
d.Windows 控制台应用
e.Windows 服务
f.数据库存储过程
g.组件库
常用的 FCL 组件库命名空间
System、System.Data、System.IO、System.Net、System.Runtime.InteropSercivces、System.Security、System.Text、System.Threading、System.XML
通用类型系统
CLR 一切都围绕类型展开,微软注定了一个正式的规范来描述类型的定义和行为
通用类型系统(Common Type System,CTS)
包括:Field、Method、Property、Event
CTS也制定了类型可见性规则以及类型成员的访问规则
包括:private、protected、internal、protected internal、public
所有类型必须从预定义的 System.Object 类型继承
有一组最基本的功能
a.Equals(比较两个实例的相等性)
b.GetHashCode(获取实例的哈希码)
c.GetType(查询实例的真正类型)
d.MemberwiseClone(执行实例的按位拷贝,值类型是拷贝,引用类型是引用对象地址)
e.ToString(获取实例对象当前状态的字符串表示)
公共语言规范
公共语言规范(Common Language Specfication,CLS),详细定义了一个最小功能集
任何编译器只有支持这个功能集,生成的类型才能兼容由其他符合 CLS、面向 CLR 语言生成的组件
编程语言的各类构造和 CLR 的字段/方法有对应关系,可以通过 ILDasm.exe 反编译器查看
与非托管代码的互操作性
a.托管代码能调用 DLL 中的非托管函数
b.托管公司可以使用现有 COM 组件
c.费托管代码可使用托管类型
上一篇:5 包含多个段的程序