class Program { static void Main(string[] args) { Console.Write("hello, world"); } }
在 CLR 执行代码前,会创建三个应用程序域,其中两个(系统域和共享域)对于托管代码是不可见的,它们只能由 CLR 启动进程创建,而提供 CLR 启动进程的是 mscoree.dll 和 mscorwks.dll (在多处理器系统下是 mscorsvr.dll) 。第三个则是默认的应用程序域。
系统域负责创建和初始化共享域和默认应用程序域,它将系统库 mscorlib.dll 载入共享域。
该域还包括了字符串驻留池。系统域在进程中保持跟踪所有域,并实现加载和卸载应用程序域的功能。
系统命名空间的基本类型,如 Object、ValueType、Array、Enum、String、Delegate 等,在 CLR 启动程序过程中会被预先加载到共享域中。
大部分的程序在运行期间只使用一个域,就是默认的应用程序域。也可以在程序中加载和卸载额外的域。
现在,我们已经基本上将程序的编译和运行的全过程粗糙地浏览了一遍。那么,运行 C# 的 Hello World 程序,需要的基本流程是:
1) 建立控制台项目,编写代码并通过编译。此时,会生成可执行文件。这是一个含有 IL 代码和元数据的程序集。
2) 运行这个程序集。因为 Windows 发现它是一个有 PE 文件头和 CLR 文件头的文件,于是知道它是 CLR 程序集。通过 CLR 创建系统域和共享域,并加载一个默认的应用程序域。
3) 在该应用程序域中,找到程序的入口点,通过 IL 中的 .entrypoint 即可。
找到入口点之后,使用类型加载器,加载该入口点所在的类型(Program 类型),生成 Program 类型对象(该类型就只有一个成员 Main 和一个 C# 生成的构造函数)。
同时,也生成了 Object 类型对象和 System.Type 类型对象。
生成 Object 类型对象时,用到了 mscorlib,因为 System.Object 是属于它的类型,但 mscorlib 已经被加载,所以不需要做额外的事情。
4) Helloworld 主程序的 IL 代码只有三行,nop 不算。进入 Main 函数,执行代码。
5) 通过 ldstr 加载字符串 Hello World,字符串是属于 mscorlib 的,且已被加载,故没问题,将字符串压入栈。
6) 使用 call 调用 WriteLine 方法,该方法不在本程序集中,而在 mscorlib 中。我们已经加载了 mscorlib,故没问题,继续。
7) 由于我们使用 call 调用方法,所以可以在 mscorlib 中找到 WriteLine 方法的 IL 代码,这段代码其实是 PreJIT 的,所以它的机器码是现成的。
8) 执行这段机器码,字符串出栈,打印到控制台上。
9) 执行 IL 代码 ret,返回,程序结束。
从运行角度看,Helloworld 程序和真正自己编写的程序相比简单很多,没有私有程序集的探测。
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/22352.html