In-depth analysis of .NET5.0 single file release and packaging operation

Optional parameters
Property description
IncludeNativeLibrariesInSingleFile When publishing, package the dependent native binary files into a single-file application.
IncludeSymbolsInSingleFile packs the .pdb file into a single file. This option is provided for compatibility with .NET 3 single file mode. The recommended alternative method is to generate an assembly with an embedded PDB (embedded)
IncludeAllContentInSingleFile to pack all published files (except symbol files) into a single file. This option is provided for backward compatibility with the .NETCore 3.x version
.
In addition to the configuration file setting parameters, you can use the form of command line parameters, you can also set the publishing parameters in the form of configuration files, edit the project file, add configuration nodes to the file and save OK.

net5.0 linux-x64 true true For the description of RID see: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog

This is the version of RID before the publication of this article. It does not rule out that there will be a new release of .NET5.0

Other parameters
In addition to the above three optional parameters, I also found in the process of querying the document that the official also mentioned the use of other parameters, and I am not sure whether it is effective.

true true PreserveNewest true can also be set by the ExcludeFromSingleFile element, this setting will specify certain files not to be embedded in a single file.

Writing the application to be packaged
In order to more intuitively see the difference between normal release and single-file release, we specially prepared a Web application and made dependent references to the two assemblies.

Prepare the project, compile successfully, try to publish, open the PowerShel console, enter the following commands respectively

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

There are publish directories under linux-x64 and win-x64 respectively. Due to different platforms, the referenced dependencies are different. This is what we have known for a long time. Let’s look at the difference before and after packaging.

The two command statements executed above will generate packages for both Linux and Windows platforms for us. As can be seen from the figure above, before packaging, the various reference dependencies of the project are copied to the release directory, which is also In our previous program release method, after packaging, all dependent files are loaded into an executable file, which is shown as PreviewWebApplication under the Linux platform and PreviewWebApplication.exe under the Windows platform. From the perspective of packaging effects, migration will become more convenient.

Packers running
program and unpacked publisher did not have much in the way of running the packaged differences, on the Windows platform, just double-click PreviewWebApplication.exe you can run the packager, and this example is to create a WebApi If you create a program with a Razor view or other resource files, you may not be able to access the specified URL.

After the program successfully ran, we found that the packaging program did not decompress the file to disk, but directly loaded the file from the package to the memory to run; this is a huge improvement and a fundamental difference from the War file.

It should be noted that the .exe file cannot be separately copied to other places to run. You must copy the current directory of the .exe completely to run it. This involves host detection issues, which we will mention one by one below.

Cross-platform packaging file
We learned from the above example that the packaging program always generates independent packaging programs for different platforms. Why? Here is a concept involved, namely Tool Interface Standard (TIS)

Executable and Linking Format (ELF)
Common Object File Format (COFF) was introduced in 1983 and was originally used on AT&T's UNIX system. Due to various limitations of COFF, such as: the maximum number of sections is limited, section names, the length of the source files contained are limited, and symbolic debugging information cannot support the actual language. Finally, after the release of System V Release 4 (SVR4), AT&T replaced COFF with ELF.

Tool Interface Standards Committee
cites the description of the committee's specification documents: executable files and link formats were originally developed by the UNIX System Development and Release Laboratory (USL) as part of the Application Binary Interface (API). The Tool Interface Standards Committee (TIS) selected the evolving ELF standard as the portable object file. The standard applies to the 32-bit Intel architecture environment format of various operating systems. The ELF standard aims to provide developers with a set of binary interface definitions that span multiple operating environments. This will reduce the number of different interface implementations, thereby reducing the need to rewrite and compile code.

The ELF file structure is divided into three types, namely:

Name Description Description
Relocatable File contains code and data suitable for linking with other object files to create executable files or shared object files.
Executable File contains programs suitable for execution.
Shared Object File contains code and data suitable for linking in two contexts. First, the link editor can process it with other object files that can be deleted and shared again to create another object file. Second, the dynamic linker combines it with executable files and other shared objects to create process images.
Portable Executable (PE) is
in the Windows camp. Based on this COFF standard, Microsoft has innovated and developed the PE file standard

PE Format
This specification describes the structure of executable files (images) and object files under the Windows operating system family. These files are called Portable Executable (PE) and Common Object File Format (COFF) files.

It can be seen from the above two specifications that LinuX and Windows have their own file format specifications, and this specification is incompatible to a certain extent, no matter from the file structure or the way of parsing; so in .NET5.0 The packager must implement independent packagers for different platforms. The implementation of the packager is in the Microsoft.NET.HostModel library in runtime.

After understanding the ELF and PE file structure, we can read and understand the packager code.

Microsoft.NET.HostModel
You can download the source code of .NET 5.0 from github and
go to the directory:

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

The source code is not too much, you can read it directly, mainly to understand the hierarchical relationship.

The packager mainly contains three major parts, namely AppHost, Bundler, ComHost

Module description
AppHost is used for file detection when a single-file host starts. It also copies the program resources from App.dll to AppHost for backup. It has been statically linked through HostFxr and HostPolicy, and its detection logic has been transferred to HostPolicy (written by C++)
The specific implementation of the Bundler packager is mainly to embed the application and its dependencies in the AppHost, and then publish a single executable file to the specified directory
ComHost to create a ComHost containing the embedded CLSIDMap file to map the CLSID to the .NET class.
At the head of the file Bundle/Manifest.cs, we see the file structure definition of "single file program"

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, its
configuration 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]

From the above file structure, we can clearly see that the structure of a single file program is divided into three parts, namely:

Definition Description Description
Embedded file is mainly configuration file and description file, such as .deps.json, runtimeconfig.json and other files.
Bundle Header describes the structure information, type, storage location, segment, table, etc. of the entire file. Information
Manifest Entries The actual packaged file list, each file is written in segments, the executable file is separated by 16byte-prev file end position, and the ordinary file is directly written according to the prev file end position. We can
view the file header information
Use some tools to view the packaged files. Under Linux, you can use programs such as readelf/objdump to obtain the information of the PreviewWebApplication file. Under Windows, you can use tools such as PE Tools

Linux readelf read file header information

From the figure, we can see Type: DYN (Shared object file) This is a standard shared object file. The content of ELF header information is no longer expanded, and interested students can learn related content on their own.

PE Tools under Windows reads file header information

The packaged program contains 319 (Linux) and Windows (359) files. The Windows version is 84.3MB before packaging and 69.8MB after packaging. The most important thing is that there is no need to decompress it at runtime, directly from Bundle Run the file.

The third part of the file, that is, the "Manifest Entries" write code is in Bundle\Bundler.cs\AddToBundle

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; } In the member method GenerateBundle(IReadOnlyList fileSpecs) iteratively calls the AddToBundle method to complete the entity Writing of the manifest file.














// code segment

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);

}
Absorbing material: www.goodsmaterial.com

Guess you like

Origin blog.csdn.net/weixin_45032957/article/details/108484978