CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core?(四)
0
.NetCore
Csharp
小笨蛋
发布于:2021年03月17日
更新于:2021年03月17日
105
#custom-toc-container
**[通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core?(一)](https://www.codesnippet.cn/home/list/16 "通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core?(一)")** **[通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core?(二)](https://www.codesnippet.cn/home/list/17 "通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core?(二)")** **[通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core?(三)](https://www.codesnippet.cn/home/list/18 "通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core?(三)")** ### 什么是程序集 上文我介绍了编译器,即将源代码文件给翻译成一个计算机可识别的二进制程序。而在.NET Framework目录文件夹中就附带的有 用于C#语言的命令行形式的编译器csc.exe 和 用于VB语言的命令行形式的编译器vbc.exe。 我们通过编译器可以将后缀为.cs(C#)和.vb(VB)类型的文件编译成程序集。 程序集是一个抽象的概念,不同的编译选项会产生不同形式的程序集。以文件个数来区分的话,那么就分 单文件程序集(即一个文件)和多文件程序集(多个文件)。 而不论是单文件程序集还是多文件程序集,其总有一个核心文件,就是表现为后缀为.dll或.exe格式的文件。它们都是标准的PE格式的文件,主要由4部分构成: - 1.PE头,即Windows系统上的可移植可执行文件的标准格式 - 2.CLR头,它是托管模块特有的,它主要包括 - 1)程序入口方法 - 2)CLR版本号等一些标志 - 3)一个可选的强名称数字签名 - 4)元数据表,主要用来记录了在源代码中定义和引用的所有的类型成员(如方法、字段、属性、参数、事件...)的位置和其标志Flag(各种修饰符) 正是因为元数据表的存在,VS才能智能提示,反射才能获取MemberInfo,CLR扫描元数据表即可获得该程序集的相关重要信息,所以元数据表使得程序集拥有了自我描述的这一特性。clr2中,元数据表大概40多个,其核心按照用途分为3类: - 4.1.即用于记录在源代码中所定义的类型的定义表:ModuleDef、TypeDef、MethodDef、ParamDef、FieldDef、PropertyDef、EventDef, - 4.2.引用了其它程序集中的类型成员的引用表:MemberRef、AssemblyRef、ModuleRef、TypeRef - 4.3.用于描述一些杂项(如版本、发布者、语言文化、多文件程序集中的一些资源文件等)的清单表:AssemblyDef、FileDef、ManifestResourceDef、ExportedTypeDef - 3.IL代码(也称MSIL,后来被改名为CIL:Common Intermediate Language通用中间语言),是介于源代码和本机机器指令中间的代码,将通过CLR在不同的平台产生不同的二进制机器码。 - 4.一些资源文件 多文件程序集的诞生场景有:比如我想为.exe绑定资源文件(如Icon图标),或者我想按照功能以增量的方式来按需编译成.dll文件。 通常很少情况下才会将源代码编译成多文件程序集,并且在VS IDE中总是将源代码给编译成单文件的程序集(要么是.dll或.exe),所以接下来我就以单文件程序集为例来讲解。 ### 用csc.exe进行编译 现在,我将演示一段文本是如何被csc.exe编译成一个可执行的控制台程序的。 我们新建个记事本,然后将下面代码复制上去。 ```csharp using System; using System.IO; using System.Net.Sockets; using System.Text; class Program { static void Main() { string rootDirectory = Environment.CurrentDirectory; Console.WriteLine("开始连接,端口号:8090"); Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Loopback, 8090)); socket.Listen(30); while (true) { Socket socketClient = socket.Accept(); Console.WriteLine("新请求"); byte[] buffer = new byte[4096]; int length = socketClient.Receive(buffer, 4096, SocketFlags.None); string requestStr = Encoding.UTF8.GetString(buffer, 0, length); Console.WriteLine(requestStr); // string[] strs = requestStr.Split(new string[] { "\r\n" }, StringSplitOptions.None); string url = strs[0].Split(' ')[1]; byte[] statusBytes, headerBytes, bodyBytes; if (Path.GetExtension(url) == ".jpg") { string status = "HTTP/1.1 200 OK\r\n"; statusBytes = Encoding.UTF8.GetBytes(status); bodyBytes = File.ReadAllBytes(rootDirectory + url); string header = string.Format("Content-Type:image/jpg;\r\ncharset=UTF-8\r\nContent-Length:{0}\r\n", bodyBytes.Length); headerBytes = Encoding.UTF8.GetBytes(header); } else { if (url == "/") url = "默认页"; string status = "HTTP/1.1 200 OK\r\n"; statusBytes = Encoding.UTF8.GetBytes(status); string body = "" + "" + "
socket webServer -- Login
" + "" + "" + "
" + "当前访问" + url + "
" + "" + ""; bodyBytes = Encoding.UTF8.GetBytes(body); string header = string.Format("Content-Type:text/html;charset=UTF-8\r\nContent-Length:{0}\r\n", bodyBytes.Length); headerBytes = Encoding.UTF8.GetBytes(header); } socketClient.Send(statusBytes); socketClient.Send(headerBytes); socketClient.Send(new byte[] { (byte)'\r', (byte)'\n' }); socketClient.Send(bodyBytes); socketClient.Close(); } } } ``` 然后关闭记事本,将之.txt的后缀改为.cs的后缀(后缀是用来标示这个文件是什么类型的文件,并不影响文件的内容)。 上述代码相当于Web中的http.sys伪实现,是建立了通信的socket服务端,并通过while循环来不断的监视获取包的数据实现最基本的监听功能,最终我们将通过csc.exe将该文本文件编译成一个控制台程序。 我已经在前面讲过BCL,基础类库。在这部分代码中,为了完成我想要的功能,我用到了微软已经帮我们实现好了的String数据类型系列类(.NET下的一些数据类型)、Environment类(提供有关当前环境和平台的信息以及操作它们的方法)、Console类(用于控制台输入输出等)、Socket系列类(对tcp协议抽象的接口)、File文件系列类(对文件目录等操作系统资源的一些操作)、Encoding类(字符流的编码)等 这些类,都属于BCL中的一部分,它们存在但不限于mscorlib.dll、System.dll、System.core.dll、System.Data.dll等这些程序集中。 附:不要纠结BCL到底存在于哪些dll中,总之,它是个物理分散,逻辑上的类库总称。 mscorlib.dll和System.dll的区别:https://stackoverflow.com/questions/402582/mscorlib-dll-system-dll 因为我用了这些类,那么按照编程规则我必须在代码中using这些类的命名空间,并通过csc.exe中的 /r:dll路径 命令来为生成的程序集注册元数据表(即以AssemblyRef为代表的程序集引用表)。 而这些代码引用了4个命名空间,但实际上它们只被包含在mscorlib.dll和System.dll中,那么我只需要在编译的时候注册这两个dll的信息就行了。 好,接下来我将通过cmd运行csc.exe编译器,再输入编译命令: ``` csc /out:D:\demo.exe D:\dic\demo.cs /r:D:\dic\System.dll ``` /r:是将引用dll中的类型数据注册到程序集中的元数据表中 。 /out:是输出文件的意思,如果没有该命令则默认输出{name}.exe。 使用csc.exe编译生成: https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/compiler-options/command-line-building-with-csc-exe csc编译命令行介绍:https://www.cnblogs.com/shuang121/archive/2012/12/24/2830874.html 总之,你除了要掌握基本的编译指令外,当你打上这行命令并按回车后,必须满足几个条件,1.是.cs后缀的c#格式文件,2.是 代码语法等检测分析必须正确,3.是 使用的类库必须有出处(引用的dll),当然 因为我是编译为控制台程序,所以还必须得有个静态Main方法入口,以上缺一不可。 可以看出,这段命令我是将 位于D:\dic\的demo.cs文件给编译成 位于D:\名为demo.exe的控制台文件,并且因为在代码中使用到了System.dll,所以还需要通过/r注册该元数据表。 这里得注意为什么没有/r:mscorlib.dll,因为mscorlib.dll地位的特殊,所以csc总是对每个程序集进行mscorlib.dll的注册(自包含引用该dll),因此我们可以不用/r:mscorlib.dll这个引用命令,但为了演示效果我还是决定通过/nostdlib命令来禁止csc默认导入mscorlib.dll文件。 所以,最终命令是这样的: ``` csc D:\dic\demo.cs /r:D:\dic\mscorlib.dll /r:D:\dic\System.dll /nostdlib ``` ![图片alt](/uploads/images/20210605/150325-49371e991e674a368931578e7049ac23.png ''图片title'') 因为没有指定输出文件/out选项, 所以会默认输出在与csc同一目录下名为demo.exe的文件。事实上,在csc的命令中,如果你没有指定路径,那么就默认采用在csc.exe的所在目录的相对路径。 ![图片alt](/uploads/images/20210605/150337-31c094d754574fbfa9e54efb2ff525f9.png ''图片title'') 而我们可以看到,在该目录下有许多程序集,其中就包含我们需要的System.dll和mscorlib.dll,所以我们完全可以直接/r:mscorlib.dll /r:System.dll 而类似于System.dll、System.Data.dll这样使用非常频繁的程序集,我们其实不用每次编译的时候都去手动/r一下,对于需要重复劳动的编译指令,我们可以将其放在后缀为.rsp的指令文件中,然后在编译时直接调用文件即可执行里面的命令 @ {name}.rsp。 ![图片alt](/uploads/images/20210605/150348-e9766463e00a4ec0beba247b802a1942.png ''图片title'') csc.exe默认包含csc.rsp文件,我们可以用/noconfig来禁止默认包含,而csc.rsp里面已经写好了我们会经常用到的指令。 所以,最终我可以这样写 csc D:\dic\demo.cs 直接生成控制台应用程序。 ![](/uploads/images/20210605/150421-9914c16eae8c41238825de9f6e10315c.gif) ### .NET程序执行原理 好的,现在我们已经有了一个demo.exe的可执行程序,它是如何被我们运行的?。 C#源码被编译成程序集,程序集内主要是由一些元数据表和IL代码构成,我们双击执行该exe,Windows加载器将该exe(PE格式文件)给映射到虚拟内存中,程序集的相关信息都会被加载至内存中,并查看PE文件的入口点(EntryPoint)并跳转至指定的mscoree.dll中的_CorExeMain函数,该函数会执行一系列相关dll来构造CLR环境,当CLR预热后调用该程序集的入口方法Main(),接下来由CLR来执行托管代码(IL代码)。 #### JIT编译 前面说了,计算机最终只识别二进制的机器码,在CLR下有一个用来将IL代码转换成机器码的引擎,称为Just In Time Compiler,简称JIT,CLR总是先将IL代码按需通过该引擎编译成机器指令再让CPU执行,在这期间CLR会验证代码和元数据是否类型安全(在对象上只调用正确定义的操作、标识与声称的要求一致、对类型的引用严格符合所引用的类型),被编译过的代码无需JIT再次编译,而被编译好的机器指令是被存在内存当中,当程序关闭后再打开仍要重新JIT编译。 #### AOT编译 CLR的内嵌编译器是即时性的,这样的一个很明显的好处就是可以根据当时本机情况生成更有利于本机的优化代码,但同样的,每次在对代码编译时都需要一个预热的操作,它需要一个运行时环境来支持,这之间还是有消耗的。 而与即时编译所对应的,就是提前编译了,英文为Ahead of Time Compilation,简称AOT,也称之为静态编译。 在.NET中,使用Ngen.exe或者开源的.NET Native可以提前将代码编译成本机指令。 Ngen是将IL代码提前给全部编译成本机代码并安装在本机的本机映像缓存中,故而可以减少程序因JIT预热的时间,但同样的也会有很多注意事项,比如因JIT的丧失而带来的一些特性就没有了,如类型验证。Ngen仅是尽可能代码提前编译,程序的运行仍需要完整的CLR来支持。 .NET Native在将IL转换为本机代码的时候,会尝试消除所有元数据将依靠反射和元数据的代码替换为静态本机代码,并且将完整的CLR替换为主要包含垃圾回收器的重构运行时mrt100_app.dll。 .NET Native: https://docs.microsoft.com/zh-cn/dotnet/framework/net-native/ Ngen.exe:https://docs.microsoft.com/zh-cn/dotnet/framework/tools/ngen-exe-native-image-generator Ngen与.NET Native比较:https://www.zhihu.com/question/27997478/answer/38978762 --------------------------------------------------- 现在,我们可以通过ILDASM工具(一款查看程序集IL代码的软件,在Microsoft SDKs目录中的子目录中)来查看该程序集的元数据表和Main方法中间码。 ![图片alt](/uploads/images/20210605/150507-578eede7b5074663a133963ffb2aa34b.png ''图片title'') c#源码第一行代码:string rootDirectory = Environment.CurrentDirectory;被翻译成IL代码: call string [mscorlib/*23000001*/]System.Environment/*01000004*/::get_CurrentDirectory() /* 0A000003 */ 这句话意思是调用 System.Environment类的get_CurrentDirectory()方法(属性会被编译为一个私有字段+对应get/set方法)。 点击视图=>元信息=>显示,即可查看该程序集的元数据。 我们可以看到System.Environment标记值为01000004,在TypeRef类型引用表中找到该项: ![图片alt](/uploads/images/20210605/150521-d212334398834d779b6db14cfc52afef.png ''图片title'') 注意图,TypeRefName下面有该类型中被引用的成员,其标记值为0A000003,也就是get_CurrentDirectory了。 而从其ResolutionScope指向位于0x23000001而得之,该类型存在于mscorlib程序集。 ![图片alt](/uploads/images/20210605/150538-719a7e34c3a042b19cf1d19b180e3236.png ''图片title'') 于是我们打开mscorlib.dll的元数据清单,可以在类型定义表(TypeDef)找到System.Environment,可以从元数据得知该类型的一些标志(Flags,常见的public、sealed、class、abstract),也得知继承(Extends)于System.Object。在该类型定义下还有类型的相关信息,我们可以在其中找到get_CurrentDirectory方法。 我们可以得到该方法的相关信息,这其中表明了该方法位于0x0002b784这个相对虚地址(RVA),接着JIT在新地址处理CIL,周而复始。 元数据在运行时的作用: https://docs.microsoft.com/zh-cn/dotnet/standard/metadata-and-self-describing-components#run-time-use-of-metadata
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024