.NET框架的核心便是通用语言运行时(CLR),顾名思义它是一个可被各种不同的编程语言所使用的运行时.CLR的很多特性可用于所有面向它的编程语言.比如,如果CLR用异常来报告错误,那么所有面向它的语言都将通过异常来得到错误报告.如果CLR允许我们创建线程,那么所有面向它的语言也都可以创建线程.在实际中,CLR在运行时对开发人员用何种编程语言来完成源代码一无所知.这意味着我们应该选择那些能够最容易表达我们意图的编程语言.我们可以用任何自己喜欢的语言来编写代码,前提是我们使用的编译器能够面向CLR的代码.
针对c#: c#源代码文件(.cs)→c#编译器→托管模块(IL和元数据) .最终是将源代码编译为托管模块.托管模块是一个需要CLR才能执行的标准Windows可移植执行文件(PE).
PE的各个组成部分:
①PE表头:该表头指出了文件类型,比如GUI和DLL,另外还包括一个时间标记用于表示文件创建日期,对于仅包含IL代码的模块,该表头的大多数信息会被忽略.对于包含本地CPU代码的模块,该表头还会包含有关本地CPU代码的一些信息.
②CLR表头:包含标识托管模块的一些信息(可以被CLR或者一些实用工具解析).这些信息包括托管模块所需要的CLR版本号,一些标记,托管模块入口点方法(Main)方法的MethodDef元数据标记,以及有关模块的元数据、资源、强命名、标记和其他一些意义不是太大的信息的位置和尺寸.
③元数据 每个托管模块都包含一些元数据表.元数据表主要分两种,一种用于描述源代码中定义的类型和成员,一种用于描述源代码中引用的类型和成员
④中间语言(IL)代码 编译器在编译源代码时产生的指令.CLR在运行时会将IL代码编译成本地CPU指令.
由于生存期和执行行为受CLR管理的缘故,IL代码有时也被称为托管代码.实际上元数据总是和这些IL代码一起被嵌入到同一个EXE/DLL文件中,两者根本不能分离.因为编译器总是同时产生元数据和IL代码,并且总是同时将它们嵌入到生成的托管模块中,所以元数据和它描述的IL代码之间总能保持同步.
元数据的作用:
1.元数据省去了源代码编译时对头文件和库文件的需求,这是因为在含有实现类型和成员的IL代码文件中,已经包含了所有被引用的类型和成员的信息.编译器可以直接从托管模块中读取元数据来获得这些信息.
2.VS可以利用元数据来辅助我们编写代码.它的智能感知特性就是通过分析元数据来告诉我们某个类型提供了哪些方法,以及某个方法有哪些参数.
3.CLR的代码验证过程可以利用元数据来确保代码仅执行"安全"操作.
4.利用元数据,我们可以将一个对象的字段序列化到一个内存中,然后远程传送给另一台机器,最后再在远程机器上执行反序列化,从而重新创建对象和它的状态.
5.利用元数据,垃圾收集器可以追踪对象的生存期.对于任何对象,垃圾收集器都能够通过元数据来确定该对象的类型,并且可以获知该对象的哪些字段引用了其他对象.
程序集
CLR其实并不和托管模块打交道,它直接打交道的对象是程序集.程序集是一个抽象的概念,刚开始往往很难理解.首先,程序集是一个或多个托管模块,以及一些资源文件的逻辑组合.其次,程序集是组件复用,以及实施安全策略和版本策略的最小单位.根据我们对编译器和相关工具所做的选择,程序集可以是一个文件或者多个文件.
当生成一个EXE程序集时,编译器/链接器会产生一些特殊的信息,并将它们嵌入到结果程序集的PE文件表头及其各个组成文件的.text部分.当EXE文件被调用时,这些特殊的信息将导致CLR被加载并初始化.CLR随后会定位到应用程序的入口点方法,从而以此来启动应用程序.类似地,如果一个非托管应用程序通过调用LoadLibrary来加载一个托管程序集,那么该托管程序集DLL的入口点函数也会知道去加载CLR来处理包含其中的代码. 加载原理如下: