[007] 详解 .NET 程序集

上一篇我们介绍了 Roslyn 编译器,我们知道,我们编写的 C#/VB 代码经过 Roslyn 编译器编译后会生成程序集文件。按照之前讲的 .NET 执行模型的顺序,这一篇我具体讲讲程序集。

1什么是程序集

我们编写的 C# 代码经过编译会生成 .dll.exe 文件,这些文件就是 .NET 的程序集(Assembly)。

尽管 .NET 的程序集文件与非托管的 Windows 二进制文件采用相同的文件扩展名(*.dll),但它们的内部完全不同。具体来说,.NET Core 程序集文件不包含平台(泛指操作系统和 CPU 架构的组合)特定的指令,而是平台无关的中间语言(IL)和类型元数据。

你可能在一些 .NET/.NET Core 的文档中看到过 IL 的另外两种缩写:MSIL(Microsoft Intermediate Language,微软中间语言) 和 CIL(Common Intermediate Language,通用中间语言)。IL、MSIL 和 CIL 都是一个概念,其中 MSIL 是早期的叫法,现在已经很少有人用了。

但 .NET Core 与 .NET Framework 不一样,.NET Core 始终只会生成 *.dll 格式的程序集文件,即使是像控制台应用这样的可执行项目也不再会生成 *.exe 格式的程序集文件。

那我们在 .NET Core 项目的 bin 目录中看到和项目同名的 *.exe 文件是怎么回事呢?这个文件并不是一个程序集文件,而是专门为 Windows 平台生成的一个可执行的快捷方式。在 Windows 平台双击这个文件等同于执行 dotnet <assembly name>.dll 命令。在我们安装的 .NET Core 目录中有个 dotnet.exe 命令文件(如 Windows 系统默认位置是C:\Program Files\dotnet\dotnet.exe),在编译时,该文件会被复制到构建目录,并重命名为与项目名称同名的 <assembly name>.exe 文件。

2程序集的组成

总的来说,每个程序集文件主要由 IL 代码、元数据(Metadata)、清单(Manifest) 和资源文件(如 jpg、html、txt 等)组成。其中,IL 代码和元数据会先被编译为一个或多个托管模块,然后托管模块和资源文件会被合并成程序集。

托管模块,或者叫托管资源或托管代码,顾名思义,这种资源是由 .NET Core 的 CLR 运行时来管理运行的,它包含 IL 代码和元数据。比如对象的回收是由 CLR 中垃圾回收器(GC)自动执行的,不需要手动管理。

程序集文件中占比最大的一般是 IL 代码。IL 代码和 Java 字节码相似,它不包含平台特定的指令,它只在必要的时候被 .NET Core 运行时中的 JIT 编译器编译成本机代码(机器码)。

程序集文件中的元数据详细地描述了程序集文件中每个类型的特征。比如有一个名为 Product 的类,类型元数据描述了 Product 的基类、实现的接口(如果有的话)和每个成员的完整描述等细节。元数据由语言编译器(Roslyn)自动生成。

除了托管模块,程序集文件还可以嵌入资源文件,如 jpg、gif、html 等格式的静态文件,这些文件是非托管资源。

当托管模块和资源文件合并成程序集时,会生成一份清单,它是专门用来描述程序集本身的元数据。清单包含程序集的当前版本信息、本地化信息(用于本地化字符串等),以及正确执行所需的所有外部引用程序集列表等。

在第 5 篇文章中我们讲了 .NET 的两种执行模型,其中,当基于本地运行时执行模型发布时,虽然你的应用程序可以发布为可直接执行的单一文件,但这个单一的文件其实是多个文件的包装。它包含了由 IL 代码编译成的本地代码和 Native AOT 本地运行时。你的代码仍然在一个托管的容器中运行,运行时它的资源的管理和它作为多个文件发布是一样的。

下面让我们更详细地了解一下 IL 代码、元数据和程序集清单。

3IL 代码

我们先来看看下面这样一段简单的 C# 代码被编译成 IL 代码会是什么样子。C# 代码如下:

class Calculator
{public int Add(int num1,int num2){return num1 + num2;}
}

经过编译后,在项目的 bin\Debug 目录会生成一个与项目名称同名的 dll 程序集文件。我们使用 ildasm.exe 工具打开这个文件,定位到 CalculatorAdd 方法,可以看到 Add 方法的 IL 代码如下:

.method public hidebysiginstance int32 Add (int32 num1,int32 num2) cil managed
{// Code size 9 (0x9).maxstack 2.locals init ([0] int32)IL_0000: nopIL_0001: ldarg.1IL_0002: ldarg.2IL_0003: addIL_0004: stloc.0IL_0005: br.s IL_0007IL_0007: ldloc.0IL_0008: ret
}

以我的安装环境为例,你可以在这个位置找到 ildasm.exe 工具:C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\ildasm.exe。为了使用方便,你可以把该工具配置到 Visual Studio 的外部工具中。

这就是 IL 代码的样子,如果使用 VB 或 F# 编写相同的 Add 方法,它生成的 IL 代码会是一样的。关于 IL 代码语法后面有机会再讲,这里我们暂且不关心。

由于程序集中的 IL 代码不是平台特定的指令,所以 IL 代码必须在使用前调用 JIT 编译器进行即时编译,将其编译成特定平台(特定的操作系统和 CUP 架构,如 Linux x64)的本地代码,才能在该平台运行起来。

.NET Core 运行时会在 JIT 编译过程中针对特定平台再次进行底层优化。比如将 IL 代码编译成特定于某平台的本地代码时,它会把平台无关的代码剔除。并且,它会以适合目标操作系统的方式将编译好的本地代码缓存在内存中,供以后使用,下次不需要重新编译 IL 代码。

4元数据

除了 CIL 代码外,.NET Core 程序集还包含完整、全面、细致的元数据,它描述了程序集中定义的每个类型(如类、结构、枚举),以及每个类型的成员(如属性、方法、事件),这些信息生成都由编译器自动完成的。

我们继续使用 ildasm.exe 来看看 .NET Core 元数据具体长什么样。以前面的代码为例,选择该程序集,依次点击“视图->元信息->显示”,可以看到当前程序集的所有元数据信息。我们可以在元数据信息中找到 Calculator 类的 Add 方法,它的元数据是这样的:

TypeDef #2 (02000003)
-------------------------------------------------------TypDefName: ConsoleApp.Calculator  (02000003)Flags     : [NotPublic] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit]  (00100000)Extends   : 0100000C [TypeRef] System.ObjectMethod #1 (06000003)-------------------------------------------------------MethodName: Add (06000003)Flags     : [Public] [HideBySig] [ReuseSlot]  (00000086)RVA       : 0x00002090ImplFlags : [IL] [Managed]  (00000000)CallCnvntn: [DEFAULT]hasThisReturnType: I42 ArgumentsArgument #1:  I4Argument #2:  I42 Parameters(1) ParamToken : (08000002) Name : num1 flags: [none] (00000000)(2) ParamToken : (08000003) Name : num2 flags: [none] (00000000)

元数据会被 .NET Core 运行时以及各种开发工具所使用。例如,Visual Studio 等工具所提供的智能提示功能就是通过读取程序集的元数据而实现的。元数据也被各种对象浏览工具、调试工具和 C# 编译器本身所使用。元数据是众多 .NET Core 技术的支柱,比如反射、对象序列化等。

5程序集清单

.NET Core 程序集还包含描述程序集本身的元数据,我们称之为清单。清单记录了当前程序集正常运行所需的所有外部程序集、程序集的版本号、版权信息等等。与类型元数据一样,生成程序集清单也是由编译器的工作。

同样地,还是以上面 Calculator 类所在项目为例,我们也来看看程序集清单长什么样子。在 ildasm.exe 工具打开的程序集的目录树中,双击 MAINFEST 即可查看程序集的清单内容:

.assembly extern System.Runtime
{.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:.ver 5:0:0:0
}
.assembly extern System.Console
{.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:.ver 5:0:0:0
}
.assembly ConsoleApp
{....custom instance void ... TargetFrameworkAttribute ....custom instance void ... AssemblyCompanyAttribute .......hash algorithm 0x00008004.ver 1:0:0:0
}
.module ConsoleApp.dll
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY

可以看到,程序集清单首先通过 .assembly extern 指令记录了它所引用的外部程序集。接着是当前程序集本身的信息,记录了程序集本身的各种特征,如版本号、模块名称等。

6提前编译 IL 代码

前面提到,IL 代码需要先通过 JIT 编译器编译成特定平台的本地代码,才能在该平台运行。你可能会问,.NET 为什么要将源代码编译成 IL 代码,而不直接编译成特定平台的本地代码呢?

这样做主要有两个好处:一是语言整合,一套运行时环境可以运行多种语言编写的程序,.NET 团队不用开发和维护多套运行时;二是平台无关,方便程序和库的移植,编译后的程序集可以发布到多个平台,而不用为不同的平台发布特定的程序文件。虽然 IL 代码带来了可移植性等的好处,但需要以牺牲一点点启动时的性能作为代价。

一般我们的 Web 应用程序最终只会部署在一种平台(如 Linux x64),为了更快的启动性能,在启动时,我们确实可以不需要中间语言编译这个环节,省去启动时的 JIT 编译的时间。.NET Core 为我们提供了两种方式把 IL 代码提前编译成特定平台的本地代码。

一种方式是使用 ReadyToRun 功能。.NET Core 运行时(CoreCLR)中的一个叫做 CrossGen 的工具,它可以预先将 IL 代码编译成本地代码。要使用这个功能,只需在程序发布的时候,选择特定平台,在发布选项中勾选 Enable ReadyToRun compilation 即可。不过 ReadyToRun 功能目前只适用于 Windows 系统。

另一种方式是使用 .NET 5 新增加的 AOT 编译功能。发布时选择 Self-Contained 模式,发布后生成单个文件。AOT 编译也是提前将 IL 代码编译成本地代码,不同的是,它在发布时生成的单个文件还包含一个精简版的本地运行时。这点在第 5 篇文章讲过,不再累述了。

这两种方式都有弊端,第一种目前只适用于 Windows 系统,第二种 Self-Contained 单个文件发布要比多文件发布大几十 M。不过对于第一次启动慢那么一点点(可能甚至不到一秒的时间),大部分的 Web 应用程序都是完全可以接受的。如果实在对启动时性能有严格的要求,也可以使用预热的方案。

7小结

本文介绍了程序集以及它的内部组成:IL 代码、元数据、资源文件和程序集清单。总的来说,程序集就是 .NET Core 在编译后生成的 *.dll 文件,它包含托管模块、资源文件和程序集清单,其中托管模块由 IL 代码和元数据组成。

需要强调的是,IL 代码不包含特定平台的指令,它只在需要的时候才会被 CoreCLR 运行时中的 JIT 编译器编译成特定于平台的本地代码。

通过本文,相信大家对 .NET Core 程序集和它的内部组成已经有了一个整体的认识。

加入我们,一起踏上.NET大牛成长之路↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/300114.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

21岁就破解困扰人们300年难题的天才,却一生坎坷,怀才不遇,至死还得不到认可...

这不是难题本来就是无解何谓数学&#xff1f;数学家Eduardo曾这样回答“数学是永恒&#xff0c;是真理&#xff0c;是一切的答案。”回首往昔数学始终伴随我们左右纵横交错的几何、繁琐复杂的运算难以求解的方程、无从下手的猜想......尽管在数学道路上有多么的坎坷、崎岖、变化…

android 百度地图 在线建议查询,百度地图SDK-----百度地图在线建议查询,结合AutoCompleteTextView实现搜索下拉列表。...

实现效果图 如下这是百度地图 POISearch的效果&#xff0c;这是自己写的效果首先实现这个功能主要用到了两个部分第一个部分 AutoCompleteTextView具体使用参考 http://blog.csdn.net/iamkila/article/details/7230160第二个部分 百度地图的在线搜索建议功能。http://developer…

共享内存 Actor并发模型到底哪个快?

HI&#xff0c;前几天被.NET圈纪检委懒得勤快问到共享内存和Actor并发模型哪个速度更快。前文传送门&#xff1a;《三分钟掌握共享内存 & Actor并发模型》说实在&#xff0c;我内心10w头羊驼跑过.....先说结论1.首先两者对于并发的风格模型不一样。共享内存利用多核CPU的优…

web service

一、Web Service简介 1.1、Web Service基本概念 Web Service也叫XML Web Service WebService是一种可以接收从Internet或者Intranet上的其它系统中传递过来的请求&#xff0c;轻量级的独立的通讯技术。是:通过SOAP在Web上提供的软件服务&#xff0c;使用WSDL文件进行说明&#…

来自爸妈的敷衍问候!| 今日最佳

全世界只有3.14 % 的人关注了青少年数学之旅

Android系统如何实现UI的自适应

2019独角兽企业重金招聘Python工程师标准>>> 做Android应用的人都知道&#xff0c;要一个apk适用多个不同的手机屏幕是很容易的&#xff0c;就是在项目的res文件夹下面有多套相关的资源文件。程序运行的 时候&#xff0c;Android系统会根据当前设备的信息去加载不同…

自定义EventSource(二)PollingCounter

在自定义EventSource时&#xff0c;可以使用四种EventCounter&#xff1a;EventCounter&#xff1a;统计指标收集器&#xff0c;比如平均值&#xff0c;最大值&#xff0c;最小值PollingCounter&#xff1a;自定义统计指标收集器&#xff0c;通过自定义统计方法的方式实现对指标…

这9个人气超高的公众号,你还没关注吗?

有些人&#xff0c;生活离不开朋友圈朋友圈是他们展示自我、观察世界的一扇窗户而有些人&#xff0c;从来也不点开朋友圈他们更愿意利用地铁上的零散化时间看点有意义、有知识的公众号推送完成对自我知识库的更新今天为大家推荐以下优质订阅号Kindle杂志公社ID&#xff1a;Mag1…

客户要求ASP.NET Core API返回特定格式,怎么办?

当ASP.NET Core API提供给到外部系统使用时&#xff0c;在某些情况下&#xff0c;可能需要根据这些外部系统的要求来格式化数据。比如&#xff0c;客户就要求API的返回值属性名必须是PascalCase&#xff08;如UserName&#xff09;&#xff0c;但是这些API需要同时提供给内部系…

史上最神奇的公式,竟然藏着这么多秘密!

全世界只有3.14 % 的人关注了青少年数学之旅前两天&#xff0c;我们的【欧拉公式—数学史上最强公式】数学经典文化衫首发众筹&#xff01;没想到短短几天时间文化衫就售罄了&#xff0c;其火爆程度远远超出了超模君的预期&#xff0c;甚至连不少“白嫖党”遇上这款文化衫后都情…

HiccDS共享音乐列表

Donet 第七组共享音乐列表200.7.7.18

html插入图片和文字,HTML第三课文字图片插入

HTML学习班第三课文字与图片的插入朋友们这一课我们一起来学习一下“文字与图片”的插入&#xff1a;一&#xff1a;首先我们先看一下插入文字的语法&#xff1a;例1&#xff1a;“朋友们好”这几个字的代码&#xff1a;朋友们好朋友们好说明&#xff1a;朋友们不难看出“朋友们…

Object C学习笔记11-数组

在Object C也提供了类似C#中的Array数组对象&#xff0c;在Object C中使用NSArray 来创建数组&#xff1b;但是在Object C中NSArray 只能存放对象类型的指针&#xff0c;不能存放int&#xff0c;char,double等基本数据类型。 一. 不可变数组对象 这个和之前的NSString有些类似&…

你是不是在混日子,看着一点就知道了

全世界只有3.14 % 的人关注了青少年数学之旅2019已经进入倒计时了&#xff0c;年初立下的flag倒了几个&#xff1f;史蒂夫马丁有一句话&#xff1a;“所有的人生谜语都可以从阅读中找到答案。”无论是读影评、读好书&#xff0c;亦或者涉猎趣闻轶事、汲取犀利观点&#xff0c;总…

Apache Member、ALC Beijing 发起人姜宁:一个人走的很快,但是一群人能走得更远

一个人走的很快&#xff0c;但是一群人能走得更远——姜宁ApacheCon 是 Apache 软件基金会&#xff08;ASF&#xff09;的官方全球系列大会。作为久负盛名的开源盛宴&#xff0c;ApacheCon 在开源界备受关注&#xff0c;也是开源运动早期的知名活动之一。早在 1998 年&#xff…

如何把朋友升级成情侣?| 今日最佳

全世界只有3.14 % 的人关注了青少年数学之旅

java 多线程原理(一)

2019独角兽企业重金招聘Python工程师标准>>> 要说Java的多线程&#xff0c;首先要明白什么是多线程。 多线程&#xff0c;线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程. 我勒个擦&#xff0c;定义好官方啊&#xf…

discuz!5.5.0安装方法及常见问题解决

今下午在前几天刚组好的windows xp sp2apache2.2.4mysql6.0php5.2.3&#xff08;下一次做成LAMP——Linux ApacheMysqlPHP&#xff09;的环境下装了discuz&#xff01;5.5.0。安装之前到网上下了一个ZendOptimizer-3.2.6-Windows-i386.zip&#xff08;最新版&#xff09;&#…

百般受虐!“波士屯动力”机器人这一次枪口对准人类

全世界只有3.14 %的人关注了青少年数学之旅如果机器人拿起了枪会怎么样&#xff1f;这两天&#xff0c;一则Bosstown Dynamics发布的新视频在社交媒体疯传&#xff1a;视频中&#xff0c;一个酷似“波士顿动力Atlas”的机器人在进行射击训练。请看视频&#xff1a;刚开始&#…