您现在的位置是:首页 >技术杂谈 >NET框架程序设计-第1章.NET框架开发平台体系架构网站首页技术杂谈
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:方法可以被任何程序集中的任何代码调用。