托管程序的启动过程(.NET CLR 寄宿)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Iron_Ye/article/details/83485499

托管程序的启动过程(.NET CLR 寄宿)

大家都知道 C# 等托管语言编写的代码都会被编译成托管程序集(*.exe 或 *.dll),这些托管程序集最终都会托管给 CLR(公共语言运行时)来执行。那么,托管程序的启动过程是怎样的?CLR 又是如何寄宿到宿主程序中的?为了回答以上问题,本文将首先介绍托管程序集的生成过程;然后介绍托管程序的启动过程,以及该过程中 CLR 的加载流程。

一个托管应用程序首先被操作系统启动,然后由操作系统调用 CLR 来托管该程序。那么 .NET 框架到底以什么方式让操作系统认识 CLR 并且可以启动它呢?微软实际将 CLR 作为 COM 服务器实现在一个 DLL 中,并提供了标准的 COM 接口。既然是 COM 服务,也就意味着普通的非托管程序也可以调用 CLR 来运行托管代码,我们把这种调用方式叫做寄宿,把调用 CLR 的非托管程序叫做宿主。宿主程序不仅可以调用 CLR 来执行托管程序集,还可以通过它来进行内存管理、垃圾回收管理、策略管理、事件管理以及线程控制等高级管理。

如上图所示,托管语言编写的源代码文件会被编译成托管模块,多个托管模块连同一些资源文件会被合并成托管程序集。托管程序集通常有 EXE 和 DLL 两类,其中前者(EXE)能被操作系统直接启动,后者(DLL)可以被前者调用。托管 EXE 被启动后,操作系统会调用 CLR 来托管它。CLR 启动后,会创建 AppDomain 来加载并运行托管程序集。

  • 托管模块已经是标准的 PE 文件了,为了部署和版本管理的方便,编译器会将多个托管模块连同相关的资源合并成一个 PE 文件,合并生成的 PE 文件被称为程序集。
  • PE 文件的全称是 Portable Executable,意为可移植执行体(文件)。常见的 EXE、DLL、OCX、SYS、COM 都是 PE 文件,PE 文件是微软 Windows 操作系统上的程序文件(可能是间接被执行,如 DLL 文件)。

托管程序集的生成

如上所述,托管程序集的生成要经历两个步骤,首先是将源代码编译成托管模块,然后再将多个托管模块及资源(或数据)文件合并成程序集。

1. 将源代码编译成托管模块

如上图所示,源代码可以由多种托管语言编写,然后由相应的编译器编译成统一的托管模块。托管模块是标准的 PE 文件,其中除了包含中间语言(IL)代码和元数据(Metadata)外,还包含一些头信息。

  • PE32 或 PE32+ 头 :标准的 Windows PE 文件头,其标记了文件运行的系统版本、文件类型(GUI\CUI\DLL)、生成时间等。PE32 文件能在 Windows 32 位或 64 位上运行,PE32+ 文件只能在 64 位版本上运行。
  • CLR 头 :包含了一些托管信息,比如 CLR 版本、托管模块入口方法的元数据标记,以及模块的强名称等。
  • 元数据 :主要包含两种类型的表,一种类型的表描述源代码中定义的类型和成员;另一种类型的表描述源代码引用的类型和成员。
  • IL 代码 :编译器编译源代码时生成的代码,这些代码将在运行时被 CLR (JIT) 编译成 CPU 指令。

2. 将托管模块合并成程序集

CLR 实际不和模块一起工作。相反,它是和程序集一起工作的。如上图所示,程序集是由多个托管模块(PE 文件)和资源(或数据)文件合并而成的单个 PE 文件。合并生成的 PE 文件中包含一个名为 “清单(Manifest)” 的数据块,其描述了构成程序集的文件,由程序集中的文件实现的公开类型,以及与程序集关联在一起的资源或数据文件。

  • 默认情况下,编译器会把生成的托管模块转为程序集,即使只有一个托管模块。
  • 将托管模块合并为程序集,主要是方便于文件部署及版本管理。

托管程序的启动

前面介绍了托管程序集(PE 文件)的生成过程,托管程序集要么是 EXE 文件,要么是 DLL 文件。Windows 操作系统可以直接启动 EXE 文件,我们来看看托管 EXE 的启动过程是怎样的。

CLR 介绍

在介绍托管程序的启动过程之前,我们先来了解一下 CLR 。CLR(Common Language Runtime)是公共语言运行时,它可以加载并执行托管模块(将模块中的 IL 代码编译成 CPU 指令,并执行)。除此之外,CLR 还提供了以下功能:

  • 内存管理
  • 程序集加载
  • 安全性
  • 异常处理
  • 线程同步

CLR 被定义为 COM 服务,MSCorWks.dll(.NET Framework 1.0 | 2.0)和 Clr.dll(.NET Framework 4.0) 实现了此 COM 服务,这两个文件位于 %SystemRoot%\Microsoft.NET\Framework(64) 下的相应子目录中。

虽然 CLR 是 COM 服务,但是在创建 CLR 实例时并不直接使用的 CoCreateInstance 方法,而是使用了另一个被称为 “垫片” 的 MSCorEE.dll 文件,由它去决定创建哪个版本的 CLR 实例。这个文件位于如下位置:

  • %SystemRoot%\System32
  • %SystemRoot%\SysWow64

.NET Framework 4.0 支持单个进程中同时运行多个版本的 CLR,可使用 ClrVer.exe 来检查某个进程中的 CLR 版本。

CLR 加载流程

运行一个托管 EXE 文件时,Windows 会检查 EXE 文件头,决定创建 32 位、64 位还是 WOW64 进程,在进程地址空间中加载 MSCorEE.dll 的相应版本。然后,进程主线程调用 MSCorEE.dll 中定义的一个方法。这个方法初始化 CLR ,加载 EXE 程序集,再调用其入口方法(Main),随即,托管应用程序启动并执行。

首先,托管 EXE 的文件头中包含 JMP _CorExeMain 指令,该指令指向了 MSCorEE.dll 文件,因此该文件被启动。MSCorEE.dll 始终是最新版,它随最新的 CLR 一起部署在 %SystemRoot%\System32 (SysWow64) 目录中。

然后,MSCorEE.dll 会调用其内部的 CLRCreateInstance 来创建 CLR 实例。在创建实例过程中会从 EXE 中提取 CLR 的版本信息,应用程序也可通过配置文件中的 requiredRuntime & supportedRuntime 来为该过程指定 CLR 版本。

最后,CLR 接管宿主程序的主线程,加载托管模块并编译执行。

参考资料

本文主要参考了 《CLR via C#》 一书的以下两个章节:

  • 第 1 章. CLR 的执行模型
  • 第 22 章. CLR 寄宿和 AppDomain

猜你喜欢

转载自blog.csdn.net/Iron_Ye/article/details/83485499
CLR
今日推荐