初入 CLR - 阅读《CLR via C#》笔记

最近买了一本书《CLR via C#》阅读了第一章 - CLR 的执行模型,对 .NET 一直提到的 CLR 和 .NET Framework 有了一个大致的了解。我理解主要体现在:
■ 各种术语有了一个大致的体会:CTS CLS 和 CLI 、CLR 与 .NET Framework、 IL(Intermediate Language) 、CIL(Common Intermediate Language)和 托管代码(Managed Code)。
■ 用 C# 写完一个程序被编译成应用程序后,是如何执行的。
■ CTS, CLS 是 CLI 规范的一部分,.NET Framework 是微软的 CLI 实现,当然微软也是 CLI 规范的发起者。
 
(注:以下是根据书中内容做的笔记)
按照书中的章节,CLR 的执行模型如下:将源代码编译成托管模块 -> 将托管模块合并成程序集(assembly) -> 加载公共语言运行时(CLR)-> 执行程序集的代码。
■ 什么是 CLR ?
引用 MSDN 中的一段文字:
“The .NET Framework provides a run-time environment called the common language runtime, which runs the code and provides services that make the development process easier. 
Compilers and tools expose the common language runtime's functionality and enable you to write code that benefits from this managed execution environment. Code that you develop with a language compiler that targets the runtime is called managed code”。
CLR 是一种概念性的统称,.NET 提供了这么一个运行时环境叫 CLR ,具体体现在编码阶段用到的类库,内存管理,异常处理等;编译阶段用到的编译器;执行阶段 JIT 编译器将 IL 转换成本机 CPU 指令。CLR 支持多种编程语言,采用 CLR 可以用不同的编程语言进行同一个项目开发。
 
■ 什么是托管模块,如何生成?什么是程序集,如何生成?托管模块和程序集有什么关联?
由书中可知,托管模块和程序集都是 PE(+) 文件,32 位或者 64 位 Windows 能加载它,并且能执行某些操作,但是托管模块需要被合并到程序集后才可以被使用。那么他们到底包含哪些内容,托管模块与程序集有哪些不同呢?
托管 PE 文件由 4 部分组成: PE32(+) 头、 CLR 头、元数据及 IL 。 IL 就是编译生成的托管代码。元数据是由几个表构成的二进制数据块。有三种表,定义表( definition table )、引用表( reference table )、清单表( mainifest table )。
程序集是一个或多个类型定义文件及资源文件的文件集合。在程序集的所有文件中,有一个文件容纳了清单(即,元数据中含有清单表)。清单也是一个元数据表集合,表中主要包含作为程序集组成部分的那些文件信息。以下是书中的部分描述:”程序集是抽象的概念,利用'程序集'这种概念性的东西,一组文件可作为一个单独的实体来对待。在 CLR 的世界中,程序集相当于'组件'。生成的每个程序集可以是可执行程序,也可以是 DLL ,最终由 CLR 管理这些程序集中的代码的执行。这意味着目标机器必须安装好 .NET Framework 。“。
托管模块在书中没有明确定义,应该就是指编译器生成的元数据表中未包含清单元数据表的 PE 文件。
常用的定义元数据表:
ModuleDef TypeDef MethodDef FieldDef ParamDef PropertyDef EventDef
常用的引用元数据表:
AssemblyRef ModuleRef TypeRef MemberRef
清单元数据表:
AssemblyDef FileDef ManifestResourceDef ExportedTypesDef
以上信息可以通过 ildasm.exe 工具查看。
 
关于日常描述这里归纳一下,假设 A.dll B.netmodule C.netmodule 这 3 个文件组成一个程序集, 其中 A.dll 文件中含有清单数据表( B C 中都未包含),我们则习惯叫 A.dll 是程序集文件,运行时 A.dll B.netmodule C.netmodule 要一起,缺少了 B 或者 C 运行时当用到其中的类型而文件找不到时,进程会报出异常。 A.dll 文件中含有清单数据表,清单数据表中具有关于 B 和 C 的信息,逻辑的认为 A.dll 已经包括了 B.netmodule 和 C.netmodule 。其他程序集需要和 B 或者 C 打交道时,也是间接的通过 A 进行,所以简略的叫 A.dll 是程序集文件。所以当遇见某文件是程序集文件时,需要再确认该程序集是否包含其他的文件。在这个例子中,B.netmodule 和 C.netmodule 则叫做托管模块。
 
指定以下任何命令行开关, C# 编译器都会生成程序集: /t[arget]:exe 、 /t[arget]:winexe 、 /t[arget]:appcontainerexe 、 /t[arget]:library 或者 /t[arget]:winmdobj 。所有这些开关都会造成编译器生成含有清单元数据表的 PE 文件。
C# 编译器还支持 /t[arget]:module 开关。这个开关指示编译器生成一个不含有清单元数据表的 PE 文件(有时叫托管模块)。 CLR 想访问其中的类型,必须将该文件添加到程序集。可以在 C# 编译器编译时用 /addmodule 开关添加模块到程序集。
另外也可以通过 AL.exe (程序集链接器)生成程序集。AL.exe 可以作用于多个编译器生成的托管模块。
 
☆ 生成单文件程序集。
文件 A.cs
class Program {
 static void Main()
 {
  System.Console.WriteLine("Hello, World!");
 }
}
csc /out:A.exe /target:exe /reference:mscorlib.dll A.cs (或 csc A.cs)
生成的 A.exe 是一个程序集,可以直接运行。这个程序集也就是它自身,未包含其他文件。
 
☆ 生成多文件程序集。
文件 A.cs
public class A {
 public static void AFunc()
 {
  System.Console.WriteLine("This is AFunc!");
 }
}
文件 B.cs
public class B {
 public static void BFunc()
 {
  System.Console.WriteLine("This is BFunc!");
 }
}
csc /out:B.netmodule /t:module B.cs
csc /out:A.dll /t:library /addmodule:B.netmodule A.cs
先生成托管模块 B.netmodule ,然后生成含有清单元数据表的文件 A.dll 并添加了 B.netmodule 到其中。这样就组成了一个多文件程序集。
文件 tmp.cs
class Program {
 static void Main()
 {
  A.AFunc();
  B.BFunc();
 }
}
csc /r:A.dll tmp.cs
生成 tmp.exe 测试结果。
 
☆ 通过 AL.exe 生成多文件程序集。
csc /t:module A.cs
csc /t:module B.cs
al /out:MultiFileLib.dll /t:library A.netmodule B.netmodule 
csc /r:MultiFileLib.dll tmp.cs 
生成 tmp.exe 测试结果。这时生成的 tmp.exe 是程序集文件,含有清单元数据表,但不含有 IL 代码。
 
■ 程序集中的代码是如何执行的?
程序集中可以执行的代码是 IL 。面向 CLR 的不同编译器都可以生成 IL 。如书中例子。
C# 源代码文件 -> C# 编译器 -> 托管模块(IL 和 元数据)
Basic 源代码文件 -> Basic 编译器 -> 托管模块(IL 和 元数据)
IL 源代码文件 -> IL 汇编器 -> 托管模块(IL 和 元数据)(ILAsm.exe IL 汇编器和 ILDasm.exe IL 反汇编器)
高级语言只提供了 CLR 全部功能的一个子集。然后,IL 汇编语言允许开发人员访问 CLR 的全部功能。总之,采用最合适的编程语言来完成任务。
首先,IL 代码是无法直接执行的,为了执行方法,必须把 IL 转换成本机(native)CPU 指令。这是 CLR 的 JIT(just-in-time)编译器的职责。
以如下代码为例,程序集中的代码如何被执行呢?
static void Main() {
    System.Console.WriteLine("Hello");
    System.Console.WriteLine("Goodbye");
}
编译生成单文件程序集,将程序集加载到内存中时,为了执行方法 System.Console.WriteLine ,首先会在实现类型 System.Console 的程序集的元数据中查找方法 WriteLine ,获取到它的 IL ,JIT 编译器将 IL 翻译成本机 CPU 指令,然后再执行指令。再次调用 System.Console.WriteLine 函数时,由于函数已被 JIT 编译,此时直接执行内存中的本机 CPU 指令。以上就是程序集执行的简要过程。
★ 说明。
对于 IL 的说明。IL(Intermediate Language,中间语言)是一种与 CPU 无关的抽象机器语言,它需要被转换成本机 CPU 指令才可以执行。对于 CLR,IL 具体是指 CIL(Common Intermediate Language)起源于 MSIL(Microsoft Intermediate Language),之后 CLI(Common Language Infrastructure)标准化,现在已被正式称作 CIL 。具体资料可以查询 CIL 。
对于 托管 的说明。似乎 .NET Framework 习惯将与 CLR 有关的一些东西称为托管 xxx 。如 托管代码,托管 PE 文件,托管模块等。而与 CLR 没有关系与本机 CPU 直接相关,则称作非托管 xxx 。如用 C/C++ 编译器写的非托管代码。叫法而已。具体要根据上下文能找到说描述的对象。
 
■ CLI 规范。
以下是书中的内容:”CRL 一切都围绕类型展开。到目前为止,这一点应该很清楚了。类型向应用程序和其他类型公开了功能。通过类型,用一种编程语言写的代码与用另一种编程语言写的代码沟通。由于类型是 CLR 的根本,所以 Microsof 制定了一个正式的规范来描述类型的定义和行为。这就是通用类型系统(Common Type System,CTS)“。
以下是书中的内容:”不同的语言创建的对象可通过 COM 相互通信。CLR 则集成了所有语言,用一种语言创建的对象在另一种语言中,和用后者创建的对象具有相同的地位。之所以能实现这一的集成,是因为 CLR 使用了标准类型集,元数据以及公共执行环境。
要创建很容易从其他编程语言中访问的类型,只能从自己的语言中挑选其他所有语言都支持的功能。为了在这个方面提供帮助,Microsoft 定义了公共语言规范(Common Language Specification,CLS),它定义了一个最小功能集。任何编译器只有支持这个功能集,生成的类型才能兼容由其他符合 CLS 、面向 CLR 的语言生成的组件。“。
以下是书中的内容:”CLR/CTS 提供了一个功能集。有的语言公开了 CLR/CTS 的一个较大的子集。如果开发人员用 IL 汇编语言写程序,可以使用 CLR/CTS 提供的全部功能。“。具体如下图(仿照书中)所示。
 
实际上微软已经把 CTS, CLS 等进行了标准化,统称公共语言架构(Common Language Infrastructure,  CLI),具体参考维基百科。下面是维基百科中引用的一句话:”The .NET Framework and the free and open source Mono and Portable.NET are implementations of the CLI.“。可知 .NET Framework 是 Windows 上的实现。而 Mono 则支持跨平台,且开源。
 
目前有部分手游服务器就是采用 Mono 。因此这也是我认真学习 C# 的原因。毕竟了解一种 GC 的静态语言还是有必要的。其实我的想法是精通 C C# Lua ,以此为基础进行扩展。不知道最终能不能完成这个目标。加油。
 
引用:
1)维基百科,Common Language Infrastructure,https://en.wikipedia.org/wiki/Common_Language_Infrastructure
2)维基百科,Common Intermediate Language,https://en.wikipedia.org/wiki/Common_Intermediate_Language

 

posted @ 2015-08-15 15:45  iiRecord  阅读(318)  评论(0编辑  收藏  举报