概念
**公共语言运行时(Common Language Runtime,CLR)**是一个可由多种编程语言使用的“运行时”(早期译文为“公共语言运行库”)
它依靠.Net Framework开发平台运行
代码运行流程
代码主要运行流程如下:
-
语言编译
托管语言(C#、C++、Visual Basic、F#等等)C# =》 编译器(托管语言对应的编译器)C#编译器 =》 托管模块
-
程序集合并
多个托管模块 =》 程序集合并工具 =》 程序集(EXE/DLL文件)
-
CLR运行
程序集 =》 CLR管理 =》 CUP指令
需要注意C++编译器允许开发人员同时写托管和非托管代码,并生成到同一个模块中,它允许开发人员在托管代码中使用原生的C/C++代码,等待时机成熟后再使用托管类型。
托管模块
托管模块的主要结构
-
PE32或PE32+头
标准的Windows PE文件头,如果头使用PE32格式,文件能在32位系统与64位Windows上使用,如果使用PE64头,则只能在64位系统上使用。
还标识了文件类型:GUI、CUI、DLL
-
CLR头
包含使这个模块成托管模块的信息
包含依赖的CLR版本,一些标志(flag)、托管模块入口方法(Main方法)的元数据等等
-
元数据
主要包含两种表
描述源代码中定义的类型和成员
描述源代码引用的类型和成员
-
IL(中间语言)代码
编译器编译原代码时生成的代码,在运行时CLR将IL编译成 本机CPU指令
IL基于栈,是无类型指令
程序集
程序集是抽象概念,它可以是可执行应用程序,也可以是DLL
主要结构
-
清单:用于描述程序集中的文件集
-
多个托管模块
-
多个资源文件:.jpeg、.gif、.html文件等等
-
引用程序集信息:与引用程序集有关的信息(包含他们的版本号)
CLR初始化
- Windows检查EXE文件头,决定使用32位/64位进程只有,会在进程地址空间加载MSCorEE.dll的x86,x64或ARM版本。
- 进程的主线程调用 MSCorEE.dll中定义的一个方法,该方法初始化CLR,加载EXE程序集,再调用入口方法(Main)。随机托管引用程序启动并运行。
执行程序集代码
首次运行方法 =》 检测代码中存在的所有引用类型 =》 为每种引用类型分配一个内部结构
内部结构
内部结构对为该类型定义的每个方法生成对应记录项,也就是一个类型中所有方法的记录项合集
两种状态:
-
【通常状态】记录项:每个记录项都包含一个地址,根据此地址可以找到方法的实现
-
【首次执行状态】JITCompiler(JIT编译器):在内部结构初始化时,CLR将每个记录项都设置成(指向)包含在CLR内部的一个未编挡函数
-
记录项首次执行时JIT函数被调用。
-
执行过程
获取要调用方法信息 =》从程序集中的元数据中查找被调用方法的IL =》 验证IL,并编译成CPU指令 =》 保存指令到内存中 =》修改对应记录项地址为内存中指令地址 =》 跳转到内存块中的代码并执行
-
非首次执行时:由于首次执行时JIT已经编译过代码,所以会直接通过记录项中内存地址找打编译过的代码并执行
托管代码优劣势
优势:
-
代码安全
IL在编译成CPU指令时,CLR会执行IL验证的过程。这个过程会检查高级IL代码,确定代码所作一切都是安全的。
-
内存安全,进程性能
通过托管代码,可以保证代码不会不正确的访问内存,不会干扰到另一个应用程序的代码。这样就可以将多个托管应用程序放到同一个进程下执行,更少的进程意味着更少的资源销毁。
劣势:
- 由于IL代码进行逆向工程相对其他语言来说比较简单,所以代码安全较弱。这时候就需要第三方混淆器来打乱程序集元数据中的所有私有符号的名称。
不安全代码
C#编译器允许开发人员写不安全代码(unsafe)。不安全代码允许直接操作内存地址,并可操作这些地址处的字节。通常只有在与非托管代码进行交互操作,或者提升效率要求极高的算法性能时才需要这样做。
C#编译器要求包含不安全代码的所有方法都用unsafe
关键字标记,/unsafe
编译器开关来编译源代码。
当JIT编译器编译一个unsafe方法时,会检查该方法所在的程序集是否被授予了权限,如果没有授予权限JIT编译时会抛出异常,禁止方法的执行