前言

随着 .NET5.0 Preview 8 的发布,许多新功能正在被社区成员一一探索;这其中就包含了“单文件发布”这个炫酷的功能,实际上,这也是社区一直以来的呼声,从 WinForm 的 msi 开始,我们就希望有这样一个功能,虽然在 docker 时代,单文件发布的功能显得“不那么重要”,但正是从这一点可以看出,.NET 的团队成员一直在致力于实用功能的完善。

在 Java 的世界里,单文件发布一直伴随着他们的成长,War 文件可以直接上传到 Tomcat 上运行,话说我们还是有那么一丢丢的羡慕的,不过凡事有利就有弊,单文件发布对于细分模块的热更新来说,还有有一点点的不方便。

不过瑕不掩瑜,在微服务概念越来越火热的今天,相信单文件发布的功能带给大家更多的是兴奋。

什么是单文件发布

首先,我们要清楚的了解,什么是单文件发布。

官方的目标定义:

.Net 5.0单个文件解决方案应为:

  • 广泛兼容:可以将包含IL程序集,随时运行的程序集,复合程序集,本机二进制文件,配置文件等的应用程序打包为一个可执行文件。
  • 可以直接从打包软件直接运行应用程序的托管组件,而无需提取到磁盘。
  • 可与调试器和工具一起使用。

从上面的目标可以看出,和以往版本最大的不同在于:将所有依赖打包到一个可执行文件中,可直接运行,不影响调试操作。

注意上面的这句话“将所有依赖打包到一个可执行文件中”,而在以往,我们使用 dotnet publish 将应用程序进行发布之后,我们会看到,在 publish 下有许多项目依赖的 dll 文件,在 .NET5.0 到来之后,这些依赖文件可收纳到一个文件中,瞬间让人感受到了清凉。

发布操作指令相关

命令

可选参数

配置文件设置参数

除了可以使用命令行参数的形式,还可以通过配置文件的形式设置发布参数,编辑项目文件,添加配置节点到文件中并保存即可。

net5.0linux-x64truetrue

关于 RID 说明见:https://docs.microsoft.com/en-us/dotnet/core/rid-catalog

这是截止本文发布前的 RID 版本,不排除 .NET5.0 有新的发布

其它参数

除了上面的三个可选参数,我在查询文档的过程中还发现,官方还提到了其它参数的使用,目前不确定是否有效

true true PreserveNewesttrue

还可以通过设置 ExcludeFromSingleFile 元素,该设置将指定某些文件不嵌入单个文件之中。

编写待打包的应用程序

为了更直观的看出正常发布和单文件发布的区别,我们特别准备了一个 Web 应用程序,并对两个程序集进行依赖引用。

准备好项目,编译成功,尝试发布,打开 PowerShel 控制台,分别输入以下命令

dotnet publish -r linux-x64 /p:PublishSingleFile=truedotnet publish -r win-x64 --self-contained=false /p:PublishSingleFile=true

linux-x64 和 win-x64 两个目录下,分别有 publish 目录,由于平台的不同,所引用的依赖也不一样,这是我们早就了解过的,我们看看打包前后的区别

以上执行的两条命令语句,会为我们生成 Linux 和 Windows 两个平台的程序包,从上图中可以看出,在打包之前,项目的各种引用依赖都被复制到了发布目录下,这也是我们之前的程序发布方式,在经过打包后,所有依赖文件都被装入了一个可执行文件中,在 Linux 平台下表现为:PreviewWebApplication ,Windows 平台下则为:PreviewWebApplication.exe。从打包效果来看,迁移将变得更加方便了。

运行打包程序

打包后的程序和未打包的发布程序在运行方式上没有太多的差异性,在 Windows 平台上,只需要双击 PreviewWebApplication.exe 就可以运行该打包程序了,本示例创建的是一个 WebApi 的程序,直接访问程序侦听的地址后得到接口返回的结果,如果您创建的是带有 Razor 视图或者携带其它资源文件的,可能无法访问指定的 url。

在程序成功运行起来后,我们发现,打包程序并没有解压缩文件到磁盘,而是直接从包中加载文件到内存中运行;这是巨大的进步,也是和 War 文件根本的区别。

需要注意的是,该 .exe 文件并不能单独复制到别的地方运行,你必须把 .exe 当前目录完整的复制才能运行,这涉及到主机探测的问题,下面我们将会一一提到。

跨平台的打包文件

通过上面的示例我们了解到,打包程序总是为不同的平台生成独立的包程序,这是为什么呢?这里就涉及到一个概念,也就是 Tool Interface Standard (TIS)

Executable and Linking Format(ELF)

Common Object File Format(COFF)于1983年引入,最初使用在 AT&T 的 UNIX 系统上。由于 COFF 的各种局限性,比如:节的最大数量受到限制,节名称,所包含的源文件的长度受到限制,并且符号调试信息无法支持实际的语言。最后,在 System V Release 4 (SVR4) 发布后,AT&T 使用 ELF 替代了 COFF。

工具接口标准委员会 援引委员会规范文件的说明:可执行文件和链接格式最初由 UNIX 系统开发和发布实验室(USL)作为应用程序二进制接口(API)的一部分。工具接口标准委员会 (TIS) 选择将不断发展的 ELF 标准作为便携式对象文件。该标准适用于各种操作系统的 32 位英特尔架构环境的格式。ELF 标准旨在通过向开发人员提供具有一组跨多个操作环境的二进制接口定义。这将减少不同接口实现的数量,从而减少需要重新编写和编译的代码。

ELF 文件结构又分为三种类型,分别是:

Portable Executable (PE)

在 Windows 阵营,微软在此 COFF 标准的基础上,又进行了创新和发展出了 PE 文件标准

PE Format 该规范描述了Windows操作系统家族下的可执行文件(图像)和目标文件的结构。这些文件分别称为可移植可执行(PE)和公用对象文件格式(COFF)文件。

从上面的两种规范中可以看出,LinuX 和 Windows 都有各自的文件格式规范,而这种规范在一定程度上是不兼容的,不论是从文件结构还是解析方式;所以 .NET5.0 中的打包程序必须为不同的平台实现独立的打包器。打包器的实现在 runtime 中的 Microsoft.NET.HostModel 库中。

认识了 ELF 和 PE 文件结构之后,我们就可以对打包器代码进行阅读理解。

Microsoft.NET.HostModel

你可以从 github 上下载 .NET 5.0 的源代码, 转到目录:

runtime/src/installer/managed/Microsoft.NET.HostModel

源码不太多,可直接进行阅读,主要理解层次关系即可。

打包器主要包含了三大部分的内容,分别是 AppHost、Bundler、ComHost

文件 Bundle/Manifest.cs 的头部,我们看到了“单文件程序”的文件结构定义

BundleManifest is a description of the contents of a bundle file. This class handles creation and consumption of bundle-manifests.  Here is the description of the Bundle Layout: _______________________________________________ AppHost ------------Embedded Files ---------------------The embedded files including the app, itsconfiguration files, dependencies, and possibly the runtime.------------ Bundle Header -------------    MajorVersion    MinorVersion    NumEmbeddedFiles    ExtractionID    DepsJson Location [Version 2+]       Offset       Size    RuntimeConfigJson Location [Version 2+]       Offset       Size    Flags [Version 2+]- - - - - - Manifest Entries - - - - - - - - - - -    Series of FileEntries (for each embedded file)    [File Type, Name, Offset, Size information]        _________________________________________________

从上面的文件结构中,我们可以非常清晰的看到,单文件程序的结构一共分为三大部分,分别是:

文件头信息查看

我们可以通过一些工具去查看已经打包好的文件,在 Linux 下,可以使用 readelf/objdump 等程序来获取 PreviewWebApplication 文件的信息。在 Windows 下,可以使用 PE Tools 等工具

Linux 下 readelf 读取文件头信息

从图中我们可以看到 Type:DYN (Shared object file) 这是一个标准的共享对象文件,关于 ELF 头部信息的内容不再展开,有兴趣的同学可以自行学习相关内容。

Windows下 PE Tools 读取文件头信息

已经打包好的程序内部包含了 319(Linux)、Windows(359) 个文件,Windows 版本在未打包前是 84.3MB,打包后是 69.8MB,最重要的是在运行时无需解压缩,直接从 Bundle 中运行文件。

文件中的第三部分,也就是 “实体清单(Manifest Entries)的写入代码在 BundleBundler.csAddToBundle

long AddToBundle(Stream bundle, Stream file, FileType type){    if (type == FileType.Assembly)    {        long misalignment = (bundle.Position % AssemblyAlignment);        if (misalignment != 0)        {            long padding = AssemblyAlignment - misalignment;            bundle.Position += padding;        }    }    file.Position = 0;    long startOffset = bundle.Position;    file.CopyTo(bundle);    return startOffset;}

在成员方法 GenerateBundle(IReadOnlyList fileSpecs) 内部迭代调用了 AddToBundle 方法,完成了实体清单文件的写入。

// 代码片段public string GenerateBundle(IReadOnlyList fileSpecs){  ... foreach (var fileSpec in fileSpecs)  {      string relativePath = fileSpec.BundleRelativePath;      ...      using (FileStream file = File.OpenRead(fileSpec.SourcePath))      {          FileType targetType = Target.TargetSpecificFileType(type);          long startOffset = AddToBundle(bundle, file, targetType);          FileEntry entry = BundleManifest.AddEntry(targetType, relativePath, startOffset, file.Length);          Tracer.Log($"Embed: {entry}");      }  }  // Write the bundle manifest  headerOffset = BundleManifest.Write(writer);  ...}

因为解压器的实现已经转移到了 HostFxr 和 HostPolicy 中,以静态链接库的方式链接到打包器中,且该部分代码由 C++ 进行编写,鉴于 C++ 水平有限,在这里不作介绍。

结束语

编写这篇文章耗费了我大量的时间,期间大量阅读海量的参考资料、文献、标准文档、制作文章配图等等,写干货文章真的需要投入巨大的精力和时间,希望你们喜欢。

主要参考资料

.NET团队计划经理 Richard Lander 的博客:https://devblogs.microsoft.com/dotnet/announcing-net-5-0-preview-8/ Bundler 进度表:https://github.com/dotnet/runtime/issues/36590 single-file:https://github.com/dotnet/designs/tree/master/accepted/2020/single-file ELF文档:https://refspecs.linuxbase.org/elf/elf.pdf ELF维基百科:https://en.wikipedia.org/wiki/Executable_and_Linkable_Format Readelf:https://sourceware.org/binutils/docs/binutils/readelf.html PE文档:https://docs.microsoft.com/en-us/windows/win32/debug/pe-format PE Tools:https://github.com/petoolse/petools

.net5 不支持winform_.NET5.0 单文件发布打包操作深度剖析相关推荐

  1. .NET5.0 单文件发布打包操作深度剖析

    .NET5.0 单文件发布打包操作深度剖析 前言 随着 .NET5.0 Preview 8 的发布,许多新功能正在被社区成员一一探索:这其中就包含了"单文件发布"这个炫酷的功能,实 ...

  2. .net5 不支持winform_.NET 5.0 RC 2 发布,正式版将在11月.NET Conf大会上发布

    原文:http://dwz.win/ThX 作者:Richard 翻译:精致码农-王亮 说明:本译文舍弃了少许我实在不知道如何翻译但又不是很重要的语句. 今天(10月13日)我们发布了 .NET 5. ...

  3. AOT和单文件发布对程序性能的影响

    AOT和单文件发布对程序性能的影响 以前的.NET框架原生并不支持最终编译结果的单文件发布(需要依赖第三方工具) 1. 前言 这里先和大家介绍一下.NET一些发布的历史,以前的.NET框架原生并不支持 ...

  4. WPF 基于 .NET 5 框架和 .NET 6 的 SDK 进行完全单文件发布

    本文来告诉大家如何基于 .NET 5 框架和 .NET 6 SDK 进行完全单文件发布,这是对 WPF 应用程序进行独立发布,生成的是完全单文件的方法 在之前的版本,尽管也是基于 .NET 5 框架的 ...

  5. 《PyInstaller打包实战指南》第十六节 单文件模式打包PyGame

    第十六节 单文件模式打包PyGame 打包示例源码下载: M to the B / Coffee Breakout · GitLab 版本信息: pygame==1.9.6 pyinstaller== ...

  6. .net5 不支持winform_「开源资讯」.NET 5.0 RC 2 发布

    微软上周发布了 .NET 5.0 RC 2,此版本已接近最终发布,也是11月正式版发布前的最后一个 RC 版本.微软还表示这是一个"go live"版本,支持用于生产环境,当然这不 ...

  7. .net5 不支持winform_昨晚实操一波.NET5,极致性能简直逆天!

    " 9月14日,.NET5发布了(Release Candidate)RC1版本,是11月正式版本之前两个RC版本中第一个,包含语言新版本C#9和F#5,需要用Visual Studio 2 ...

  8. 文件锁定工具IObit Unlocker v1.2.0单文件

    介绍: IObit Unlocker,解除文件锁定工具,文件强制删除工具,支持Windows所有版本,可以方便的通过资源管理器右键解除被锁定占用的文件,从而轻松删除被锁定的文件或文件夹. 什么是锁定的 ...

  9. linux的文件压缩打包操作,Linux文件管理-压缩打包

    原标题:Linux文件管理-压缩打包 压缩打包介绍 windows下我们接触最多的压缩文件就是.rar格式, 但Linux有自己所特有的压缩工具. 如果希望windows和Linux互相能使用的压缩工 ...

  10. 360浏览器升级_360驱动大师v2.0.0单文件版

    软件介绍 360驱动大师,驱动安装更新软件,百万级的驱动库,驱动安装和升级一键化,无需手动操作:首创驱动体检技术,驱动精确识别匹配:支持无人值守安装驱动,启动参数-q:首创一键智能识别假显卡.假硬件. ...

最新文章

  1. 蓝桥杯-前缀表达式(java)
  2. 机房系统(二)——【数据设定 注册 】
  3. 【ArcGIS风暴】全站仪、RTK测量坐标数据在CASS和ArcGIS中展点的区别和联系(带数据)
  4. yum mysql 版本低_mysql小版本升级(yum方式)
  5. java - 求最大公约数和最小公倍数
  6. python爬虫下载重试_python爬虫多次请求超时的几种重试方法(6种)
  7. java fields是_一个快速生成R2.java中fields的插件
  8. openStack vm备份
  9. 步步为营,重构出模式(2)
  10. angularjs应用总结
  11. Vue源码后记-vFor列表渲染(3)
  12. C语言 链表 3个结点,一个关于C语言链表头结点的问题
  13. 微信公众号制作简单线上预约系统,公众号预约系统怎么做
  14. Excel-甘特图制作
  15. Vue进阶(四十四):vue 图片加载完成事件
  16. REMIX编译DeclarationError: Identifier already declared
  17. 微信小程序----返回上一页刷新或当前页刷新
  18. 重磅干货不容错过!2017云栖大会汇总资料,速来领取!
  19. ESP32 强制门户 WEB配网
  20. 基于ITIL搭建公司IT治理服务框架

热门文章

  1. SQL分类,DDL,DML,DCL
  2. 20180529 Linux配置ip 排查问题
  3. 引入springboot的两种方式以及springboot容器的引入
  4. angularjs外部文件中的控制器使用
  5. Python selenium 文件自动下载 (自动下载器)
  6. 由数据库连接池想到的----处理他人未释放的资源
  7. .NET基础知识(一)
  8. [wcf]入门.3.1
  9. Server.MapPath()用法
  10. 服务器安全设置篇(珍藏补充版中)