您现在的位置是:首页 >技术杂谈 >NET框架程序设计-第1章.NET框架开发平台体系架构网站首页技术杂谈

NET框架程序设计-第1章.NET框架开发平台体系架构

CRongQ 2024-07-03 18:01:02
简介NET框架程序设计-第1章.NET框架开发平台体系架构

1.1 .NET 框架基本组成

.NET 框架的核心便是通用语言运行时(Commomn Language Runtime,简称 CLR),CLR 是一个可被各种不同的编程语言所使用的运行时。
托管模块(mangaed module): 一个需要 CLR 才能执行的标准 Windows 可移植可执行(porttable executable ,简称 PE)文件。

将源代码编译为托管模块:
请添加图片描述

表1-1 描述了一个托管模块的各个组成部分
请添加图片描述

托管代码:由于生存期和执行行为受 CLR 管理的缘故,IL 代码有时也称作托管代码
元数据:为托管模块产生完整的元数据。一个数据表的集合。一些用于描述托托管模块中所定义的内容(比如所定义的类型和它们的成员),另外还有一些用于描述托管模块中所引用的内容(比如被引用的类型和它们的成员)。元数据是一些早先的技术如类型库、接口定义语言(IDL)文件的一个超集(CLR 元数据包括但不仅限于列举出来的文件)。

编译器同时产生元数据和 IL 代码,并且总是同时将它们嵌入到生成的托管模块中。

元数据有很多用处,比如:

  • 元数据省去了源代码编译时对头文件和库文件的需求,这是因为在含有实现类型和成员的 IL 代码文件中,已经包含了所有被引用的类型和成员的信息。编译器可以直接从托管模块中读取元数据来获得这些信息。

  • Visual Studio .NET 可以利用元数据来辅助我们编写代码。它的智能感知(IntelliSense)特性就是通过分析元数据来告诉我们某个类型提供了哪些方法,以及某个方法有哪些参数。

  • CLR 的代码验证过程可以利用元数据来确保代码仅执行“安全”的操作

  • 利用元数据,我们可以将一个对象的字段序列化到一个内存块中,然后远程传送给另一台机器,最后再在远程机器上执行反序列化,从而重新创建对象和它的状态

  • 利用元数据,垃圾收集器可以追踪对象的生存期。对于任何对象,垃圾收集器都能够通过元数据来确定该对象的类型,并且可以获知该对象的哪些字段引用了其他对象。

微软的C++ 编译器默认生成的是非托管模块,但可以通过指定一个新的命令行开关,C++ 编译器也能够产生处需要 CLR 才能执行的托管模块。

1.2 将托管模块组合为程序集

CLR 实际上并不和托管模块打交道,它直接打交道的对象是程序集(assembly)。
程序集:一个或多个托管模块,以及一些资源文件的逻辑组合。程序集是组件复用,以及实施安全策略和版本策略的最小单位

图1-2 将托管模块组合为程序集
请添加图片描述

PE 文件(托管模块):包含了一个称作清单(manifest)的数据块。所谓清单仅仅是另外一些元数据表的集合。这些表描述了组成程序集的文件,程序集所有文件中实现的共有导出类型,以及一些和程序集相关的资源文件或数据文件。
托管模块与程序集

  • 仅包含以管模块、并且没有资源(或者数据)文件,程序集就是托管模块。程序集中的模-
  • 块还包含它所引用的程序集的一些信息(如版本号信息)。它不需要再在注册表或者活动目录(Active Directory)中获取额外的信息。因此,程序集的部署要比非托管组件的部署容易得多。

1.3 加载通用语言运行时

判断机器中是否安装了 .NET 框架:

  • 通过在 %windir%system32 目录下查找 MSCorEE.dll 文件来判断。(…WindowsSystem32 目录下)
    判断机器中安装了哪些版本的 .NET 框架:
HKEY_LOCAL_MACHINESOFTWAREMicrosoft.NETFramewirkpolicy

启动应用程序:
当生成一个 EXE 程序集时,编译器/链接器会产生一些特殊的信息,并将它们嵌入到结果程序集的 PE 文件表头及其各个组成文件的 .text 部分。当 EXE 文件被调用时,这些特殊的信息将导致 CLR 被加载并初始化。 CLR 随后会定位到应用程序的入口点方法,从而以此来启动应用程序。

图1-3 加载并初始化 CLR
请添加图片描述

MSCorEE.dll 表示为微软组件对象运行时执行引擎。

托管应用程序启动:
当编译器/链接器创建一个可执行程序集时,x86 stub 函数 JMP CorExeMain 被嵌入到PE文件里。
CorExeMain 函数 从 MsCorEE.dll 动态链接库中导入的,所以 MsCorEE.dll 将在程序集文件的导入==(.idata)==部分被引用。

执行过程:

  • 1、当托管 EXE 文件被调用时,Windows 将像对待通常(即非托管)的 EXE 文件一样来对待它。

  • 2、Windows 加载器首先加载该EXE文件,然后检查其 .idata 部分发现 MSCoreEE.dll 需要被加载到进程的地址空间,于是加载器获取 MSCoreEE.dll 中 __CoreExeMain 函数地址,同时修正托管 EXE 文件中 stub 函数的 JMP 指令

  • 3、进程的主线程开始执行修正后的 x86 stub 函数,该 stub 函数立即跳转到 MSCorEE.dll 中的_CorExeMain 函数上。

  • 4、_CorExeMain 函数接着初始化 CLR,并查看可执行程序集的 CLR 表头以确定要执行的托管入口点方法。

  • 5、入口点方法找到后,其 IL 代码随之被编译为本地 CPU 指令

  • 6、最后,CLR 跳转到编译后的本地指令上(使用进程的主线程)。

  • 7、这时,托管应用程序才算开始真正运行

托管 DLL 执行:
编译器/链接器将会为 DLL 程序集 PE文件的 .text 部分产生一个类似 x86 stub 函数: JMP __CorDllMain。
_CorDllMain 函数 从 MsCorEE.dll 动态链接库中导入的,所以 托管DLL 中的 (.idata)部分 也包含有对MSCorEE.dll 的引用。

执行过程:

  • 1、当 Windows 加载托管 DLL 时,它将自动加载 MSCorEE.dll(如果它还没有被加载)。

  • 2、然后获取 _CorDllMain 函数的地址,并修正托管 DLL 中 x86 stub 函数的 JMP 指令。

  • 3、之后,调用 LoadLibrary 加载托管 DLL 的线程将跳转到该托管 DLL 中的x86 stub 函数上。(非托管代码加载托管 DLL 步骤)
    托管代码调用托管 DLL: 首先检测托管 DLL 中的元数据,然后便以即时编译的方式执行其内方法的 IL 代码,stub 函数及其跳转过程被忽略)

  • 4、该 stub 函数接着又立即跳转到 MSCorEE.dll 中的 _CorDllMain 函数上。

  • 5、_CorDllMain 初始化 CLR(如果 CLR 还没有为该进程初始化)后便立即返回。(非托管代码加载托管 DLL 步骤)

  • 6、应用程序也返回到正常状态并继续运行。

注意:托管 PE 文件总是使用 32 位的 PE 文件格式。在64位的 Windows 系统上,操作系统加载器检测到 32 位的托管 PE 文件后会自动创建64位的地址空间。

1.4 执行程序集代码

IL 代码的作用:理解对象类型,并且拥有很多高级的指令,这些指令可以创建和初始化对象,调用对象上的虚方法,以及直接操作数组元素。它甚至还有抛出和捕获异常的指令。
IL 汇编器:ILAsm.exe
IL 反汇编:ILDasm.exe

即时(just-in-time)编译器执行内容:执行一个方法,它的 IL 代码还必须首先被转换成本地 CPU 指令。

方法的第一次调用:
请添加图片描述

执行过程:

Main函数第一次调用:

  • 1、在 Main 方法执行之前,CLR 首先会检测 Main 中代码引用到的所有类型。这会导致 CLR 分配一个内部的数据结构。该数据结构用于管理对所引用到的类型的访问。(比如为 Console 分配一个单独的内部结构)

  • 2、在这里内部的数据结构中, Console 类型中定义的每一个方法都会有一个对应的条目,每个条目中将保存有一个方法实现代码的地址。

  • 当该数据结构被初始化时,CLR 将==把每一个条目设置为 ==CLR 内部的一个没有正式记录的函数:JITCompiler。

  • 当 Main 方法第一个调用 WriteLine 时, JITCompiler 函数将被调用,该函数负责将一个方法的 IL 代码编译成本地 CPU 指令。因为 IL 代码是被“即时(just-in-time)”编译的,所以 CLR 的这一部分通常被称作 JITter 或者即时编译器。(JIT compiler)

  • 当 JITCompiler 函数被调用时,它会知道正在调用的是哪个方法,以及该方法是由哪个类型定义的。

  • JITCompiler 函数随后会在被调用方法所定义的程序集中的元数据内搜索其 IL 代码的位置

  • JITCompiler 接着验证这些 IL 代码并将编译成本地 CPU 指令。这些本地 CPU 指令将被保存在一个动态分配的内存块中

  • 然后 JITCompiler 将前面内部数据结构中被调用方法的地址替换为包含本地 CPU 指令的内存块地址。

  • 最后 JITCompiler 将跳转到该内存块中的代码上。这里的代码就是 WriteLine 方法的实现代码。当该代码执行完毕,它将返回到 Main 函数中,Main 函数会接着继续执行下面的代码。

Main 函数第二次调用:

  • 1Main 函数开始第二次调用 WriteLine。由于 WriteLine 中的 IL 代码已经被验证并且编译过,所以这一次将直接调用内存块中已有的本地代码,完全跳过 JITCompiler 函数的验证和编译过程。
  • 在 WriteLine 方法执行之后,同样将返回到 Main 中。

请添加图片描述

1.4.1 IL与代码验证

IL 是一种基于堆栈的语言。意味着它的所有指令或者是将操作数推进一个执行堆栈中,或者从堆栈中弹出结果。
IL 指令是无类型的。
当 IL 代码被编译为本地 CPU 指令时,CLR 将执行一个称作验证的过程。验证过程检查高级 IL 代码,确保它做的每件事都是”安全“的。

验证过程可以验证的情况:

  • 不能从未初始化的内存中读取数据;
  • 每个方法调用都必须传入正确的参数个数,并且各个参数的类型要正确匹配;
  • 每个方法的返回值都必须被正确地使用;
  • 每个方法都必须有一个返回语句;

托管代码的好处:

  • 通过验证托管代码,确保它们不会访问它们不应该访问的内存,因此也就不会干扰另一个应用程序的代码。
  • 在一个操作系统进程中运行多个托管应用程序可以减少进程的数量,从而提高系统性能,降低资源需求。

应用程序域: CLR 提供了在一个单独的操作系统进程中执行多个托管应用程序的能力。在 CLR 中,一个托管应用程序称作一个应用程序域。

默认情况下,一个托管 EXE 仅执行在它自己单独拥有的地址空间中(该地址空间中仅含有一个因公用程序域)。到那时,CLR 的宿主进程可以决定在一个操作系统进程中运行多个应用程序域。

1.5 .NET 框架类库

.NET 框架包括 .NET 框架类库(Framework Class Library,简称 FCL)程序集
.NET 应用程序:

  • XML Web 服务: XML Web Services。涉及的内容,HTTP,SOAP,XML,用来
  • Web 窗体:Web Forms。一种基于 HTML的应用程序(Web 站点),涉及的内容,ASP.Net。
  • Windows 窗体:Windows Forms。
  • Windows 控制台应用程序。
  • Windows 服务:由 Windows 服务控制管理器(Service Control Manager,简称 SCM)控制的服务程序。
  • 组件库。
命名空间描述
System其中的类型是为所有应用程序使用的一些基本类型
System.Collection其中的类型用于管理对象集合。包括常用的集合类型,例如堆栈、队列、散列表等
System.Diagnostics其中的类型用于帮助诊断和调试应用程序
System.Drawing其中的类型用于操作二维图形。它们典型地用于 Windows 窗体应用程序,以及创建 Web 窗体页面中显示的图像
System.EnterpriseServices其中的类型用于管理事务、队列组件、对象池、JIT 激活(JIT特指 COM+ 组件服务,即 .NET 内的企业服务中对象的即时激活技术)、安全以及其他一些提高服务器程序中托管代码效能的特性
System.Globalization其中的类型用于多国语言支持(National Language Support,简称 NLS),例如字符串比较、格式化以及日历功能
System.IO其中的类型用于操作 I/O 流、遍历目录和文件
System.Management其中的类型通过 Windows 管理设备(Windows Management Instrumentation,简称 WMI)来管理企业中的计算机
System.Net其中的类型用于网路通信
System.Reflection其中的类型用于查看元数据以及延迟绑定类型和它们的成员
System.Resources其中的类型用于操作外部数据资源
System.Runtime.InteropServices其中的类型允许托管代码访问非托管操作系统平台中的一些功能,如 COM 组件和 Win32 DLL 内的函数
System.Remoting其中的类型用于从远程机器上访问类型
System.Serialization其中的类型用于持久化(persist)对象实例,以及一个流(stream)中重新产生对象实例
System.Security其中的类型用于保护数据和资源
System.Text其中的类型用于以不同的编码方式(如 ASCLL 或者 Unicode)来操作文本
System.Threading其中的类型用于异步操作,以及同步访问资源
System.Xml其中的类型用于处理 XML 模式(schema)和数据
System.Web.Services其中的类型用于创建 XML Web 服务
System.Web.UI其中的类型用于创建 Web 窗体
System.Windows.Forms其中的类型用于创建 Windows GUI 应用程序
System.SeviceProcess其中的类型用于创建由 SCM 控制的 Windows 服务

1.6 通用类型系统

通用类型系统(Common Type System,简称 CTS)来描述类型的定义和行为,包括:字段、方法、属性、事件

CTS还定义了类型可见性和访问类型成员的一些规则,访问控制如下:

  • Private:方法(此处本书只提到方法,但其他成员如字段也是此规则的吧?)只能被同一类型中的其他方法调用。
  • Family:方法可以被派生类型中的代码调用。而不管它们是否位于同一个程序集。(即 protected)
  • Family 与 assembly:方法只可以被位于同一个程序集中的派生类型中的代码调用。IL 汇编语言有这种访问控制,但C# 和其他一些编程语言不支持
  • Assembly:方法可以被同一个程序集中的任何代码调用。(即 internal)
  • Family 或 assembly:方法可以被任何程序集中的派生类型的代码调用,也可以被同一程序集中的任何类型调用。 C# 将之称为 protected internal。
  • Public:方法可以被任何程序集中的任何代码调用。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。