单文件部署和可执行文件

通过将所有依赖应用程序的文件捆绑到一个二进制文件中,为应用程序开发人员提供一个具有吸引力的选项,那就是将应用程序作为单个文件进行部署和分发。 单文件部署可用于依赖框架的部署模型独立应用程序

此部署模型从 .NET Core 3.0 开始提供,在 .NET 5 中进行了增强。 之前在 .NET Core 3.0 中,当用户运行单文件应用时,.NET Core 主机会先将所有文件提取到一个目录,然后再运行该应用程序。 .NET 5 改进了这一体验,它可直接运行代码,无需从应用中提取文件。

独立应用程序中单个文件的大小很大,因为它包含运行时和框架库。 在 .NET 6 中,可以通过发布剪裁来减小与剪裁兼容的应用程序的总大小。 单文件部署选项可与 ReadyToRunTrim 发布选项结合使用。

单个文件部署与 Windows 7 不兼容。

示例项目文件

下面是指定单文件发布的示例项目文件:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <PublishSingleFile>true</PublishSingleFile>
    <SelfContained>true</SelfContained>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <PublishReadyToRun>true</PublishReadyToRun>
  </PropertyGroup>

</Project>

这些属性具有下列函数:

  • PublishSingleFile. 启用单文件发布。 此外,还会在 dotnet build 期间启用单文件警告。
  • SelfContained. 确定应用是独立的还是依赖于框架的。
  • RuntimeIdentifier. 指定目标 OS 和 CPU 类型。 默认情况下,还会设置 <SelfContained>true</SelfContained>
  • PublishReadyToRun. 启用预先 (AOT) 编译

单文件应用始终特定于 OS 和体系结构。 需要为每个配置发布,例如 Linux x64、Linux Arm64、Windows x64 等。

单个文件中包含运行时配置文件,例如 *.runtimeconfig.json 和 *.deps.json。 如果需要额外的配置文件,可将其放在单个文件旁边。

发布单文件应用

使用 dotnet publish 命令发布单文件应用程序。

  1. <PublishSingleFile>true</PublishSingleFile> 添加到项目文件。

    此更改将针对独立发布生成一个单文件应用。 它还会在生成期间显示单文件兼容性警告。

    <PropertyGroup>
        <PublishSingleFile>true</PublishSingleFile>
    </PropertyGroup>
    
  2. 使用 dotnet publish -r <RID> 针对特定运行时标识符发布应用

    以下示例将 Windows 应用作为独立的单一文件应用程序发布。

    dotnet publish -r win-x64

    以下示例将 Linux 应用作为依赖框架的单一文件应用程序发布。

    dotnet publish -r linux-x64 --self-contained false

应在项目文件中设置 <PublishSingleFile> 以在生成期间启用文件分析,但也可以将这些选项作为 dotnet publish 参数传递:

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

有关详细信息,请参阅使用 .NET CLI 发布 .NET Core 应用

将文件排除在嵌入之外

通过设置以下元数据,可明确指定不在单文件中嵌入某些文件:

<ExcludeFromSingleFile>true</ExcludeFromSingleFile>

例如,若要将某些文件放置在发布目录中,但不将它们捆绑到文件中:

<ItemGroup>
  <Content Update="Plugin.dll">
    <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
  </Content>
</ItemGroup>

在绑定内包括 PDB 文件

可以使用下面的设置将程序集的 PDB 文件嵌入到程序集本身 (.dll)。 由于符号是程序集的一部分,因此,它们也是应用程序的一部分:

<DebugType>embedded</DebugType>

例如,将以下属性添加到程序集的项目文件,以将 PDB 文件嵌入到该程序集:

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

其他注意事项

单文件应用程序旁边将具有所有相关的 PDB 文件,并且默认情况下不会绑定。 如果要在生成的项目的程序集内包含 PDB,请将 DebugType 设置为 embedded。 请查阅在绑定内包括 PDB 文件

托管的 C++ 应组件不太适合进行单文件部署。 建议使用 C# 或其他非托管 C++ 语言编写应用程序来实现单文件兼容。

与 .NET 3.x 的输出差异

在 .NET Core 3.x 中,作为一个文件发布将生成一个文件,其中包含应用本身、依赖项和发布期间文件夹中的所有其他文件。 当应用启动时,系统将单文件应用提取到一个文件夹,并从该文件夹中运行。

从 .NET 5 开始,仅将托管的 DLL 与应用捆绑到一个可执行文件中。 当应用启动时,托管的 DLL 将被提取并加载到内存中,从而避免提取到文件夹中。 在 Windows 上,此方法意味着托管二进制文件将嵌入单文件捆绑包中,但核心运行时本身的本机二进制文件是单独的文件。

若要嵌入这些文件以进行提取并获取一个输出文件(与在 .NET Core 3.x 中类似),请将属性 IncludeNativeLibrariesForSelfExtract 设置为 true。 有关提取的详细信息,请参阅添加本机库

API 不兼容

某些 API 与单文件部署不兼容。 如果应用程序使用这些 API,可能需要进行修改。 如果使用第三方框架或包,则它们可能使用了这样的 API 并需要修改。 出现问题的最常见原因是依赖于应用程序附带的文件或 DLL 的文件路径。

下表提供了用于单文件的相关运行时库 API 详细信息。

API 注意
Assembly.CodeBase 引发 PlatformNotSupportedException
Assembly.EscapedCodeBase 引发 PlatformNotSupportedException
Assembly.GetFile 引发 IOException
Assembly.GetFiles 引发 IOException
Assembly.Location 返回空字符串。
AssemblyName.CodeBase 返回 null
AssemblyName.EscapedCodeBase 返回 null
Module.FullyQualifiedName 返回值为 <Unknown> 的字符串,或引发异常。
Marshal.GetHINSTANCE 返回 -1。
Module.Name 返回值为 <Unknown> 的字符串。

我们提供了关于修复常见问题的一些建议:

附加调试程序

在 Linux 上,可以附加到独立单文件进程或调试故障转储的唯一调试程序是有 LLDB 的 SOS

在 Windows 和 Mac 上,可以使用 Visual Studio 和 VS Code 调试故障转储。 要附加到运行独立单文件可执行文件,需要额外的文件:mscordbi.{dll,so}。

如果没有此文件,Visual Studio 可能会生成错误:“无法附加到进程。 未安装调试组件。”VS Code 可能生成错误:“未能附加到进程: 未知错误: 0x80131c3c。”

若要修复这些错误,需要将 mscordbi 复制到可执行文件旁边。 mscordbi 默认 到具有应用程序运行时 ID 的子目录中。 例如,如果使用适用于 Windows 的 dotnet CLI 并使用 -r win-x64 参数发布独立单文件可执行文件,则可执行文件将置于 bin/Debug/net5.0/win-x64/publish 中。 mscordbi.dll 的副本将存在于 bin/Debug/net5.0/win-x64 中。

包括本机库

默认情况下,单文件部署不捆绑本机库。 在 Linux 上,将运行时预链接到捆绑包中,并且仅将应用程序本机库部署到单文件应用所在的目录中。 在 Windows 上,仅预链接托管代码,而且运行时库和应用程序本机库都部署到单文件应用所在的目录中。 此方法的目的是确保提供良好的调试体验,这要求将本机文件从单文件中排除。

从 .NET 6 开始,运行时预链接到所有平台上的捆绑包中。

可以设置一个标志 IncludeNativeLibrariesForSelfExtract,从而在单文件捆绑包中包含本机库。 这在运行单文件应用程序时,这些文件将被提取到客户端计算机上的目录中。

指定 IncludeAllContentForSelfExtract 将在运行可执行文件之前提取所有文件,包括托管程序集。 此方法将保留原始 .NET Core 单文件部署行为。

注意

如果进行提取,则会在应用启动前将文件提取到磁盘:

  • 如果环境变量 DOTNET_BUNDLE_EXTRACT_BASE_DIR 设为路径,则会将文件提取到该路径下的目录。
  • 此外,如果在 Linux 或 MacOS 上运行,则会将文件提取到 $HOME/.net 下的目录。
  • 如果在 Windows 上运行,则会将文件提取到 %TEMP%/.net 下的目录。

若要防止篡改,这些目录不应由具有不同权限的用户或服务写入。 在大多数 Linux 和 MacOS 系统上,不要使用 /tmp 或 /var/tmp。

注意

在某些 Linux 环境下(例如在 systemd 下),由于未定义 $HOME,无法进行默认提取。 在这种情况下,建议显式设置 $DOTNET_BUNDLE_EXTRACT_BASE_DIR

对于 systemd,还可将服务单元文件中的 DOTNET_BUNDLE_EXTRACT_BASE_DIR 定义为 %h/.netsystemd 可为运行该服务的帐户将其正确地扩展为 $HOME/.net

[Service]
Environment="DOTNET_BUNDLE_EXTRACT_BASE_DIR=%h/.net"

在单文件应用中压缩程序集

从 .NET 6 开始,在嵌入式程序集上启用压缩后,可以创建单文件应用。 将 EnableCompressionInSingleFile 属性设置为 true。 生成的单个文件将包含所有已压缩的嵌入式程序集,这可以显著减小可执行文件的大小。

压缩会导致性能下降。 在应用程序启动时,必须将程序集解压缩到内存中,这需要花费一些时间。 在使用压缩之前,建议衡量启用压缩造成的大小更改和启动开销影响。 这种影响在不同的应用程序之间有很大的差异。

请参阅