取消

在多个可执行程序(exe)之间共享同一个私有部署的 .NET 运行时

从 .NET Core 3 开始,.NET 应用就支持独立部署自己的 .NET 运行时。可以不受系统全局安装的 .NET 运行时影响,特别适合国内这种爱优化精简系统的情况……鬼知道哪天就被优化精简了一个什么重要 .NET 运行时组件呢!然而,如果你的项目会生成多个 exe 程序,那么他们每个独立发布时,互相之间的运行时根本不互通。即便编译时使用完全相同的 .NET 框架(例如都设为 net6.0),最终也无法共用运行时文件。

那么,还有没有方法能在多个 exe 之间共享运行时而又不受制于系统安装的版本呢?有!


问题

例如,你要部署的应用程序文件夹结构是这样的(只看 exe 和文件夹,不看其他文件):

1
2
3
4
- Walterlv.Demo.exe
- Walterlv.Updater.exe
+ 1.2.1
    - Walterlv.SubProcess.exe

那么,以上这些 exe 是应该发布成“依赖框架”(Framework Dependent)还是“独立”(Self Contained)呢?

如果是“依赖框架”,那么发布完后,需要目标系统先安装有 .NET 运行时,而这个系统全局的 .NET 运行时会被各个不同的应用影响,谁知道会不会被精简或被魔改呢!如果是“独立”,那么这几个 exe 之间的运行时不会共享,每个都占用了大量的存储空间,用来放一模一样的 .NET 运行时和库文件,而且如果放一起的话还跑不起来——就算后续修复了跑不起来的 bug,上面那个多级文件夹之间共享这些 .NET 运行时文件也是一个令人头疼的事情。

官方解决方案

GitHub 上其实也有人在讨论如何共享运行时的问题:

官方给出了一个解决方案:

  • 设置 DOTNET_ROOT 环境变量

那么,我们把 runtime 文件夹放到以上根目录,然后设一下 DOTNET_ROOT 就可以让这些 .NET 的 exe 脱离系统安装的 .NET 运行了。

等等!是不是有什么问题?

  1. 这个 DOTNET_ROOT 环境变量怎么设?安装软件的时候安装包去系统里设一下吗?这一设不就跟在系统全局安装一个意思吗?
  2. 这个环境变量能设相对路径吗?肯定不行,因为不同文件夹下的 exe 如果希望共享同一个独立部署的运行时,那么相对路径肯定不同。
  3. 如果每个 exe 设自己的 DOTNET_ROOT 环境变量呢?那谁来设呢?难不成还要专门为每一个 exe 写一个非托管的启动器用来设环境变量吗?真是杀鸡用牛刀啊!

我们的解决方案

鉴于官方目前仍没有比较省心的共享独立部署 .NET 运行时的方案,我们就不得不自己操刀来干这件事情。为此,我们开发了一个 dotnetCampus.AppHost 库,其原理是允许你单独修改每个 exe 所查找的 .NET 运行时路径。

dotnetCampus.AppHost 库

使用方法

第一步:在 exe 入口项目上安装 NuGet 包:dotnetCampus.AppHost

第二步:修改项目,加入一行设置将来运行时要用的 .NET 运行时路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    <Project Sdk="Microsoft.NET.Sdk">

      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
++      <!-- 可以是相对路径,也可以是绝对路径。但既然要私有部署,当然选相对路径更好。这里瞎写一个 runtime\6.0.1 -->
++      <AppHostDotNetRoot>runtime\6.0.1</AppHostDotNetRoot>
      </PropertyGroup>

      <ItemGroup>
        <PackageReference Include="dotnetCampus.AppHost" Version="1.0.0-alpha04" />
      </ItemGroup>

    </Project>

第三步:在编译(dotnet build)完你的项目后,记得把 .NET 运行时的整个文件夹打包到你项目对应的文件夹下。

例如,对于本文一开始举例的项目,就可以指定成自己设的文件夹:

1
2
3
4
5
6
7
8
9
10
+ runtime
    + 6.0.1
        - dotnet.exe
        + host
        + shared
        + swidtag
- Walterlv.Demo.exe
- Walterlv.Updater.exe
+ 1.2.1
    - Walterlv.SubProcess.exe

这样,为 Walterlv.Demo 和 Walterlv.Updater 项目设置 AppHostDotNetRootruntime\6.0.1;为 Walterlv.SubProcess 项目设置 AppHostDotNetRoot..\runtime\6.0.1,他们就可以共用一个私有部署的运行时了。

那,这个 .NET 运行时文件夹哪里来呢?当然是官网下啦:

下载完安装后,可以在以下文件夹提取到:

  • C:\Program Files\dotnet
  • C:\Program Files(x86)\dotnet

其中,前者适用于编译成 x64 的应用程序(例如设置 PlatformTargetx64 或设置 RuntimeIdentifierwin-x64 的程序),后者适用于编译成 x86 的应用程序(例如设置 PlatformTargetx86 或设置 RuntimeIdentifierwin-x86 的程序)。

适用

目前,dotnetCampus.AppHost 支持的框架与平台如下,还在继续添加其他框架和平台的支持:

  • net6.0
    • win-x64
    • win-x86
    • win-arm
    • win-arm64
  • net5.0
    • win-x64
    • win-x86
    • win-arm
    • win-arm64
  • netcoreapp3.1
    • win-x64
    • win-x86
    • win-arm
    • win-arm64
  • netcoreapp3.0
    • win-x64
    • win-x86
    • win-arm
    • win-arm64

对于多框架项目,可以放心安装而不需要做框架判断。只有在需要生成 AppHost 的时候才会设置 .NET 运行时,不需要生成时不会报错,需要生成而无法生成时才会报错。

原理

挖个坑,稍后填。


参考资料

本文会经常更新,请阅读原文: https://blog.walterlv.com/post/share-self-deployed-dotnet-runtime-among-multiple-exes ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://blog.walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 ([email protected])

登录 GitHub 账号进行评论