Loading [MathJax]/jax/input/TeX/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何让 .NET 程序脱离系统安装的 .NET 运行时独立运行?除了 Self-Contained 之外还有更好方法!谈 dotnetCampus.AppHost 的工作原理

如何让 .NET 程序脱离系统安装的 .NET 运行时独立运行?除了 Self-Contained 之外还有更好方法!谈 dotnetCampus.AppHost 的工作原理

作者头像
walterlv
发布于 2023-10-23 09:48:48
发布于 2023-10-23 09:48:48
1K00
代码可运行
举报
运行总次数:0
代码可运行

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

dotnetCampus.AppHost 就可以帮助你完成多个 exe 共享独立部署的 .NET 环境的功能。其原理是允许你单独修改每个 exe 所查找的 .NET 运行时路径。那么本文带你详细了解其原理和实现。

原代码解读

首先记得先把仓库拉下来:

如果有产品化需求,记得切到对应的 Tag(例如 v6.0.1 等)。

.NET 的 AppHost 负责查找 .NET 运行时并将其运行起来,而 AppHost 相关的代码在 src\native\corehost 文件夹中。这些文件夹中的代码是以 CMakeList 方式管理的零散 C++ 文件(和头文件),可以使用 CMake 里的 cmake-gui 工具来打开、管理和编译。不过我依然更喜欢使用 Visual Studio 来打开和编辑这些文件。Visual Studio 支持 CMake 工作区,详见 CMake projects in Visual Studio。不过这些 CMakeList.txt 并没有针对 Visual Studio 做较好的适配,所以实际上个人认为最好的视图方式是 Visual Studio 的文件夹视图,或者 Visual Studio Code。

通过阅读 corehost 文件夹内各个 C++ 源代码文件,我们可以找到运行时寻找 .NET 运行时路径的功能在 fxr_resolver.cpp 文件中实现,具体是 fxr_resolver::try_get_path 函数。关键代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// For apphost and libhost, root_path is expected to be a directory.
// For libhost, it may be empty if app-local search is not desired (e.g. com/ijw/winrt hosts, nethost when no assembly path is specified)
// If a hostfxr exists in root_path, then assume self-contained.
if (root_path.length() > 0 && library_exists_in_dir(root_path, LIBFXR_NAME, out_fxr_path))
{
    trace::info(_X("Resolved fxr [%s]..."), out_fxr_path->c_str());
    out_dotnet_root->assign(root_path);
    return true;
}

// For framework-dependent apps, use DOTNET_ROOT_<ARCH>
pal::string_t default_install_location;
pal::string_t dotnet_root_env_var_name;
if (get_dotnet_root_from_env(&dotnet_root_env_var_name, out_dotnet_root))
{
    trace::info(_X("Using environment variable %s=[%s] as runtime location."), dotnet_root_env_var_name.c_str(), out_dotnet_root->c_str());
}
else
{
    if (pal::get_dotnet_self_registered_dir(&default_install_location) || pal::get_default_installation_dir(&default_install_location))
    {
        trace::info(_X("Using global installation location [%s] as runtime location."), default_install_location.c_str());
        out_dotnet_root->assign(default_install_location);
    }
    else
    {
        trace::error(_X("A fatal error occurred, the default install location cannot be obtained."));
        return false;
    }
}

解读:

  1. root_path 参数的含义为 .NET 程序的入口 dll 所在路径。一开始先判断一下 .NET 程序入口 dll 所在文件夹内有没有一个名为 hostfxr.dll 的文件,如果存在那么直接返回找到,就在应用程序所在文件夹;如果没有找到,就继续后续执行。
  2. 试图从环境变量中找一个名为 DOTNET_ROOT 的变量并取得其值,然后将其转换为绝对路径。如果找到了这个变量并且路径存在,则使用此文件夹;如果没有定义或文件夹不存在,则继续后续执行。
  3. 试图从全局安装的路径(C:\Program Files\dotnetC:\Program Files(x86)\dotnet 路径下找 .NET 运行时,如果找到则使用此文件夹;如果没有找到,则返回错误,要求用户下载 .NET 运行时。

新代码修改

那么,我们的改动便可以从这里开始。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
--  // For framework-dependent apps, use DOTNET_ROOT_<ARCH>
    pal::string_t default_install_location;
    pal::string_t dotnet_root_env_var_name;
++  if (is_dotnet_root_enabled_for_execution(out_dotnet_root))
++  {
++      // For apps that using dotnetCampus.AppHost, use the EMBED_DOTNET_ROOT placeholder.
++      trace::info(_X("Using embedded dotnet_root [%s] as runtime location."), out_dotnet_root->c_str());
++  }
--  if (get_dotnet_root_from_env(&dotnet_root_env_var_name, out_dotnet_root))
++  else if (get_dotnet_root_from_env(&dotnet_root_env_var_name, out_dotnet_root))
    {
++      // For framework-dependent apps, use DOTNET_ROOT_<ARCH>
        trace::info(_X("Using environment variable %s=[%s] as runtime location."), dotnet_root_env_var_name.c_str(), out_dotnet_root->c_str());
    }
    else
    {
        if (pal::get_dotnet_self_registered_dir(&default_install_location) || pal::get_default_installation_dir(&default_install_location))
        {
            trace::info(_X("Using global installation location [%s] as runtime location."), default_install_location.c_str());
            out_dotnet_root->assign(default_install_location);
        }
        else
        {
            trace::error(_X("A fatal error occurred, the default install location cannot be obtained."));
            return false;
        }
    }

解读:

  1. 我添加了一个名为 is_dotnet_root_enabled_for_execution 的函数调用,试图找一下编译时确定的 .NET 运行时路径。如果发现编译时设过此路径,并且此文件夹在运行时存在,那么将此文件夹改为绝对路径后继续后续执行;如果没设过或路径不存在,则使用其他的方式来确定 .NET 运行时的路径。

而这个 is_dotnet_root_enabled_for_execution 函数,我的实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#if defined(FEATURE_APPHOST) || defined(FEATURE_LIBHOST)
#define EMBED_DOTNET_ROOT_HI_PART_UTF8 "622e5d2d0f48bd3448f713291ed3f86d" // SHA-256 of "DOTNET_ROOT" in UTF-8
#define EMBED_DOTNET_ROOT_LO_PART_UTF8 "f2f05ca222e95084f222207c5c348eea"
#define EMBED_DOTNET_ROOT_FULL_UTF8    (EMBED_DOTNET_ROOT_HI_PART_UTF8 EMBED_DOTNET_ROOT_LO_PART_UTF8) // NUL terminated

bool is_dotnet_root_enabled_for_execution(pal::string_t* dotnet_root)
{
    constexpr int EMBED_SZ = sizeof(EMBED_DOTNET_ROOT_FULL_UTF8) / sizeof(EMBED_DOTNET_ROOT_FULL_UTF8[0]);
    constexpr int EMBED_MAX = (EMBED_SZ > 1025 ? EMBED_SZ : 1025); // 1024 DLL name length, 1 NUL

    // Contains the EMBED_DOTNET_ROOT_FULL_UTF8 value at compile time or the managed DLL name replaced by "dotnet build".
    // Must not be 'const' because std::string(&embed[0]) below would bind to a const string ctor plus length
    // where length is determined at compile time (=64) instead of the actual length of the string at runtime.
    static char embed[EMBED_MAX] = EMBED_DOTNET_ROOT_FULL_UTF8;     // series of NULs followed by embed hash string

    static const char hi_part[] = EMBED_DOTNET_ROOT_HI_PART_UTF8;
    static const char lo_part[] = EMBED_DOTNET_ROOT_LO_PART_UTF8;

    if (!pal::clr_palstring(embed, dotnet_root))
    {
        trace::error(_X("The dotnet_root value could not be retrieved from the executable image."));
        return false;
    }

    // Since the single static string is replaced by editing the executable, a reference string is needed to do the compare.
    // So use two parts of the string that will be unaffected by the edit.
    size_t hi_len = (sizeof(hi_part) / sizeof(hi_part[0])) - 1;
    size_t lo_len = (sizeof(lo_part) / sizeof(lo_part[0])) - 1;

    std::string binding(&embed[0]);
    if ((binding.size() >= (hi_len + lo_len)) &&
        binding.compare(0, hi_len, &hi_part[0]) == 0 &&
        binding.compare(hi_len, lo_len, &lo_part[0]) == 0)
    {
        trace::info(_X("This executable does not binding to dotnet_root yet. The binding value is: '%s'"), dotnet_root->c_str());
        return false;
    }

    trace::info(_X("The dotnet_root binding to this executable is: '%s'"), dotnet_root->c_str());
    if (pal::realpath(dotnet_root))
    {
        return true;
    }
    trace::info(_X("Did not find binded dotnet_root directory: '%s'"), dotnet_root->c_str());
    return false;
}
#endif // FEATURE_APPHOST

解读:

  1. 随便生成了一段字符串 622e5d2d0f48bd3448f713291ed3f86df2f05ca222e95084f222207c5c348eea,然后定义三个宏,一个是前一半,一个是后一半,一个是整个字符串。
    • 这个字符串是一个占位符,将来在编译最终 exe 时,会在 exe 的二进制文件中搜索这一字符串,将其替换为我们需要的 .NET 运行时路径(如在这篇文章中我们设置成的 runtime\6.0.1)。
    • 这是一段随便生成的字符串,是通过将字符串 DOTNET_ROOT 进行 UTF-8 编码后 SHA-256 哈希得到的,你也可以用其他任何方法得到,只要避免整个 exe 不会碰巧遇到一模一样的字节序列就好。
    • 我们分成了前一半后一半和整条,是因为我们未来编译时只替换整条的,一半的不会被替换。于是可以通过在运行时比较整条的是否刚好等于两个半条的拼接来判定是否在编译时设置过 .NET 运行时路径。
  2. 我们使用 pal::clr_palstring 将被替换的字符串进行 UTF-8 到 Unicode 的转码,这样就可以在运行时直接使用了。
  3. 随后,我们比较一下前面第 1 条所说的是否设置过 .NET 运行时路径,只有设置过才会用,否则使用默认的运行时查找逻辑。
  4. 最后,检查一下路径是否存在,将相对路径转换为绝对路径。(这个步骤是通过实测发现有问题才加的,如果只是相对路径,会有一部分逻辑正常执行另一部分挂掉)。

改完后,整个项目编译一下,以得到我们想要的 apphost.exe 和 singleapphost.exe。参考:

配合 NuGet 包编译

前面的修改,只是为了得到 apphost.exe,我们还没有让这个 apphost.exe 工作起来呢。

为了能工作起来,我们需要做一个像下面这样的 NuGet 包:

其中:

  • build
    • 这个是预定义的文件夹,必须使用此名称。
    • 用来修改编译过程(这是 NuGet 包里用来编译的入口点),以便能将 AppHost 的修改加入到编译环节。
  • template
    • 这个名字是随便取的,会在 build 里用到。
    • 替换占位符时会替换这里的 apphost.exe 文件。
  • tools
    • 这个名字是随便取的,会在 build 里用到(虽然 tools 本来也是有特殊用途,不过我们没用到)。
    • 用来替换占位符的可执行程序就在这里。

而为了得到这样的 NuGet 包,我们这样来设计项目:

其中:

  • Assets 文件夹里的内容会近似原封不动地放到目标 NuGet 包里(唯一变化的,就是在放进 NuGet 包之前会自动把 Build.props 和 Build.targets 的名字改一下)。
  • Patches 文件夹里存放的是对 dotnet/runtime: .NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps. 项目的修改补丁,以便后续修改 dotnet runtime 仓库时能随时使用这里的补丁继续。
  • SourceProject 文件夹无需关心,是安装的源码包引来的。
  • 其他所有文件都是用来编译生成替换占位符程序的。

为了能让这样的项目结构生成前面所述的 NuGet 包,我们需要修改项目的 csproj 文件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFrameworks>net6.0;net5.0;net45</TargetFrameworks>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <RootNamespace>dotnetCampus.AppHosting</RootNamespace>
    <IncludeBuildOutput>false</IncludeBuildOutput>
  </PropertyGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
    <PackageReference Include="dotnetCampus.CommandLine.Source" Version="3.3.0" PrivateAssets="All" />
    <PackageReference Include="dotnetCampus.MSBuildUtils.Source" Version="1.1.0" PrivateAssets="All" />
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)' != 'net6.0'">
    <Compile Remove="**\*.cs" />
    <Compile Include="Program.cs" />
  </ItemGroup>

  <!-- 引入包文件用于打包。 -->
  <Target Name="_IncludeAllDependencies" BeforeTargets="_GetPackageFiles">
    <ItemGroup>
      <None Include="Assets\build\Build.props" Pack="True" PackagePath="build\$(PackageId).props" />
      <None Include="Assets\build\Build.targets" Pack="True" PackagePath="build\$(PackageId).targets" />
      <None Include="Assets\template\**" Pack="True" PackagePath="template" />
      <None Include="$(OutputPath)net6.0\**" Pack="True" PackagePath="tools" />
    </ItemGroup>
  </Target>

其中,重要的部分为:

  1. TargetFrameworks:虽然我们只生成 net6.0 框架的替换 AppHost 占位符程序,但为了能让 NuGet 包能装在多框架项目中,我们需要添加其他框架的支持(虽然这些框架可能甚至都没有 AppHost 机制)。
    • 为此,我们需要在依赖其他 NuGet 包时使用 Condition="'$(TargetFramework)' == 'net6.0'" 判断,只在 net6.0 项目中用包。同时,还需要在非 net6.0 项目中移除几乎所有的源代码,避免其他框架限制我们的代码编写(例如 net45 框架会限制我们使用 .NET 6 的新 API)。
  2. GeneratePackageOnBuild 设为 true 以生成 NuGet 包;IncludeBuildOutput 以避免将生成的文件输出到 NuGet 包中(因为我们有多个框架,而且除了 net6.0 都是垃圾文件,所以要避免默认生成进去;我们随后手工放入到 NuGet 包中)。
  3. 通过名为 _IncludeAllDependencie 的 Target,我们将 Assets 文件夹中的所有文件打入 NuGet 包中,同时改一下 Build.props 和 Build.targets 文件的名字。然后把前面忽略的输出文件,将其 net6.0 框架部分手工打入 NuGet 包中。

那么剩下的,就是 Build.props / Build.targets 和占位符替换程序的部分了。

源码在这里:dotnet-campus/dotnetCampus.AppHost

Build.props 和 Build.targets 部分如果有问题,可以留言或者私信沟通;而占位符替换程序的本质就是读取文件并替换其一部分二进制序列,会比较简单。

参考资料

本文会经常更新,请阅读原文: https://cloud.tencent.com/developer/article/2350198 ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-01-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
dotnet 9 通过 AppHostRelativeDotNet 指定自定义的运行时路径
在 2022 时,我写了一个提案,允许应用程序自定义使用的 dotnet 运行时文件夹路径。详细请看 https://github.com/dotnet/runtime/issues/64430
郑子铭
2025/07/20
680
dotnet 9 通过 AppHostRelativeDotNet 指定自定义的运行时路径
在多个可执行程序(exe)之间共享同一个私有部署的 .NET 运行时
从 .NET Core 3 开始,.NET 应用就支持独立部署自己的 .NET 运行时。可以不受系统全局安装的 .NET 运行时影响,特别适合国内这种爱优化精简系统的情况……鬼知道哪天就被优化精简了一个什么重要 .NET 运行时组件呢!然而,如果你的项目会生成多个 exe 程序,那么他们每个独立发布时,互相之间的运行时根本不互通。即便编译时使用完全相同的 .NET 框架(例如都设为 net6.0),最终也无法共用运行时文件。
walterlv
2023/10/23
7250
dotnet core 应用是如何跑起来的 通过AppHost理解运行过程
在 dotnet 的输出路径里面,可以看到有一个有趣的可执行文件,这个可执行文件是如何在框架发布和独立发布的时候,找到 dotnet 程序的运行时的,这个可执行文件里面包含了哪些内容
林德熙
2020/12/03
1.1K0
dotnet core 应用是如何跑起来的 通过AppHost理解运行过程
dotnet 9 通过 AppHostRelativeDotNet 指定自定义的运行时路径
进行框架依赖发布的时候,应用程序需要有 dotnet runtime 运行时才能跑起来。在 dotnet 9 之前,通常都是需要安装到系统的 Program File 文件夹下的全局 dotnet 运行时的支持。在 dotnet 9 时,引入了 AppHostRelativeDotNet 机制,允许开发者自定义依赖框架发布的应用使用的 dotnet 运行时路径
林德熙
2025/07/04
1000
记将一个大型客户端应用项目迁移到 dotnet 6 的经验和决策
在经过了两年的准备,以及迁移了几个应用项目积累了让我有信心的经验之后,我最近在开始将团队里面最大的一个项目,从 .NET Framework 4.5 迁移到 .NET 6 上。这是一个从 2016 时开始开发,最多有 50 多位开发者参与,代码的 MR 数量过万,而且整个团队没有一个人能说清楚项目里面的所有功能。此项目引用了团队内部的大量的基础库,有很多基础库长年不活跃。此应用项目当前也有近千万的用户量,迁移的过程也需要准备很多补救方法。如此复杂的一个项目,自然需要用到很多黑科技才能完成到 .NET 6 的落地。本文将告诉大家这个过程里,我踩到的坑,以及学到的知识,和为什么会如此做
林德熙
2022/08/12
1.8K0
记将一个大型客户端应用项目迁移到 dotnet 6 的经验和决策
让.NET应用发布文件夹更清爽!
大家都知道,在.NET 发布时,将会在输出的 publish 文件夹包含所需的依赖。在 .NET Core 开始,引入了 AppHost 的概念,即使是单个程序集,也需要独立的 Exe 可执行文件带上实际包含 Main 函数的 dll 文件。
郑子铭
2022/03/22
8180
NetBeauty2:让你的.NET项目输出目录更清爽
NetBeauty2是一个开源的.NET依赖库整理工具,它的主要作用是在.NET项目独立发布时,对输出目录进行整理和优化。通过NetBeauty2,开发者可以轻松地将.NET运行时和依赖的dll文件移动到指定的目录,使得项目的输出目录更加清晰、易于管理。
沙漠尽头的狼
2024/03/21
2370
NetBeauty2:让你的.NET项目输出目录更清爽
修改 .NET 运行时、框架和库,从编译 dotnet runtime 仓库开始
.NET 以 MIT 协议开源,于是任何人都可以尝试对其进行一丢丢的修改以实现一些原本很难实现的功能,例如在多个可执行程序(exe)之间共享同一个私有部署的 .NET 运行时。然而,对其的修改得能够编译生成期望的文件才行。本文介绍一下如何编译 dotnet/runtime 仓库,日常使用非常简单,所以如果只是轻微修改的话,本文大概就够了。
walterlv
2023/10/23
4900
修改 .NET 运行时、框架和库,从编译 dotnet runtime 仓库开始
SourceYard 制作源代码包 控制台项目WPF 程序调试
本文带大家走进SourceYard开发之旅 在项目开发中,将一个大的项目拆为多个小项目解耦,减少模块之间的耦合。因为如果将代码放在一起,即使有团队的约束,但只要能写出的代码就会有小伙伴写出,很快就发现各个模块耦合的代码很多。但是对一个项目的拆分会让拆分出来的每一个项目都编译出一个 dll 增加运行文件的启动时间。 在开发中,常常会用到很多工具类,这些小轮子很多的功能基本就只有一个类,如何对这些小轮子进行管理?通过复制代码还是通过 Nuget 管理?
林德熙
2019/03/13
1.2K0
SourceYard 制作源代码包
            控制台项目WPF 程序调试
如何编译、修改和调试 dotnet runtime 仓库中的 apphost nethost comhost ijwhost
.NET 以 MIT 协议开源,于是任何人都可以尝试对其进行一丢丢的修改以实现一些原本很难实现的功能,例如在多个可执行程序(exe)之间共享同一个私有部署的 .NET 运行时。在这个例子中,我们修改了 AppHost 添加了一个可以定制 .NET 运行时路径的功能,这就需要我们能编译、修改和调试 dotnet/runtime 仓库里的 apphost 部分。
walterlv
2023/10/23
5630
.NET 将多个程序集合并成单一程序集的 4+3 种方法
编写 .NET 程序的时候,我们经常会在项目的输出目录下发现一大堆的文件。除了我们项目自己生成的程序集之外,还能找到这个项目所依赖的一大堆依赖程序集。有没有什么方法可以把这些依赖和我们的程序集合并到一起呢?
walterlv
2023/10/22
1.6K0
.NET 将多个程序集合并成单一程序集的 4+3 种方法
探索 dotnet core 为何在 Windows7 系统需要补丁的原因
在一些 Windows 7 系统上,根据 dotnet 官方文档,需要安装上 KB2533623 补丁,才能运行 dotnet core 或 .NET 5 等应用。尽管非所有的设备都需要安装此,但这也让应用的分发不便,安装包上都需要带上补丁给用户安装。此补丁同时也要求安装完成之后重启系统,这对用户端来说,也是较不方便。本文来聊聊为什么 dotnet core 一系的框架依赖于此补丁
沙漠尽头的狼
2024/01/15
2900
探索 dotnet core 为何在 Windows7 系统需要补丁的原因
.NET Core部署中你不了解的框架依赖与独立部署
框架依赖的部署:顾名思义,依赖框架的部署 (FDD) 依赖目标系统上存在共享系统级版本的 .NET Core。 由于已存在 .NET Core,因此应用在 .NET Core 安装程序间也是可移植的。 应用仅包含其自己的代码和任何位于 .NET Core 库外的第三方依赖项。 FDD 包含可通过在命令行中使用 dotnet 实用程序启动的 .dll 文件。 例如,dotnet app.dll 就可以运行一个名为 app 的应用程序。 对于 FDD,仅部署应用程序和第三方依赖项。 不需要部署 .NET Core,因为应用将使用目标系统上存在的 .NET Core 版本。 这是定目标到 .NET Core 的 .NET Core 和 ASP.NET Core 应用程序的默认部署模型。
依乐祝
2018/10/10
2.2K0
尝试 IIncrementalGenerator 进行增量 Source Generator 生成代码
在加上热重载时,源代码生成 Source Generator 的默认行为会让 Visual Studio 有些为难,其原因是热重载会变更代码,变更代码触发代码生成器更新代码,代码生成器更新的代码说不定又会有某些逗比逻辑再次触发热重载。于是就会发现在某些复杂的项目下,开启热重载之后,在编辑并继续界面将会等非常久,甚至再也无法继续。为了解决这个问题,大聪明设计了 Incremental Generators 机制,此 Incremental Generators 机制和 Source Generator 不冲突,被设计用来解决热重载的源代码生成性能问题,本文将告诉大家此新的 API 的入门级使用
林德熙
2023/04/07
6280
.NET 环境变量
在本文中,您将了解 .NET SDK、.NET CLI 和 .NET 运行时使用的环境变量。某些环境变量由 .NET 运行时使用,而其他环境变量仅由 .NET SDK 和 .NET CLI 使用。一些环境变量被所有人使用。 .NET 运行时环境变量 DOTNET_SYSTEM_NET_HTTP_* 有几个全局 HTTP 环境变量设置: DOTNET_SYSTEM_NET_HTTP_ENABLEACTIVITYPROPAGATION 指示是否为全局 HTTP 设置启用诊断处理程序的活动传播。 DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2SUPPORT 设置为falseor 时0,禁用 HTTP/2 支持,默认情况下启用。 DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP3SUPPORT 设置为true或 时1,启用 HTTP/3 支持,默认情况下禁用。 DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2FLOWCONTROL_DISABLEDYNAMICWINDOWSIZING 当设置为falseor 时0,覆盖默认值并禁用 HTTP/2 动态窗口缩放算法。 DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_FLOWCONTROL_MAXSTREAMWINDOWSIZE 默认为 16 MB。覆盖时,HTTP/2 流接收窗口的最大大小不能小于 65,535。 DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_FLOWCONTROL_STREAMWINDOWSCALETHRESHOLDMULTIPLIER 默认为 1.0。当被覆盖时,更高的值会导致更短的窗口但下载速度更慢。不能小于0。 DOTNET_SYSTEM_GLOBALIZATION_* DOTNET_SYSTEM_GLOBALIZATION_INVARIANT:请参阅设置不变模式。 DOTNET_SYSTEM_GLOBALIZATION_PREDEFINED_CULTURES_ONLY: 指定是否只加载预定义的文化。 DOTNET_SYSTEM_GLOBALIZATION_APPLOCALICU:指示是否使用应用程序本地Unicode 国际组件(ICU)。有关更多信息,请参阅App-local ICU。 设置不变模式 应用程序可以通过以下任何一种方式启用不变模式: 在项目文件中: XML 复制 <PropertyGroup> <InvariantGlobalization>true</InvariantGlobalization> </PropertyGroup> 在runtimeconfig.json文件中: JSON 复制 { "runtimeOptions": { "configProperties": { "System.Globalization.Invariant": true } } } 通过将环境变量值设置DOTNET_SYSTEM_GLOBALIZATION_INVARIANT为true或1。 重要的 在项目文件或runtimeconfig.json 中设置的值比环境变量具有更高的优先级。 有关详细信息,请参阅.NET 全球化不变模式。 DOTNET_SYSTEM_GLOBALIZATION_USENLS 这仅适用于 Windows。要使全球化使用国家语言支持 (NLS),请将其设置DOTNET_SYSTEM_GLOBALIZATION_USENLS为true或1。DOTNET_SYSTEM_GLOBALIZATION_USENLS要不使用它,请设置为false或0。 DOTNET_SYSTEM_NET_SOCKETS_* 本节重点介绍两个System.Net.Sockets环境变量: DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS DOTNET_SYSTEM_NET_SOCKETS_THREAD_COUNT 套接字延续从事件线程分派到System.Threading.ThreadPool。这避免了阻塞事件处理的延续。要允许继续直接在事件线程上运行,请设置DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS为1. 默认情况下它是禁用的。 笔记 如果有昂贵的工作最终会占用 IO 线程的时间超过所需时间,则此设置会使性能变差。测试以确保此设置有助于提高性能。 使用 TechEmpower 基准测试,在非常高的负载下生成大量小型套接字读取和写入,单个套接字引擎能够
呆呆
2022/01/05
2.6K0
CoreHook:基于.NET Core运行时实现的Windows HOOK库
今天为大家介绍一款基于.NET Core运行时实现的Windows HOOK库,CoreHook。
FB客服
2019/06/18
1.9K0
如何给 CI CD 服务器搭建上 .NET 5 构建和运行环境
在 2020.11.10 官方正式发布了 .NET 5 正式版。而我花了一周的时间进行踩坑和预研,本文将告诉大家如何在自动化服务器上搭建 dotnet 5 的构建和运行环境
林德熙
2020/11/23
1.1K0
如何给 CI CD 服务器搭建上 .NET 5 构建和运行环境
.NET Core系列 : 1、.NET Core 环境搭建和命令行CLI入门
2016年6月27日.NET Core & ASP.NET Core 1.0在Redhat峰会上正式发布,社区里涌现了很多文章,我也计划写个系列文章,原因是.NET Core的入门门槛相当高,很有必要写个深入浅出的系列文章,本节内容帮助你入门。我将可能用Windows做开发环境,也可能用Linux/Mac,但是所有的dotnet CLI命令都是跨平台的,我们在windows/Linux/mac平台上开发跨平台的应用。 安装.NET Core .NET Core 包括.NET Core Runtime 和 .
张善友
2018/01/22
2K0
使用 Source Generator 在编译你的 .NET 项目时自动生成代码
本文以 dotnetCampus.Ipc 项目为例,来说明如何为一个现成的 .NET 类库添加自动生成代码的功能。这是一个在本机内进行进程间通信的库,在你拥有一个 IPC 接口和对应的实现之后,本库还会自动帮你生成通过 IPC 代理访问的代码。由于项目加了 Roslyn 的 SourceGenerator 功能,所以当你安装了 dotnetCampus.Ipc NuGet 包 后,这些代码将自动生成,省去了手工编写的费神。
walterlv
2023/10/23
8880
使用 Source Generator 在编译你的 .NET 项目时自动生成代码
制作一个能构建 dotnet AOT 的 gitlab ruuner 的 Debian docker 镜像
我的需求是需要有一个能够构建出 dotnet 的 AOT 包的环境,要求这个环境能解决 glibc 兼容依赖的问题,能打出来 x64 和 arm64 的 AOT 的包,且能够运行 gitlab runner 对接自动构建
林德熙
2024/04/29
2910
制作一个能构建 dotnet AOT 的 gitlab ruuner 的 Debian docker 镜像
推荐阅读
相关推荐
dotnet 9 通过 AppHostRelativeDotNet 指定自定义的运行时路径
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验