从.NET 8开始,.NET环境变量API的一个显著限制是不能定义环境变量without a value,换句话说,不能定义empty string的值.

然而,Windows和类似Unix的平台都通过系统调用在本地支持这一点,这就是为什么直接调用后者是有意义的.

虽然使用P/Invoke声明来访问相关系统调用在Windows上按预期工作,但在类Unix平台上不能,从.NET 8开始:

  • NET不对P/Invoke中介的环境修改进行see,并继续通过Environment和启动子进程时报告old环境.

这是一个错误,还是下面的代码遗漏了什么?


示例C#控制台应用程序代码,使用8.0.101版的.NET SDK进行测试:

  • 在类Unix平台(Linux或MacOS)上运行,作为使用dotnet new console创建的项目中Program.cs文件的内容

  • *.csproj文件的<PropertyGroup>元素中添加<AllowUnsafeBlocks>true</AllowUnsafeBlocks>,以便编译项目.

using System.Diagnostics;
using System.Runtime.InteropServices;

partial class Program
{
  [LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
  private static partial int setenv(string name, string value, int overwrite);

  [LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
  private static partial IntPtr getenv(string name); // Convert to string with Marshal.PtrToStringUTF8()

  [LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
  private static partial int system(string command);

  static void ThrowSysCallError() => throw new System.ComponentModel.Win32Exception(Marshal.GetLastSystemError());

  static void Main(string[] args)
  {
    // Define / set env. var. 'FOO' with / to value 'new' 
    if (-1 == setenv("FOO", "new", 1)) ThrowSysCallError();

    Console.WriteLine(
      $"In-process value of FOO after setting it to 'new', via syscall: [{Marshal.PtrToStringUTF8(getenv("FOO"))}]"
    );

    Console.Write(
      "Value of FOO in a syscall-launched child process: "
    );
    system("echo [$FOO]");

    Console.WriteLine(
      $"In-process value of FOO per Environment.GetEnvironmentVariable(\"FOO\"): [{Environment.GetEnvironmentVariable("FOO")}]"
    );

    Console.Write(
      "Value of FOO in a .NET-launched child process: "
    );
    Process.Start(
      new ProcessStartInfo()
      {
        FileName = "sh",
        ArgumentList = { "-c", @"echo [$FOO]" },
      }
    )?.WaitForExit();

  }

}

expected的输出将是:

In-process value of FOO after setting it to 'new', via syscall: [new]
Value of FOO in a syscall-launched child process: [new]
In-process value of FOO per Environment.GetEnvironmentVariable("FOO"): [new]
Value of FOO in a .NET-launched child process: [new]

actual的输出是-请注意,.NET显然没有看到setenv()系统调用设置的FOO变量:

In-process value of FOO after setting it to 'new', via syscall: [new]
Value of FOO in a syscall-launched child process: [new]
In-process value of FOO per Environment.GetEnvironmentVariable("FOO"): []
Value of FOO in a .NET-launched child process: []

推荐答案

这是一个有趣的兔子洞,谢谢.

如果我们看一下运行时源代码,GetEnvironmentVariable被委托给特定于平台的GetEnvironmentVariableCore.

On Windows,这会导致对Kernel32的适当互操作调用(也是在Set-ing时).那里没什么可看的.


On Unix,事情看起来更有趣一点.它包含static字典高速缓存s_environment.如果我们幸运的是very,它还没有被填充,我们将得到我们想要的互操作调用getenv.

不幸的是,对GetEnvironmentVariables(复数形式)和SetEnvironmentVariable的任何调用都将填充缓存--感谢EnsureEnvironmentCached.

一旦发生这种情况,所有操作都将在缓存上进行.因此,如果anything in the runtime给他们中的任何一个打了电话(它是这样做的.在这么多地方),我们运气不佳.

这解释了我们第一个缺失的价值.拨打GetEnvironmentVariable很可能会跳过getenv呼叫,最终为我们提供一个缓存值.


但是子进程又如何呢?这难道不应该继承我们通过setenv设置的进程环境吗?

如果我们有这么幸运就好了.像以前一样,Process.Start()被委托给特定于平台的StartCore.Here it is in all its glory.第string[] envp = CreateEnvp(startInfo);章注意了如果我们继续挖掘,这envp不断得到通过更深,最终在execve syscall结束后,fork ing.

这是一个问题,因为CreateEnvp的值来自ProcessStartInfo.Environment,而ProcessStartInfo.Environment的值来自...我们的好朋友Environment.GetEnvironmentVariables(),我们从上一集知道,将为我们提供缓存值.


所有这一切带给我们的信息是,在Unix/Linux中,如果我们坚持使用Environment.[Set,Get]EnvironmentVariable个调用,应该是安全的,因为我们将被限制在使用缓存值进行操作.

但正如您在问题中所确定的那样,如果我们想要使用空变量,我们就不走运了.

Csharp相关问答推荐

C#类主构造函数中的调试参数

获取ASP.NET核心身份认证cookie名称

Microsoft. SQLServer. Types(106.1000.6)在try 从用户定义的类型检索值时引发异常

在. NET Core 8 Web API中,当为服务总线使用通用消费者时,如何防止IServiceProvider被释放或空?"

CS0103 dlibdotnet和www.example.com facerect不在上下文中

如何在C#中删除一个特殊字符,如"使用Regex"

无法解析数据库上下文的服务

是否可以使用EF—Core进行临时部分更新?

图形API基于appid列表检索多个应用程序

附加标题不起作用,而添加则起作用

如何在毛伊岛应用程序中完美地同步视图模型和视图的加载?

如何让两个.NET版本不兼容的项目对话?

C#动态设置ServerReport报表参数

VS 2022与VS 2019:如何/为什么创建额外的任务?

Azure函数-在外部启动类中生成配置时出错

为什么C#/MSBuild会自发地为不同的项目使用不同的输出路径?

从Base64转换为不同的字符串返回相同的结果

用于ASP.NET核心的最小扩展坞

如何查找Span;T&>是否包含相同顺序的其他Span<;T&>

我应该使用IMhemyCache来存储承载令牌,还是应该为Azure函数中的401个错误实施Polly重试策略?