[.NET大牛之路 007] 详解 .NET 程序集
.NET大牛之路 • 王亮@精致码农 • 2021.07.13
上一篇我们介绍了 Roslyn 编译器,我们知道,我们编写的 C#/VB 代码经过 Roslyn 编译器编译后会生成程序集文件。按照之前讲的 .NET 执行模型的顺序,这一篇我具体讲讲程序集。
什么是程序集
我们编写的 C# 代码经过编译会生成 .dll
或 .exe
文件,这些文件就是 .NET 的程序集(Assembly)。
尽管 .NET 的程序集文件与非托管的 Windows 二进制文件采用相同的文件扩展名(*.dll
),但它们的内部完全不同。具体来说,.NET Core 程序集文件不包含平台(泛指操作系统和 CUP 架构的组合)特定的指令,而是平台无关的中间语言(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
文件。
程序集的组成
总的来说,每个程序集文件主要由 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 代码、元数据和程序集清单。
IL 代码
我们先来看看下面这样一段简单的 C# 代码被编译成 IL 代码会是什么样子。C# 代码如下:
class Calculator
{
public int Add(int num1,int num2)
{
return num1 + num2;
}
}
经过编译后,在项目的 bin\Debug
目录会生成一个与项目名称同名的 dll 程序集文件。我们使用 ildasm.exe
工具打开这个文件,定位到 Calculator
的 Add
方法,可以看到 Add
方法的 IL 代码如下:
.method public hidebysig
instance int32 Add (
int32 num1,
int32 num2
) cil managed
{
// Code size 9 (0x9)
.maxstack 2
.locals init (
[0] int32
)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: ldarg.2
IL_0003: add
IL_0004: stloc.0
IL_0005: br.s IL_0007
IL_0007: ldloc.0
IL_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 代码。
元数据
除了 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.Object
Method #1 (06000003)
-------------------------------------------------------
MethodName: Add (06000003)
Flags : [Public] [HideBySig] [ReuseSlot] (00000086)
RVA : 0x00002090
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: I4
2 Arguments
Argument #1: I4
Argument #2: I4
2 Parameters
(1) ParamToken : (08000002) Name : num1 flags: [none] (00000000)
(2) ParamToken : (08000003) Name : num2 flags: [none] (00000000)
元数据会被 .NET Core 运行时以及各种开发工具所使用。例如,Visual Studio 等工具所提供的智能提示功能就是通过读取程序集的元数据而实现的。元数据也被各种对象浏览工具、调试工具和 C# 编译器本身所使用。元数据是众多 .NET Core 技术的支柱,比如反射、对象序列化等。
程序集清单
.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
指令记录了它所引用的外部程序集。接着是当前程序集本身的信息,记录了程序集本身的各种特征,如版本号、模块名称等。
提前编译 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 应用程序都是完全可以接受的。如果实在对启动时性能有严格的要求,也可以使用预热的方案。
小结
本文介绍了程序集以及它的内部组成:IL 代码、元数据、资源文件和程序集清单。总的来说,程序集就是 .NET Core 在编译后生成的 *.dll
文件,它包含托管模块、资源文件和程序集清单,其中托管模块由 IL 代码和元数据组成。
需要强调的是,IL 代码不包含特定平台的指令,它只在需要的时候才会被 CoreCLR 运行时中的 JIT 编译器编译成特定于平台的本地代码。
通过本文,相信大家对 .NET Core 程序集和它的内部组成已经有了一个整体的认识。
[.NET大牛之路 007] 详解 .NET 程序集的更多相关文章
- Android进阶之路(2)-详解MVP
### MVP简介 >MVP 全称:Model-View-Presenter :MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的[地方](https://baike.baidu.co ...
- [原创]java WEB学习笔记58:Struts2学习之路---Result 详解 type属性,通配符映射
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- Go开发之路 -- 函数详解
声明语法 func 函数名 (参数列表) [(返回值列表)] {} Golang函数特点 a. 不支持重载,一个包不能有两个名字一样的函数 b. 函数是一等公民,函数也是一种类型,一个函数可以赋值给变 ...
- Android进阶之路(1)-详解MVC
最近因为换工作的原因没有写博客,现在慢慢稳定了,我准备写一些关于Android 进阶的文章,也是为了督促自己学习,大家一起进步! 今天详细的分析一下Android APP架构之一:MVC ### MV ...
- Nop--NopCommerce源码架构详解专题目录
最近在研究外国优秀的ASP.NET mvc电子商务网站系统NopCommerce源码架构.这个系统无论是代码组织结构.思想及分层都值得我们学习.对于没有一定开发经验的人要完全搞懂这个源码还是有一定的难 ...
- 从苦逼到牛逼,详解Linux运维工程师的打怪升级之路
做运维也快四年多了,就像游戏打怪升级,升级后知识体系和运维体系也相对变化挺大,学习了很多新的知识点. 运维工程师是从一个呆逼进化为苦逼再成长为牛逼的过程,前提在于你要能忍能干能拼,还要具有敏锐的嗅觉感 ...
- [js高手之路] es6系列教程 - 对象功能扩展详解
第一:字面量对象的方法,支持缩写形式 //es6之前,这么写 var User = { name : 'ghostwu', showName : function(){ return this.nam ...
- [js高手之路]深入浅出webpack系列2-配置文件webpack.config.js详解
接着上文,重新在webpack文件夹下面新建一个项目文件夹demo2,然后用npm init --yes初始化项目的package.json配置文件,然后安装webpack( npm install ...
- [js高手之路]深入浅出webpack教程系列3-配置文件webpack.config.js详解(下)
本文继续接着上文,继续写下webpack.config.js的其他配置用法. 一.把两个文件打包成一个,entry怎么配置? 在上文中的webpack.dev.config.js中,用数组配置entr ...
随机推荐
- css边框样式(动画)
html: <div class="wrap"> <a href="#">shui</a> </div> css ...
- Springboot:Springboot+mysql5.7搭建服务,超过8小时连接mysql失败
报错信息 2017-03-12 03:00:02.539 ERROR 9311 --- [nio-9000-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] ...
- 面试:Spring面试知识点总结
Spring知识点总结 1. 简介一下Spring框架. 答:Spring框架是一个开源的容器性质的轻量级框架.主要有三大特点:容器.IOC(控制反转).AOP(面向切面编程). 2. Spring框 ...
- Mysql索引降维 优化查询 提高效率
在前一篇文章中,我们已经介绍了索引.索引的优化规则等等 原文链接:Siam博客 mysql索引优化 在其中我们有引申出组合索引,多个单字段索引冲突两个知识点. 本文章主要是与后者有关联. 在原文中,我 ...
- TCP/UDP/HTTP的区别和联系(转载)
一.TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据. 关于TCP/IP和HTTP协议的关系,网络有一段比较容易理解的介绍:"我们在传 ...
- linux学习之路第二天(xshell和xftp的使用图解)
以上xshell的连接过程 2.远程上传和下载文件xftp6 一定要换成22和sftp,否则连接不上 如果出现中文乱码的情况 先点击那个小齿轮 再点击选项 把编码编程UTF-8就ok了
- RabbitMQ入门教程 [转]
1.引言 RabbitMQ--Rabbit Message Queue的简写,但不能仅仅理解其为消息队列,消息代理更合适.消息队列主要解决应用耦合,异步消息,流量削锋等问题.实现高性能,高可用,可伸缩 ...
- 前端-Vue基础1
Vue核心思想:只要改变数据,页面就会发生改变 1.引入vue 1.下载vue.js 2.在script标签的src属性中,引入vue.js <script src="js/vue.j ...
- Command 'ifconfig' not found, but can be installed with: sudo apt install net-tools VM Ubuntu 解决方案
VMware下安装的Ubuntu 一开始由于Firefox连不上网,然后通过看了https://www.bbsmax.com/A/VGzlEGYJbq/这个文章之后,自己也测了一下,确实好用 但是if ...
- 高校表白App-团队冲刺第六天
今天要做什么 在引导页的基础上添加小红点,并且在滑动时进行增强用户体验的修饰 做了什么 在布局中成功添加小红点,并在activity中得到实现;滑动在3/4时发生渐变,增强用户体验;滑动可回退;在最后 ...