.NET 术语
程序员文章站
2022-07-13 21:42:43
...
.NET 术语
1. CIL——公共中间语言
测试CIL:
c#:
namespace _5
{
class Program
{
static void Main(string[] args)
{
string text = "hello, rod chen!";
Console.WriteLine(text);
}
}
}
vb:Module Module1
Sub Main()
Dim text As String = "hello, rod chen !"
Console.WriteLine(text)
End Sub
End Module
然后编译程序,使用IL DASM(IL Disassembler,IL反汇编程序查看程序集的信息从图中可以看出编译出来的程序集基本上是相同的,红色框内的内容vb程序中多了一个nop中间代码,不过由于No Operation 没有任何操作,我们也不用管它。
可以得到一个初步的推断:不管是VB.NET还是是C#,编译之后的程序集都能够用IL DASM打开,因此它们生成的程序集的格式都是相同的。
剖析术语:
公共:因为不论是C#语言也好,VB.NET语言也好,C++/CLI语言也好,甚至是重新开发的一套以自己的名字缩写命名的语言,只要它期望运行的目标平台是.NET,在经过相应的编译器编译之后,所生成的程序集就是由CIL语言代码描述的。
中间:这个词也是大有深意,为什么不叫公共机器语言(Common Machine Language),或者公共本地语言(Common Native Language)?因为这种语言只是比我们使用的高级语言,比如C#低级一点,并不是CPU可以直接执行的本地机器语言。这种语言还需要.NET运行时(.Net runtime)环境的支持,在执行之前,进行一个被称为Just-in-time(即时)的二次编译过程,才能转变成计算机可以识别的指令。关于.NET运行时,以及详细过程后面再介绍,现在只要知道,这个文件所包含的CIL代码并非机器可以直接执行的指令代码。
语言: CIL不过是一种程序语言,只不过相对于C#来说,它是一种更低级语言。从截图中,已经可以看到,CIL是一种基于堆栈的语言,同时,它提供了class、interface、继承、多态等诸多面向对象的语言特性,因此它又是完全面向对象的语言。如果愿意,甚至可以直接编写CIL代码,并且使用CIL的编译工具IL ASM(IL Assembler,IL汇编程序)来对它进行编译。只不过,和大多数低级语言一样,这种方式会使开发效率会变得很低。这里注意区别一下IL ASM和IL
DASM,它们的拼写是不同的。
好了,已经知道了CIL的存在,从现在开始,最好在头脑里建立起两个模型或两种视角:一种是基于C#或其他高级语言的源程序的视角,一种是基于CIL中间语言的程序集视角。
初始创建项目的时候,会默认在项目中引入一些dll,如下图所示:
2. BCL和FCL
(1) BCL
BCL是一个公共编程框架,称为基类库,所有语言的开发者都能利用它。是CLI(Common Language Infrastructure,公共语言基础结构)的规范之一,主要包括:字符串操作、 高级的数学运算、 文件访问、 加密和更多的功能的类。初始创建项目的时候,会默认在项目中引入一些dll,如下图所示:
如果我们将引入的dll全部删除的话,对于下面的代码还是可以运行成功。
class Program
{
static void Main(string[] args)
{
Test test = new Test() { Name = "rod chen" };
System.Int32 a = 0;
Console.WriteLine(test);
}
}
public class Test
{
public string Name;
}
代码中使用System.Int32以及Console.WriteLine通过F12,看到他们的程序集地址都是#region Assembly mscorlib.dll, v4.0.0.0
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\mscorlib.dll
#endregion
不管我们是否引用mscorlib.dll程序集,它总是会自动引用进来。这个程序集中所包含的类库,即是本节标题中的BCL(Base Class Library,基类库)。从名字就可以看出来,这个类库包含的都是些最基本的类型,其本身已经与CIL语言融为一提了,为CIL语言提供基础的编程支持,以至于该类库已经成为了CLI标准的一部分(后面会介绍,因此也可以说BCL中的类型就是CIL语言的类型,所有面向CIL的语言都能够使用它们。查看mscorlib.dll程序集中都包含了哪些命名空间和类型
string userName = "rod chen";
当我们F12,转到string定义的地方#region 程序集 mscorlib.dll, v4.0.0.0
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\mscorlib.dll
#endregion
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Runtime;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
namespace System
{
// 摘要:
// 表示文本,即一系列 Unicode 字符。
[Serializable]
[ComVisible(true)]
public sealed class String : IComparable, ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string>
{
注意最上方的程序集地址,再次看到了mscorlib.dll,并且String类型与Console类型一样,同位于System命名空间下。由此可见,C#的关键字string,不过是BCLSystem.String类型的一个别名而已。类似地,VB.NET中的String关键字也是BCL中的System.String类型的别名。下面列举C#基元类型在FCL中对应的类型:
.NET同时也对语言开发者提供支持.如你需要设计一款语言,那么在开发编译器时将语言的关键字映射为CIL中的类型就可以了,也就是说,对自己语言中的一些特殊符号(关键字)进行映射处理,就好像C#中的关键字int和string一样。
这就是基元类型(Primitive Type)。由编译器直接支持,将语言本身的关键字类型转换为CIL类型的,就叫做基元类型。
这种映射关系相当于: using string = System.String;可以看到BCL的作用是支持源程序的。
(2) FCL
FCL提供了大粒度的编程框架,它是针对不同应用设计的框架 ,FCL大部分实现都引用了BCL,例如我们常说的开发框架:ASP.NET、MVC、WCF和WPF等等,提供了针对不同层面的编程框架 。从功能上来看,可以将FCL框架类库划分成以下几层。
最内一层,由BCL的大部分组成,主要作用是对.NET框架、.NET运行时及CIL语言本身进行支持,例如基元类型、集合类型、线程处理、应用程序域、运行时、安全性、互操作等。
中间一层,包含了对操作系统功能的封装,例如文件系统、网络连接、图形图像、XML操作等。
最外一层,包含各种类型的应用程序,例如Windows Forms、Asp.NET、WPF、WCF、WF等。
3. CTS——公共类型系统
我们可以在c#语言中使用class, 基元类型, 枚举类型, 结构类型。
class Program
{
static void Main(string[] args)
{
Test test = new Test() { Name = "rod chen" };
System.Int32 a = 0;
string userName = "rod chen";
Console.WriteLine(test);
}
}
public class Test
{
public string Name;
}
就像上面的例子,为什么我们可以这样写代码,可以直接使用class,System.Int32? 我们通过1中讲解的CIL先编译成中间语言ILmethod private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 36 (0x24)
.maxstack 2
.locals init ([0] class _5.Test test,
[1] int32 a,
[2] string userName,
[3] class _5.Test '<>g__initLocal0')
IL_0000: nop
IL_0001: newobj instance void _5.Test::.ctor()
IL_0006: stloc.3
IL_0007: ldloc.3
IL_0008: ldstr "rod chen"
IL_000d: stfld string _5.Test::Name
IL_0012: ldloc.3
IL_0013: stloc.0
IL_0014: ldc.i4.0
IL_0015: stloc.1
IL_0016: ldstr "rod chen"
IL_001b: stloc.2
IL_001c: ldloc.0
IL_001d: call void [mscorlib]System.Console::WriteLine(object)
IL_0022: nop
IL_0023: ret
} // end of method Program::Main
通过第1段的描述,我们知道,这里的中间语言接下来会被LCR运行,CLR居于底层,肯定是有自己的小脾气的,我不能随便什么代码都可以在我的地盘上运行。然后自己会去检测这个中间代码都是些什么东西,看了一下,class, int32, string这些东西都是我允许的,可以通过。那么这个规则就是CTS.CTS定义很多规则:规范了类型中可以包含字段(filed)、属性(property)、方法(method)、事件(event)等。
除了定义各种类型外,CTS还规定了各种访问性,比如Private、Public、Family(C#中为Protected)、Assembly(C#中为internal)、Family and assembly(C#中没有提供实现)、Family or assembly(C#中为protected internal)。
CTS还定义了一些约束,例如,所有类型都隐式地继承自System.Object类型,所有类型都只能继承自一个基类。从CTS的名称和公共类型系统可以看出,不仅C#语言要满足这些约束,所有面向.NET的语言都需要满足这些约束。众所周知,传统C++是可以继承自多个基类的。为了让熟悉C++语言的开发者也能在.NET框架上开发应用程序,微软推出了面向.NET的C++/CLI语言(也叫托管C++),它就是符合CTS的C++改版语言,为了满足CTS规范,它被限制为了只能继承自一个基类。
关于上面内容有两点需要特别说明:
1)C#并没有提供Family and assembly的实现,C#中也没有全局方法(Global Method)。换言之,C#只实现了CTS 的一部分功能。,也就是说,CTS规范了语言能够实现的所有能力,但是符合CTS规范的具体语言实现不一定要实现CTS规范所定义的全部功能。
2)C++/CLI又被约束为只能继承自一个基类,换言之,C++中的部分功能被删除了。,就是说,任何语言要符合CTS,其中与CTS不兼容的部分功能都要被舍弃。
4. CLS——公共语言规范
其实上面的解释猛一看不是很理解,接下来就是有一个概念的东西。其实在.net平台上可以有很多的开发原因,c#,vb, c++,j#等等。如果需要多个开发语言集成开发,如果每个语言提供的借口都是按照自己的想法来的,然后在其他的语言中没有对应的关键字接收,这样就会出问题了。
那么CLS的规范有哪些呢?
.NET为我们提供了一个特性CLSCompliant,便于在编译时检查程序集是否符合CLS。看个例子
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
[assembly: CLSCompliant(true)]
namespace _5
{
class Program
{
static void Main(string[] args)
{
test test = new test();
test.name = "rod chen";
test.Name();
}
}
public class test
{
public string name;
public string Name()
{
return "";
}
public uint GetValue()
{
return 0;
}
public void SetValue(sbyte a) { }
public string _MyProperty { get; set; }
public UInt32 _MyProperty1 { get; set; }
}
}
编译之后看一下错误列表中会有一些警告:可以看到里面有7个不符合CLS的规范
public UInt32 _MyProperty1 { get; set; } 有两个警告,
警告 6 标识符“_5.test._MyProperty1”不符合 CLS E:\Git_Source_project\Study-for-asp.net\5\5\Program.cs 39 23 5
警告 7 “_5.test._MyProperty1”的类型不符合 CLS E:\Git_Source_project\Study-for-asp.net\5\5\Program.cs 39 23 5
命名类型不符合CLS规则,也是上面文字描述说的System.UInt32不符合CLS规则。
public UInt32 _MyProperty1 { get; set; } 有两个警告,
警告 6 标识符“_5.test._MyProperty1”不符合 CLS E:\Git_Source_project\Study-for-asp.net\5\5\Program.cs 39 23 5
警告 7 “_5.test._MyProperty1”的类型不符合 CLS E:\Git_Source_project\Study-for-asp.net\5\5\Program.cs 39 23 5
命名类型不符合CLS规则,也是上面文字描述说的System.UInt32不符合CLS规则。
编译器给出的只是警告信息,而非错误信息,因此可以无视编译器的警告,不过这个程序集只能由其他C#语言编写的程序集所使用。
CTS规则了语言所有的特性,CLS视为了语言在协作中更好的运行,毕竟没有规矩不成方圆,它们之间的关系就像相面的图(网上搜索的)
CTS规则了语言所有的特性,CLS视为了语言在协作中更好的运行,毕竟没有规矩不成方圆,它们之间的关系就像相面的图(网上搜索的)
5. CLR——公共语言运行时
首先要说明的是,.NET平台与C#不是一回事 它是C#,VB.NET等程序运行的平台。CLR是公共语言运行时,是 .NET Framework的重要组成部分。它提供了内存管理、线程管理和异常处理等服务,而且还负责对代码实施严格的类型安全检查,保证了代码的正确性。事实上,类型安全(Type Checker)、垃圾回收(Garbage Collector)、异常处理(Exception Manager)、向下兼容(COM Marshaler)等很多C#中的特性都是由CLR来提供的。程序集包含了CIL语言代码,而CIL语言代码是无法直接运行的,需要经过.NET运行时进行即时编译才能转换为计算机可以直接执行的机器指令。那么这个过程是如何进行的呢?
先看一下程序集的组成
PE/COFE头:Microsoft Windows Portable Executable/Common Object File Format 微软Windows可移植可执行/通用对象文件格式。Windows操作系统能够加载并运行.dll和.exe是因为它能够理解PE/COFF文件的格式。显然,所有在Windows操作系统上运行的程序都需要符合这个格式,当然也包括.NET程序集在内。在这一级,程序的控制权还属于操作系统,PE/COFF头包含了供操作系统查看和利用的信息。
CLR头:程序集中包含的CIL语言代码并不是计算机可以直接执行的,还需要进行即时编译,那么在对CIL语言代码进行编译前,需要先将编译的环境运行起来,因此PE/COFF头之后的就是CLR头了。CLR头最重要的作用之一就是告诉操作系统这个PE/COFF文件是一个.NET程序集,区别于其他类型的可执行程序。CLR头中包含要求的CLR版本,一些标志flag,托管代码模块入口方法(Main)的MethodDef元素据token以及模块的元数据,资源,强名称,一些标志以及其他不太重要的数据项的位置/大小。
清单:程序集包含一个清单(manifest),这个清单相当于一个目录,描述了程序集本身的信息,例如程序集标识(名称、版本、文化)、程序集包含的资源(Resources)、组成程序集的文件等。
元素据: 每个托管代码都包含元数据表。主要有两种表:一种描述源代码中定义的类型和成员,另一种描述代码引用的类型和成员。
CIL代码:编译器编译源代码时生成的代码,在运行时,CLR将CIL代码编译成CPU指令。
资源文件:例如.jpg图片。
运行程序集
虽然从Windows Server 2003开始,.NET框架已经预装在操作系统中,但是它还没有集成为操作系统的一部分。当操作系统尝试打开一个托管程序集(.exe)时,它首先会检查PE头,根据PE头来创建合适的进程。接下来会进一步检查是否存在CLR头,如果存在,就会立即载入MsCorEE.dll。这个库文件是.NET框架的核心组件之一,注意它也不是一个程序集。
MsCorEE.dll是一个很细的软件层。加载了MsCorEE.dll之后,会调用其中的_CorExeMain()函数,该函数会加载合适版本的CLR。在CLR运行之后,程序的执行权就交给了CLR。CLR会找到程序的入口点,通常是Main()方法,然后执行它。这里又包含了以下过程:
1. 加载类型。在执行Main()方法之前,首先要找到拥有Main()方法的类型并且加载这个类型。CLR中一个名为Class loader(类加载程序)的组件负责这项工作。它会从GAC、配置文件、程序集元数据中寻找这个类型,然后将它的类型信息加载到内存中的数据结构中。在Class loader找到并加载完这个类型之后,它的类型信息会被缓存起来,这样就无需再次进行相同的过程。在加载这个类以后,还会为它的每个方法插入一个存根(stub)。
2. 验证。在CLR中,还存在一个验证程序(verifier),该验证程序的工作是在运行时确保代码是类型安全的。它主要校验两个方面,一个是元数据是正确的,一个是CIL代码必须是类型安全的,类型的签名必须正确。
3. 即时编译。这一步就是将托管的CIL代码编译为可以执行的机器代码的过程,由CLR的即时编译器(JIT Complier)完成。即时编译只有在方法的第一次调用时发生。回想一下,类型加载程序会为每个方法插入一个存根。在调用方法时,CLR会检查方法的存根,如果存根为空,则执行JIT编译过程,并将该方法被编译后的本地机器代码地址写入到方法存根中。当第二次对同一方法进行调用时,会再次检查这个存根,如果发现其保存了本地机器代码的地址,则直接跳转到本地机器代码进行执行,无需再次进行JIT编译。
看一下CLR的结构组成
最后在网上看到了一幅图,总结这些术语:
我自己也简单的画了一个用于自己理解:
参考:
上一篇: 搭建无人值守安装服务器
下一篇: Numpy与Pandas基础