.NET Framework(一)

.NET Framework:即Microsoft .NET Framework,它是用于Windows的新托管代码编程模型。它强大功能与新技术结合起来,用于构建具有视觉上引人注目的用户体验的应用程序,实现跨技术边界的无缝通信,并且能支持各种业务流程。

  结合微软官方的说明,我们也许可以那么定义它:.NET Framework是一个普及的开发平台,给程序设计师和使用者提供微软的Web Services引擎,来建立WebServices(因特网服务)。 .NET Framework 平台包括 C# 和 Visual Basic等编程语言、公共语言运行时和广泛的类库。 

  下面的插图显示公共语言运行时和类库与应用程序之间以及与整个系统之间的关系。  该插图还显示托管代码如何在更大的结构内运行。

.NET Framework 环境

 

 公共语言运行时:CLR(Common Language Runtime)是一种可以由多种编程语言使用的运行时环境,它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间的必要的分离。CLR的核心功能包括:内存管理、程序集加载、安全性、异常处理和线程同步,可由面向CLR的所有语言使用。 

公共语言运行时所需要的步骤:

1、选择编译器

      为获得公共语言运行库提供的优点,必须使用一个或多个针对运行时的语言编译器,如 Visual Basic、C#、Visual C++、JScript 或许多第三方编译器(如 Eiffel、Perl 或 COBOL 编译器)中的某一个。

   由于运行库是一个多语言执行环境,因此它支持各种数据类型和语言功能。您所用的语言编译器确定可用的运行库功能,而您使用这些功能设计代码。编译器(而不 是运行库)建立代码必须使用的语法。如果您的组件必须完全能够被用其他语言编写的组件使用,您的组件的导出类型必须只公开公共语言规范 (CLS) 中包括的语言功能。

2、将代码编译为MSIL

  2.1将源代码编译成托管代码

    用一个对应的编译器编译源代码之后,结果都是一个托管模块。

    

  托管模块是一个标准的32/64位Microsoft Windows 可移植执行体(PE32/PE32+)文件,它们都需要CLR才能执行。托管的程序集利用Windows的数据执行保护和地 址空间布局随机化增强整个系统的安全性。

以下是托管模块的各个组成部分:

   

  每个面向CLR的编译器生成的都是IL(中间语言)代码。IL有时称为托管代码,因为CLR要管理它的执行。除了生成IL,面向CLR的每个编译器还要在每个托管模块中生成完整的元数据。元数据总是与包含IL代码的文件关联。事实上,元数据总是嵌入和IL代码相同的EXE/DLL文件中,两者密不可分。

    元数据的用途:

•       编译时,元数据消除了对本地C/C++头和库文件的需求,因为在负责实现类型/成员的IL代码文件中,已包含和引用的类型/成员有关的全部信息。编译器可直接从托管代码中读取元数据。

•    Visual Studio 使用元数据帮你写代码。它的"智能感知"技术可以解析元数据。

•    CLR的代码验证过程使用元数据确保代码执行“类型安全”的操作。 

2.2将托管模块合并成程序集

  CLR是不和托管模块一起工作的,CLR是和程序集一起工作的。因为程序集是一个或多个托管模块/资源文件的逻辑性分组。程序集是重用、安全性以及版本控制的最小单位。程序集取决于你对编译器的选择,可以生产单文件程序集,也可以生产多文件程序集。在CLR中,程序集相当于一个“组件”。

     将托管模块合并成程序集图示:

     生成的 程序集会包含一个名为"清单"(maniest)的数据块。清单是有元数据构成的另一种集合。这些表描述了构成程序集的文件。

 

  默认情况下,编译器实际会把生成的托管模型转化成程序集。C#编译器会生成含有清单的一个托管模块。清单指出程序集只有一个文件构成。所以,假如项目只有一个托管模块,没有资源/数据等文件,那么程序集就是托管模块,生成过程不需要额外操作。如果是将一系列文件合并到一个程序集中,就必须掌握更多的工具(比如程序集链接器AL.exe)以及它们的命令行。

    在程序集模块中,还包含与引用的程序集有关的信息(包括他们的版本号)。这些信息使得程序集能够自描述(self-describing)。也就是说,CLR能判断出为了执行程序集中的代码,该程序集的直接依赖对象是什么。所以部署起来十分的方便。

2.3加载公共语言运行时

  你生成的每个程序集可以是EXE,也可以是DLL。最终都是有CLR管理这些程序集中代码的执行。VS2010中,创建新的EXE项目时,默认平台是x86,而不是anycpu。Windows的64位版本提供了一个名为WoW64(Windows on Windows64)的技术,允许允许32位的Windows程序。这是因为这个技术能模拟x86的指令集,但这样会显著影响性能。

编译目标平台对生成的模型的影响以及运行时的影响。

 

   Windows检查好EXE文件头,决定创建32位、64位还是WoW64进程之后,会在进程的地址空间中加载MSCorEE.dll的x86,x64或IA64版本。然后,进行的主线程调用MSCorEE.dll中定义的一个方法。这个方法初始化CLR,加载EXE程序集,然后调用其入口方法(Main)。最后,托管的应用程序将启用并运行。

       如果非托管程序调用LoadLibrary来加载一个托管程序集,Windows会自动并初始化CLR(如果尚未加载的话),以便处理程序集中的代码。

 

3、将MSIL编译为本机代码

3.1、托管程序集同时包含元数据和IL。IL是与CPU无关的机器语言。可将IL视为一种面向对象的机器语言。

3.2.、IL也是能使用汇编语言来写的,MicroSoft专门提供了一个名为ILAsm.exe的IL汇编器和一个名为ILDasm.exe的IL反汇编。 

3.3. 高级语言只公开了CLR的所有功能的一个子集,IL汇编语言允许开发人员访问CLR的所有功能。如果你需要当前使用的语言不支持的CLR功能,可以使用IL语言或者其他CLR语言。 

3.4. 为了执行一个方法,首先必须将它的IL转换成为本地CPU指令,这是CLR的JIT(just-in-time或"即时")编译器的职责。 

3.5. 展示一个方法首次调用发生的事情。

①在Main方法执行之前,CLR会检测出Main的代码引用的所有类型。这会使CLR分配一个内部数据结构,用于管理对所引用的类型的访问。

图1-4中,Main方法引用了一个Console类型(或就叫做Console类),这将让CLR分配一个内部结构。在这个结构中,Console类型定义的每个方法都有一个相对应的记录项。每一个记录项都容纳一个地址(但目前还是没有的,还没到这一步),根据地址即可找到方法的实现。

②初始化CLR分配了一个内部结构,CLR将每个记录项都设置成包含在CLR内部的一个未文档化的函数(就理解成未公开的,只有微软自己清楚的函数)。姑且就将这个函数命名为JITCompiler(MSDN找不到这个函数,为了说明流程,自己取的函数名,因为真正的函数名微软没公开)

③Main方法首次调用WriteLine时,JITCompiler也就被调用了。JIT函数负责将一个方法的IL代码编译成本地CPU指令。由于IL是"即时"编译的,所有通常将这个组件成为JIT编译器或JITter。

④JITCompiler函数被调用时,它知道要调用的是哪个方法,以及具体是什么类定义了该方法。于是乎,JITCompiler会在定义该类型的程序集的元数据中查找被调用的方法的IL。

⑤接着就是验证IL代码,并将IL编译成为本地CPU指令。本地CPU指令被保存到了一个动态分配的内存块中。

⑥然后,JITCompiler在CLR为类型创建的内部数据结构,找到与被调用的方法对应的那一条记录项,修改最初对JITCompiler的引用,让它现在指向内存块(其中包括了刚才编译好的本地CPU指令)的地址。

⑦最后,JITCompiler函数跳转到内存块中的代码,继续执行里面的具体的功能代码,这些代码执行完后,会返回到Main中,并像往常一样继续执行。

 

⑧现在,Main要执行第二个WriteLine方法了。这一次,由于第一次已对WriteLine的代码进行了验证和编译,所以会直接执行内存块中的代码,完全跳过JITCompiler函数。第二个WriteLine方法执行完毕,会再次返回Main。图1-5展示了第二次调用WriteLine时发生的事。

 

3.6. 对于大多数应用程序,因JIT编译造成的性能损失并不显著。大多数引用程序会反复调用相同的方法。看到上面,你对.NET的“第一次”是否有了颠覆性的认识了。

3.7. CLR的JIT编译器会对本地代码进行优化,代码优化后会获得更出色的性能。

3.8. IL是基于栈的。这就意味着它的所有执行都要将操作数压入(push)一个执行栈,并处栈弹出(pop)结果。

3.9. IL提供的最大优势在于应用程序的健壮性和安全性。将IL编译成CPU指令时,CLR会执行一个名为验证(verfication)的过程。这个过程会检查高级IL代码,确定代码所做的一切都是安全的。

3.10. C#编译器默认生成的是安全(safe)代码,这种代码是否安全是可验证的。然而,C#编译器也允许开发人员写不安全(unsafe)代码。

3.11. 不安全代码允许直接操作内存地址,并可操作这些地址处的字节,通常只有在与非托管代码进行互操作,或在提升效率极高的一个算法的性能时,才会这么做。

3.12.  MicroSoft提供一个名为PEverify.exe的好、程序,它检查一个程序集的所有方法,并报告其中含有不安全代码的方法。

 

使用 NGen.exe 的安装时代码生成

1. NGen.exe工具,可以在一个程序安装到用户计算机时,将IL代码编译成为本地代码。由于代码在安装时已经编译好,所以CLR的JIT编译器不需要再运行时编译IL代码了,这有助于提升程序的性能。

2. NGen.exe可以加快程序的启动速度,减少程序的工作集。

3. NGen.exe生成的文件存在以下问题:

    1)没有知识产权保护。在运行时,CLR要求访问程序集的元数据,这就要求同时发布包含IL代码和元数据的程序集。

    2)NGen生成的文件可能失去同步。NGen生成的文件时,会与当前执行环境相适应的,当你改变了先前的执行环境时,NGen生成的文件就不能使用了。

    3)较差的执行时性能。NGen无法像JIT编译器那么对最终执行环境做出许多优化。

4、运行代码

  公共语言运行时提供使托管代码执行能够发生以及可在执行期间使用的各种服务的基础结构。 在运行方法之前,必须先将其编译为特定于处理器的代码。 当首次调用已经为其生成 MSIL 的每个方法,然后运行该方法时,该方法将是 JIT 编译的。 下次运行该方法时,将运行现有的 JIT 编译的本机代码。 这种进行 JIT 编译然后运行代码的过程一直重复到执行完成时为止。

  在执行过程中,托管代码接收若干服务,这些服务涉及垃圾回收,安全性,与非托管代码的互操作性,跨语言调试支持,增强的部署,以及版本控制支持等。

  在 Microsoft Windows XP 和 Windows Vista 中,操作系统加载程序通过检查 COFF 头中的某个位来检查托管模块。 所设置的位表示托管模块。 如果加载程序检测到托管模块,它将加载 mscoree.dll,当加载和卸载托管模块映像时,_CorValidateImage 和 _CorImageUnloading 将通知加载程序。 _CorValidateImage 执行下列操作:

    1. 确保代码是有效的托管代码。
    2. 将映像中的入口点更改为运行时中的入口点。

  在 64 位的 Windows 上,_CorValidateImage 会修改内存中的映像,将其从 PE32 格式转换为 PE32+ 格式。

 

以上内容,多数都为本人在网上整理而来,并非原创,整理只为了让自己对.NET这一平台有有点深入的认识和了解,希望和大家一起学习和进步。虽然知识点比较晦涩难懂,但是细细品味和钻研着实可以有所收获和成长。参考文献如下:

烧点饭博客

.NET Framework 概述(微软官网资料)

百度百科

原文地址:https://www.cnblogs.com/huijiBreathe/p/3675704.html