托管程序的执行模型大致如下:
- 编译源代码为程序集(dll或exe文件),程序集包括了记录相关信息的元数据和IL代码
- 执行程序集文件时,启动CLR,JIT负责把IL编译为本地代码并执行
IL是微软推出的一种面向对象的类似汇编指令集合的中间语言,无论是C#还是VB代码,经过不同的编译器可编译为无差别的IL,或者说单看IL代码是无法分辨出它是来自C#还是VB,IL类似于Java的.Class文件,该文件具有平台无关性,这使得.Net程序具备了像Java那样跨平台的条件。
程序集的组成及编译
程序集就是平时我们看到的由源代码编译产生的dll或exe文件,程序集是一个windows PE文件,和Java界的jar文件差不多在一个逻辑层次上。
下面来分别介绍程序集的组成结构以及如何从源代码编译为一个程序集:
程序集的组成部分
一个程序集文件的组成如下:
PE头
包括如下信息:文件运行在32位系统还是64为系统上;文件类型是GUI,CUI还是DLL,文件的生成时间等
CLR头
包含以下信息:CLR版本;入口方法;元数据,资源的位置等
清单
包含了程序集本身的一些信息,如:名称,版本,模块,资源等
元数据
包含以下信息:源代码定义的类型和成员;源代码应用的类型和成员,Vistual studio的智能感知就是靠读取元数据实现的
IL代码
如题
资源
嵌入程序集的图片,声音等资源
模块
上述的内容都包含在模块中,一个程序集文件可以包含一到多个模块,可以手动使用csc命令编译多模块程序集,使用Visual studio 编译的程序集默认都是单模块的,关于更详细模块的介绍可以看这本书:《.NET之美:.NET关键技术深入解析》
欲了解程序集文件更详细的内容,请参考博客园牛人Anders Liu多年前制作的工具,这个工具可以打开一个程序集文件,查看里面的结构。目前Anders Liu又在GitHub上面创建了新项目peviewer,基于.Net core。
编译源代码为程序集
编程简单程序如下:
public class Program
{
public static void Main()
{
System.Console.WriteLine("Hello");
}
}
保存为d:\test\program.cs,打开“VS2015 开发人员命令提示”,执行如下命令:csc.exe /out:d:\test\program.exe \t:exe \r:MsCorLib.dll d:\test\program.cs
执行该命令即可生产程序集program.exe文件。上述命令中,/out:
后面为要生成的程序集文件名称,\r:
后面为要引用的程序集文件名,上述程序中的System.Console类即包含于此dll文件中,由于MsCorLib.dll是.Net Framwork最为核心的程序集,编译器默认加载,所以上面的程序也可省略\r:
选项。\t:
选项表示生成的程序集的类型,可以接“exe”或“winexe”,前者表示生成的是一个控制台程序集,后者表示生成的是图形界面程序集,由于编译器默认的选项就是“exe”,所以上述命令中/t:
选项也可省略。省略后的命令如下:csc.exe /out:d:\test\program.exe d:\test\program.cs
另外一个重要的概念是“respones file”,在csc.exe文件所在目录,即C:\Windows\Microsoft.NET\Framework\vX.X.X下面,有一个名称为csc.rsp的文件,该文件被称为“respones file”,其默认内容如下:
正如你想象的,执行csc.exe时,该命令会自动读取这个文件中记录的选项,可以修改此文件加入自己想要的选项,也可以自己新建一个respones file 如“myrsp.rsp",然后像这样执行命令:csc.exe @d:\test\myrsp.rsp /out:d:\test\program.exe d:\test\program.cs
更多的命令选项请看这里:https://msdn.microsoft.com/en-us/library/2fdbz5xd.aspx
运行程序集
托管代码的运行需要寄宿于操作系统提供的非托管的进程中,执行exe文件启动一个进程,该进读取CLR头后加载CLR,CLR启动后接管后续操作,包括内存管理,垃圾回收,异常管理等,在一个操作系统进程内部CLR又维护了一个或多个相当于轻量级进程被称为App Domain的逻辑隔离,不过,通常的程序大部分都是一个App Domain,关于App Domain的介绍可以参考:
- http://www.cnblogs.com/awpatp/archive/2009/11/24/1609570.html
- https://msdn.microsoft.com/en-us/library/cxk374d9.aspx
CLR使用JIT编译器编译IL为本地CPU指令,JIT编译器可根据CPU的不同而生成不同的CUP指令,仅当方法被一次执行时才会被编译为CUP指令,再次执行则读取编译好的指令。这也是为什么.Net程序在刚启动时较慢的原因,一个解决办法是使用NGen.exe工具直接将IL编译为本地CPU指令,但是存在一个同步的问题,及如果执行环境(换了CPU,CLR版本变化等)改变了,原来编译好的指令可能无法运行。另外使用NGen.exe编译来的CPU指令不会像JIT那样了解CPU的特点而能生成较为优化的指令,所有执行效率要低一些。
参考资料
- 《CLR via C#》
- 《.NET之美:.NET关键技术深入解析》