CLRviaC#中各种基础概念的理解
汉字都认识,但各种术语组成段落后就懵了,根本原因还是很多术语的概念不了解。在这⾥把总是影响理解的概念和术语总结罗列出来,不断更新、添加、修改。以便读书时更加顺畅。
using System;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.Write("Hello World!");
}
}
}
税费怎么计算源代码
就是我们在各种⽂本编辑器⾥写的那些代码,⼩⽩都能认识的if else Console.Write(),源代码对⼈类友好,对机器不算友好,可以通过编译器编译成机器码让CPU去运⾏。原代码的另⼀个好处是同⼀份源代码,可以编译成不同的机器码,在不同的硬件上执⾏相同的操作。
PE 可移植的执⾏体 Portable Executable
通俗理解
PE翻译成中⽂是:可移植的执⾏体。执⾏体就是说这个⽂件可以被运⾏,可以在物理CPU上跑起来。可移植,是说可以在不同的CPU上运⾏(64位PE的不能在32位上运⾏),微软的windows底层帮我们屏蔽了不同CPU硬件的差异(不同的CPU⽀持的指令集都是不同的,如果程序员还要挨个去适配不同版本代、不同型号的CPU的话,想想就够了)。只要在微软的windows系统上,PE⽂件就可以跑,不⽤管装的什么CPU。(Linux系统上对应的是ELF(Executable Linkable Format))。
那PE到底是什么?
简单说PE是⼀种⽂件格式,是COFF(Common file format)格式的变种。啥是⽂件格式?格式就是⼀种规范,⼀种⼈为的规定。就像早期的邮政信封⼀样,左上⽅填收件⼈邮编,右上⽅粘邮票,中间上⾯写收件⼈地址、姓名,中间下⾯写寄件⼈地址、姓名。右下⾓写寄件⼈邮编。这就是⼀种规定的格式。只要寄信的⼈遵循这个标准,信息都填对,就可以寄出去,⽽不⽤操⼼这封信是坐的飞机还是⽜车。如果不遵循这个格式呢?你在信封上画⼀幅到达收件⼈位置的路线地图拿到邮局,⼯作⼈员会⼀个⽩眼翻到后脑勺:滚!(windows会说:此程序⽆法在XXX运⾏)
我们程序⾥写的函数、类、变量等等数据,会被编译成遵循PE格式规范的⽂件,我也半懂不懂,好像是各种C语⾔代码:指针、结构体那些东西组成的。
常见的PE⽂件后缀
常见的EXE、DLL、OCX、SYS、COM都是PE⽂件,PE⽂件是微软Windows操作系统上的程序⽂件(可能是间接被执⾏,如DLL)
PE⽂件是指32位可执⾏⽂件,也称为PE32。64位的可执⾏⽂件称为PE+或PE32+,是PE(PE32)的⼀种扩展形式(请注意不是PE64)。
更多详细信息参考
PE⽂件解析基础篇
托管代码 IL(中间语⾔)(Intermediate Language)
参考⽂章1
参考⽂章2
解释1: IL是.NET框架中中间语⾔(Intermediate Language)的缩写。使⽤.NET框架提供的编译器可以直接将源程序编译为.exe或.dll ⽂件,但此时编译出来的程序代码并不是CPU能直接执⾏的机器代码,⽽是⼀种中间语⾔IL(Intermediate Language)的代码解释2: MS中间语⾔在 .Net Framework中有⾮常重要的作⽤,⾯向.Net的所有语⾔逻辑上都必须⽀持IL。
解释3:《C#⼊门经典(第3版)》中的描述:在编译使⽤ .NET Framework 库的代码时,不是⽴即创建操作系统特定的本机代码,⽽是把代码编译为Microsoft中间语⾔(Microsoft intermediate language, MSIL)代码,这些代码不专⽤于任何⼀种操作系统,也不专⽤于C#。其他.NET语⾔,如Visual Basic .NET 也可以在第⼀阶段编译为这种语⾔,当使⽤VS开发C#应⽤程序时,编译过程就由VS 完成。
解释4:中间语⾔是⼀组独⽴于CPU的指令集,它可以被即时编译器Jitter翻译成⽬标平台的本地代码。
中间语⾔代码使得所有Microsoft.NET平台的⾼级语⾔C#,VB .NET,VC.NET等得以平台独⽴,以及语⾔之间实现互操作。
本机代码(Native Code)
被编译为特定于处理器的机器码的代码。
通俗说:本机特指当前运⾏程序的这台机,这台机装的是什么CPU,这个CPU⽀持什么指令集,代码就是机器码,是不是01⼆进制不清楚。本机代码,就是⼀堆可以直接在当前CPU上直接运⾏的代码。这坨代码放在别的CPU上可能会⽔⼟不服。
.NET应⽤程序的执⾏过程
原⽂地址:
graph LR
原代码--VS-->IL代码
茂名市中考成绩查询IL代码--JIT-->本机代码
本机代码--直接执⾏-->CPU
我们对"HelloWorld.cs"⽂件⽤命令编译后发⽣了什么。是的,我们得到了⽂件。但那仅仅是事情的表象,实际上那个根本不是⼀个可执⾏⽂件!那它是什么?⼜为什么能够执⾏?
好的,下⾯正是回答这些问题的地⽅。⾸先,编译输出的是⼀个由中间语⾔(IL),元数据(Metadata)和⼀个额外的被编译器添加的⽬标平台的标准可执⾏⽂件头(⽐如Win32平台就是加了⼀个标准Win32可执⾏⽂件头)组成的PE(portable executable,可移植执⾏体)⽂件,⽽不是传统的⼆进制可执⾏⽂件--虽然他们有着相同的扩展名。中间语⾔是⼀组独⽴于CPU的指令集,它可以被即时编译器Jitter翻译成⽬标平台的本地代码。中间语⾔代码使得所有Microsoft.NET平台的⾼级语⾔C#,VB.NET,VC.NET等得以平台独⽴,以及语⾔之间实现互操作。元数据是⼀个内嵌于PE⽂件的表的集合。元数据描述了代码中的数据类型等⼀些通⽤语⾔运⾏时(Common Language Runtime)需要在代码执⾏时知道的信息。元数据使得.NET应⽤程序代码具备⾃描述特性,提供了类型安全保障,这在以前需要额外的类型库或接⼝定义语⾔(Interface Definition Language,简称IDL)。
这样的解释可能还是有点让⼈困惑,那么我们来实际的解剖⼀下这个PE⽂件。我们采⽤的⼯具是 .NET SDK Beta2⾃带的,它可以帮助我们提取PE⽂件中的有关数据。我们键⼊命令"ildas
m /output:HelloWorld.",⼀般可以得到两个输出⽂件:helloworld.il和s。其中后者是提取的资源⽂件,我们暂且不管,我们来看helloworld.il⽂件。我们⽤"记事本"程序打开可以看到元数据和中间语⾔(IL)代码,由于篇幅关系,我们只将其中的中间语⾔代码提取出来列于下⾯,有关元数据的表项我们暂且不谈:
class private auto ansi beforefieldinit HelloWorld extends [mscorlib]System.Object
卷心菜投手{
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 11 (0xb)
.maxstack 8
IL_0000: ldstr "Hello World !"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method HelloWorld::Main
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method HelloWorld::.ctor
} // end of class HelloWorld
我们粗略的感受是它很类似于早先的汇编语⾔,但它具有了对象定义和操作的功能。我们可以看到它定义并实现了⼀个继承⾃突然变冷的唯美的句子
System.Object 的HelloWorld类及两个函数:Main()和.ctor()。其中.ctor()是HelloWorld类的构造函数,可在"HelloWorld.cs"源代码中我们并没有定义构造函数呀--是的,我们没有定义构造函数,但C#的编译器为我们添加了它。你还可以看到C#编译器也强制HelloWorld类继承System.Object类,虽然这个我们也没有指定。关于这些⾼级话题我们将在以后的讲座中予以剖析。
那么PE⽂件是怎么执⾏的呢?下⾯是⼀个典型的C#/.NET应⽤程序的执⾏过程:
1. ⽤户执⾏编译器输出的应⽤程序(PE⽂件),操作系统载⼊PE⽂件,以及其他的DLL(.NET动态连接库)。
2. 操作系统装载器根据前⾯PE⽂件中的可执⾏⽂件头跳转到程序的⼊⼝点。显然,操作系统并不能执⾏中间语⾔,该⼊⼝点也被设计为
跳转到mscoree.dll(.NET平台的核⼼⽀持DLL)的CorExeMain()函数⼊⼝。
3. CorExeMain()函数开始执⾏PE⽂件中的中间语⾔代码。这⾥的执⾏的意思是通⽤语⾔运⾏时按照调⽤的对象⽅法为单位,⽤即时编译
器将中间语⾔编译成本地机⼆进制代码,执⾏并根据需要存于机器缓存。
4. 程序的执⾏过程中,垃圾收集器负责内存的分配,释放等管理功能。
5. 程序执⾏完毕,操作系统卸载应⽤程序。
清楚的知晓编译输出的PE⽂件的执⾏过程是深度掌握C#语⾔编程的关键,这种过程的本⾝就诠释着C#语⾔的⾼级内核机制以及其背后Microsoft.NET平台种种诡秘的性质。
JIT(Just In Time)
参考1:
参考2:
参考3:
1. 动态编译(dynamic compilation)指的是“在运⾏时进⾏编译”;与之相对的是事前编译(ahead-of-t
ime compilation,简称AOT),也
叫静态编译(static compilation)。
2. JIT编译(just-in-time compilation)狭义来说是当某段代码即将第⼀次被执⾏时进⾏编译,因⽽叫“即时编译”。JIT编译是动态编译的⼀
种特例。JIT编译⼀词后来被泛化,时常与动态编译等价;但要注意⼴义与狭义的JIT编译所指的区别。
3. ⾃适应动态编译(adaptive dynamic compilation)也是⼀种动态编译,但它通常执⾏的时机⽐JIT编译迟,先让程序“以某种式”先运⾏
起来,收集⼀些信息之后再做动态编译。这样的编译可以更加优化。
JIT:即时编译(Just In-Time compile),这是.NET运⾏可执⾏程序的基本⽅式,也就是在需要运⾏的时候,才将对应的IL代码编译为本机指令。传⼊JIT的是IL代码,输出的是本机代码,所以部分加密软件通过挂钩JIT来进⾏IL加密,同时⼜保证程序正常运⾏。同解释执⾏的代码相⽐,JIT的执⾏效率要⾼很多。
托管模块(Managed Module)
托管模块是⼀个需要CLR才能执⾏的标准WindowsPE(Portable executable,简称PE)⽂件。
元数据(MetaData)
参考1:
参考2:
参考3:
⾸先要知道“元”是什么。元(meta),⼀般被我们翻译成“关于……的……”。元数据:关于数据的信息/数据,元标签:关于标签的信息/数据。元是⽤来描述⼀个东西各个⽅⾯的信息表。⽐如我的个⼈简历,就是描述我的元数据。招聘⽹站上有⼗万个应聘者的简历,他们都要求应聘者提供姓名、性别、年龄等信息。这样的信息表格,就是这些求职者的元数据。这些元数据(简历)的内容,可以描述⼀个应聘者的画像。
那.Net的元数据,是在描述什么?是关于什么的信息?
.Net的元数据,是描述当前程序集的数据。我们编译⼀个的程序,在编译成PE⽂件的过程中,编译器除了将代码转换为Microsoft 中间语⾔ (MSIL)外,还⽣成了⼀些数据对当前这个hellow
orld程序进⾏描述,在模块或程序集中定义和引⽤的每个类型和成员都将在元数据中进⾏说明。⽐如会记录helloworld这个程序集定义了哪些类型,有哪些⽅法,添加了哪些引⽤等等等等。当执⾏代码时,运⾏时将元数据加载到内存中,并引⽤它来发现有关代码的类、成员、继承等信息。
所以(敲⿊板),元数据就是让程序有了⾃描述的能⼒,我们写的程序在运⾏的时候,能够让系统、其它程序能够通过我们的元数据(本程序的"简历")知道:我是谁?我在哪?我在⼲什么?
vs这个宇宙最强编译器,它的智能提⽰,就利⽤到了元数据。当我们输⼊⼀个类再加⼀个点,就会⾃动提⽰有哪些⽅法和属性可以使⽤。这些信息就是从元数据中翻出来给我们的。
【元数据】以⾮特定语⾔的⽅式【描述】在【代码中】定义的每⼀个【类型和成员】。元数据存储以下信息:
⼜长⼜拗⼝的解释,必须要⼿⼯断句⼀下...
元数据描述代码中的类型和成员
程序集的说明。
标识(名称、版本、区域性、公钥)。
导出的类型。
该程序集所依赖的其他程序集。
运⾏所需的安全权限。
类型的说明。
名称、可见性、基类和实现的接⼝。
成员(⽅法、字段、属性、事件、嵌套的类型)。
特性。
修饰类型和成员的其他说明性元素。
程序集 Assembly
参考1:
在写完代码之后进⾏⽣成(build)时,CLR 将 .NET 应⽤程序打包为由模块(module)组成的程序集(assembly)。
⼀个程序集由⼀或多个托管模块组成,程序代码被编译为 IL 代码,存在于托管模块之中。
程序集是⼀个可以寄宿于 CLR 中的、拥有版本号的、⾃解释、可配置的⼆进制⽂件,程序集的扩展名为 exe 或 dll。
程序集中的代码可以被其他程序集中的 C# 代码调⽤,例如⼏乎所有的 C# 代码都会⽤到 mscorlib.dll 这个程序集中的对象。
程序集是⾃解释的,因为它记录了它需要访问的其他程序集(在清单中)。
另外,元数据描述了程序集内部使⽤的类型的所有信息,包括类型的成员和构造函数等。
程序集可以私有或共享的⽅式配置,如果以共享⽅式进⾏配置,则同⼀台机器的所有应⽤程序都可以使⽤它。
程序集也可以⼿动进⾏⽣成,这需要选择对应语⾔的编译器。
C# 的编译器是。可以通过 /t 参数指定编译⽬标,最常见的⼏个为:
/t:library:⽬标为⼀个 dll ⽂件(需要指定托管模块)。
/
t:exe:⽬标为⼀个可执⾏ exe ⽂件,必须指定⼊⼝点。
/t:module:⽬标为⼀个托管模块。
其中,前两个⽬标的结果⽂件都是程序集,⽽最后⼀个⽬标的结果⽂件是托管模块。
下图简单显⽰了各种代码和编译器之间的关系。
CLR(Common Language Runtime)公共语⾔运⾏时
参考1:
CLR:通⽤语⾔运⾏时(Common Language Runtime)的简称,CLR是.NET框架的核⼼内容之⼀,可以把它看为⼀套标准资源,可以呗任何.NET程序使⽤。它包括:⾯向对象的编程模型、安全模型、类型系统(CTS)、所有.NET基类、程序执⾏及代码管理等。
我们可以这样理解,CLR是托管程序运⾏的环境,就像Windows是普通的PE程序的运⾏环境⼀样。
在Windows中,整个CLR系统的实现基本其实就是⼏个关键的DLL,⽐如mscorwks.dll、mscorjit.dll,
企业宣传口号它们共同的特点就是前缀均为mscor。
在win32中,可执⾏⽂件在开始运⾏时,有操作系统载⼊到内存中,然后运⾏⽂件中的.text代码,结束时有操作系统负责卸载。⽽在.NET 下,这个过程却不⼤⼀样。⽤PE结构查看⼯具(这⾥使⽤PEiD)载⼊⼀个实例⽂件,观察导⼊表,可以发现整个表只引⼊了⼀个mscoree.dll中的⼀个⽅法:_CorExeMain。⽽这个⽅法,真是该可执⾏⽂件在win32意义上的⼊⼝点。
六级分数分配明细和win32程序⼀样,.NET可执⾏程序在运⾏初始,⾸先有Windows将PE载⼊内存,然后跳⾄_CorExeMain中执⾏。分⽔岭就在这,当代码跳⾄_CorExeMain中之后,程序运⾏进⼊了.NET的初始化阶段,经过⼀番准备⼯作,.NET框架便正式接管程序的运⾏了。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论