C# 调用'Environment.GetEnvironmentVariable'会影响后续调用

C# 调用'Environment.GetEnvironmentVariable'会影响后续调用,c#,.net,environment-variables,C#,.net,Environment Variables,在以下在.NET 4.5.1或.NET 4.5.2上执行的代码示例(结果相同)中,当代码查询“不存在”变量时,会发生一些奇怪的情况。另一个完全存在的变量“myvar”的值是空字符串,它将通过调用GetEnvironmentVariable停止显示,但仍然可以通过整个环境的迭代看到它 纯粹使用.NET API可能无法重新创建此行为,因为它们不允许将环境变量设置为空字符串;但是本地API允许这样做 调用Environment.GetEnvironmentVariable会使另一个变量从环境中消失,或

在以下在.NET 4.5.1或.NET 4.5.2上执行的代码示例(结果相同)中,当代码查询“不存在”变量时,会发生一些奇怪的情况。另一个完全存在的变量“myvar”的值是空字符串,它将通过调用
GetEnvironmentVariable
停止显示,但仍然可以通过整个环境的迭代看到它

纯粹使用.NET API可能无法重新创建此行为,因为它们不允许将环境变量设置为空字符串;但是本地API允许这样做

调用
Environment.GetEnvironmentVariable
会使另一个变量从环境中消失,或消失一半,这感觉非常奇怪

目标框架设置为.NET2.0时的行为略有不同。直接获取
myvar
与迭代之间的不匹配在对
SetEnvironmentVariable
的本机调用之后立即发生-无需查询其他变量即可查看它

编辑:按照Hans Passant的建议(谢谢!)添加
Charset=Charset.Auto
,将.NET 4.5.x的疯狂程度降低到.NET 2.0的疯狂程度,除了修复Unicode处理外,与上一段所述完全相同。
DllImport
也缺少一个
SetLastError
参数,但这只是一个人工示例,我们知道此本机调用在Win32级别上是成功的。因此,到目前为止没有任何解释。我知道解决这个问题的几种方法,但我想更好地理解我所看到的

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        [DllImport("Kernel32.dll")]
        public static extern int SetEnvironmentVariable(string name, string value);

        static void Main(string[] args)
        {
            ShowMyVar();

            Environment.SetEnvironmentVariable("myvar", "somevalue");
            ShowMyVar();

            Environment.SetEnvironmentVariable("myvar", String.Empty);
            ShowMyVar();

            SetEnvironmentVariable("myvar", String.Empty);
            ShowMyVar();
            // once again, for good measure.
            ShowMyVar();

            Console.WriteLine("\nOkay, sane results so far.  Now let's query an unrelated non-existent variable.");

            Environment.GetEnvironmentVariable("nonexistent");
            ShowMyVar(); // Here we get weird results.

            Console.WriteLine("\nNow again, but purely through .NET APIs.");

            Environment.SetEnvironmentVariable("myvar", "somevalue");
            ShowMyVar();

            Environment.SetEnvironmentVariable("myvar", String.Empty);
            ShowMyVar();

            Environment.GetEnvironmentVariable("nonexistent");
            ShowMyVar();


        }

        private static void ShowMyVar()
        {
            if (Environment.GetEnvironmentVariable("myvar") != null)
            {
                Console.WriteLine("myvar is set to \"{0}\"", Environment.GetEnvironmentVariable("myvar"));
            }
            else
            {
                Console.WriteLine("myvar is not set");
            }
            foreach (var x in Environment.GetEnvironmentVariables().Keys)
            {
                if (x.ToString() == "myvar")
                {
                    Console.WriteLine("iteration gives value of myvar as \"{0}\"", Environment.GetEnvironmentVariable("myvar"));
                    return;
                }
            }
            Console.WriteLine("iteration over environment does not yield myvar");
        }
    }
}
原因是:

Okay, sane results so far.  Now let's query an unrelated non-existent variable.

myvar is not set
.NET调用
kernel32!GetEnvironmentVariable(“myvar”,128)
并返回0。
GetLastError()
设置为203=系统找不到输入的环境选项

这个API在这里有文档记录

步骤2调用
GetEnvironmentStringsW()
,返回指向环境字符串块的指针。 这里有MyVar

这个API在这里有文档记录

此信息是在对.NET EXE使用API监视器时使用Rohitab API监视器获得的。您可能需要在应用程序开始时暂停,然后再附加API监视器

在这种情况下,启用了监视系统服务->进程和线程->进程->内核32.dll和未选中的GetCurrentProcess()

调用的.NET源代码位于此处

Win32Native.ERROR\u ENVVAR\u NOT\u FOUND=203,这将触发返回null

[System.Security.SecuritySafeCritical]  // auto-generated
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        public static String GetEnvironmentVariable(String variable)
        {
            if (variable == null)
                throw new ArgumentNullException("variable");
            Contract.EndContractBlock();

#if !FEATURE_CORECLR
            (new EnvironmentPermission(EnvironmentPermissionAccess.Read, variable)).Demand();
#endif //!FEATURE_CORECLR

            StringBuilder blob = new StringBuilder(128); // A somewhat reasonable default size
            int requiredSize = Win32Native.GetEnvironmentVariable(variable, blob, blob.Capacity); 

            if( requiredSize == 0) {  //  GetEnvironmentVariable failed
                if( Marshal.GetLastWin32Error() == Win32Native.ERROR_ENVVAR_NOT_FOUND)
                    return null;
            }

            while (requiredSize > blob.Capacity) { // need to retry since the environment variable might be changed 
                blob.Capacity = requiredSize;
                blob.Length = 0;
                requiredSize = Win32Native.GetEnvironmentVariable(variable, blob, blob.Capacity); 
            }
            return blob.ToString();
        }
在Win32 C++中,下面的代码与您预期的一样,与.NET:< /P>不同
DWORD dwResult;
wchar_t buffer[128];

if (!SetEnvironmentVariableW(L"myvar", L""))
{
    dwResult = GetLastError();
    std::cout << "ERROR #" << dwResult << std::endl;

}

if (!GetEnvironmentVariableW(L"myvar", buffer,128))
{
    dwResult = GetLastError();
    std::cout << "ERROR #" << dwResult << std::endl;
}

std::wcout << "Buffer : '" << buffer << "'";
这将导致这些API调用/结果

 clr.dll->SetEnvironmentVariableW ( "myvar", "" )   TRUE    
       KERNELBASE.dll!RtlSetEnvironmentVar ( NULL, "myvar", 5, "", 0 )  STATUS_SUCCESS

    clr.dll->SetLastError ( ERROR_ENVVAR_NOT_FOUND )        
    clr.dll->SetLastError ( ERROR_ENVVAR_NOT_FOUND )            
    clr.dll->SetLastError ( ERROR_ENVVAR_NOT_FOUND )            
    clr.dll->SetLastError ( ERROR_ENVVAR_NOT_FOUND )            

    clr.dll!GetEnvironmentVariableW ( "myvar", <pointer 1>, 128 )   0   203 = The system could not find the environment option that was entered. 
       KERNELBASE.dll!RtlQueryEnvironmentVariable ( NULL, "myvar", 5, <pointer 1>, 128, <pointer 2> )   STATUS_SUCCESS  
显示进程环境块(PEB)以查找环境的地址

0:003> !peb
PEB at 7f40d000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            Yes
    ImageBaseAddress:         00250000
    Ldr                       77d891e0
      etc...
    Environment:  **006b0c68**
      etc..
在内存中搜索我们价值的位置

0:003> s -u **006b0c68** **006b0c68**+1000 "somevalue"
**006b1214**  0073 006f 006d 0065 0076 0061 006c 0075  s.o.m.e.v.a.l.u.
在该内存位置上设置读取断点

0:003> ba r 1 **006b1214**
0:000> du 6b1214
006b1214  "somevalue"
0:000> g
Breakpoint 1 hit
eax=00000000 ebx=006b1228 ecx=006b1214 edx=00000000 esi=00000000 edi=006b293a
eip=77ce37ad esp=003df094 ebp=003df108 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!RtlSetEnvironmentVar+0x516:
77ce37ad 83c102          add     ecx,2
0:000> du 6b1214
006b1214  ""
0:000> kv
ChildEBP RetAddr  Args to Child              
003df108 7774029e 00000000 01fe2b00 00000005 ntdll!RtlSetEnvironmentVar+0x516 (FPO: [SEH])
003df12c 005d04a3 01fe2b00 01fe1230 ed4737ca KERNELBASE!SetEnvironmentVariableW+0x47 (FPO: [Non-Fpo])
WARNING: Frame IP not in any known module. Following frames may be wrong.
003df1f8 73cc2552 0068e698 003df258 73ccf237 0x5d04a3
003df204 73ccf237 003df29c 003df248 73e18ad2 clr!CallDescrWorkerInternal+0x34
003df258 73ccff60 00000000 00000001 003df2b8 clr!CallDescrWorkerWithHandler+0x6b (FPO: [Non-Fpo])
003df2d0 73de671c 003df3cc efea5369 004e37fc clr!MethodDescCallSite::CallTargetWorker+0x152 (FPO: [Non-Fpo])
003df3f4 73de6840 01fe2a68 00000000 efea5495 clr!RunMain+0x1aa (FPO: [Non-Fpo])
003df668 73e23dc5 00000000 efea56e5 00250000 clr!Assembly::ExecuteMainMethod+0x124 (FPO: [1,149,0])
003dfb68 73e23e68 efea5b5d 00000000 00000000 clr!SystemDomain::ExecuteMainMethod+0x63c (FPO: [0,313,0])
003dfbc0 73e23f7a efea5c9d 00000000 00000000 clr!ExecuteEXE+0x4c (FPO: [Non-Fpo])
003dfc00 73e26b86 efea5ca1 00000000 00000000 clr!_CorExeMainInternal+0xdc (FPO: [Non-Fpo])
003dfc3c 7436ffcc eff9e4d3 7573980c 74360000 clr!_CorExeMain+0x4d (FPO: [Non-Fpo])
003dfc74 743ebbb7 003dfc8c 743ebbcc 00000000 mscoreei!_CorExeMain+0x10a (FPO: [0,10,4])
003dfc7c 743ebbcc 00000000 00000000 003dfc98 MSCOREE!_CorExeMain_Exported+0x77 (FPO: [Non-Fpo])
003dfc8c 7573919f 7f40d000 003dfcdc 77cda8cb MSCOREE!_CorExeMain_Exported+0x8c (FPO: [Non-Fpo])
003dfc98 77cda8cb 7f40d000 eea2cea9 00000000 KERNEL32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
003dfcdc 77cda8a1 ffffffff 77ccf663 00000000 ntdll!__RtlUserThreadStart+0x20 (FPO: [SEH])
003dfcec 00000000 743ebb40 7f40d000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
0:000> !clrstack
OS Thread Id: 0x3464 (0)
Child SP       IP Call Site
003df140 77ce37ad [InlinedCallFrame: 003df140] 
003df13c 005d04a3 *** WARNING: Unable to verify checksum for ConsoleApplicationTest.exe
DomainBoundILStubClass.IL_STUB_PInvoke(System.String, System.String)
003df140 005d0106 [InlinedCallFrame: 003df140] ConsoleApplication1.Program.SetEnvironmentVariable(System.String, System.String)
003df1a4 005d0106 ConsoleApplication1.Program.Main(System.String[]) [c:\Users\mccafferym\Documents\Visual Studio 2013\Projects\ConsoleApplicationTest\ConsoleApplicationTest\Program.cs @ 32]
003df378 73cc2552 [GCFrame: 003df378] 
0:000> g
我们现在在读回值时遇到了断点

Breakpoint 1 hit
eax=0000003d ebx=006b1212 ecx=006b1214 edx=01fe2b0a esi=006b1208 edi=00000001
eip=77cda2f7 esp=003def5c ebp=003def74 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!RtlpScanEnvironment+0xa7:
77cda2f7 75f7            jne     ntdll!RtlpScanEnvironment+0xa0 (77cda2f0) [br=0]
0:000> kv
ChildEBP RetAddr  Args to Child              
003def74 77cda138 01fe2b0a 003df000 00000080 ntdll!RtlpScanEnvironment+0xa7 (FPO: [Non-Fpo])
003defc8 77732b88 00000000 01fe2b00 00000005 ntdll!RtlQueryEnvironmentVariable+0xa7 (FPO: [SEH])
003defec 005d0682 01fe2b00 003df000 00000080 KERNELBASE!GetEnvironmentVariableW+0x39 (FPO: [Non-Fpo])
从这里跟踪字的执行,我们看到错误设置在这里:

KERNELBASE!GetEnvironmentVariableW+0x52:
77732ba9 c20c00          ret     0Ch
006f0682 8b4d98          mov     ecx,dword ptr [ebp-68h] ss:002b:0033f040=004ee698
006f0685 c6410801        mov     byte ptr [ecx+8],1         ds:002b:004ee6a0=00
006f0689 833d64a32f7400  cmp     dword ptr [clr!g_TrapReturningThreads (742fa364)],0 ds:002b:742fa364=00000000
006f0690 7407            je      006f0699                                [br=1]
006f0699 c7458000000000  mov     dword ptr [ebp-80h],0 ss:002b:0033f028=006f0682
006f06a0 8945ac          mov     dword ptr [ebp-54h],eax ss:002b:0033f054=0033f288
006f06a3 e801545e73      call    clr!StubHelpers::SetLastError (73cd5aa9)

如果此行为需要更改,我建议向Microsoft提出支持案例。

观察:您从未检查本机
SetEnvironmentVariable
的结果。你怎么知道它在每次调用中都能正常工作呢?这里没有奇怪的结果,在
Environment.GetEnvironmentVariable(“不存在”)
之后,myVar继续为空
”,我相信对于许多命令行/bat操作来说,带有
String.empty
的变量和未定义的变量是一样的(这是我用一个规则来伪装:))。这也是MS在指示用户检查变量是否存在以及检查变量是否为空时所说的:。@Luizgrs-在这一点上,我得到
myvar未设置
,并且
迭代将myvar的值设为“
”。您正在运行哪些IDE、操作系统和框架版本?我使用的是VS2013、4.5.1、Windows 7.aha,现在我遇到了问题,逐行调试没有发现问题,如果使用
ctrl+f5运行它,我就遇到了问题:)感谢您深入了解.NET正在做什么,我自己还无法设置.NET调试,只使用了参考源代码。答案中的第一个超链接指向此信息:“如果函数失败,返回值为零。如果在环境块中找不到指定的环境变量,则GetLastError返回错误\u ENVVAR\u not\u found。”。未找到的错误为230。既然“myvar”存在于环境块中,并且通过随后的Win32 API调用在环境块中找到了它,那么在这个阶段为什么不能在环境块中找到它呢?我已经添加了一些额外的解释,以及可以用来进一步探索这个问题的技术。我一定是哑巴,但是,即使在多次尝试重新阅读您的深入答案之后,我仍然不知道Microsoft代码可能认为它在做什么。无论如何,我在这里发布之前已经向Microsoft报告了可疑的错误:。我可以很好地解决这个问题,这让我很困惑。P/Invoke to
SetEnvironmentVariable
是.NET 1.1的遗物,不再需要了。我不知道“为什么”我刚才“如何”尝试简化:getEnvironmentVariable调用kernelbase.dll RtlQueryEnvironmentVariable中的函数。通常(在C++程序中)GETEnrimeValuabLW返回任何RtQuyError变量返回。然而,对于.NET,尽管RtlQueryEnvironmentVariable返回正确的值,但CLR正在添加一些导致设置此错误的代码。我很慢地看到调用
SetLastError
(CLR经常这样做)会如何影响
getEnvironmentVariable
返回的值。但后者可能有一个独立的原因。这看起来返回值不明确-0可能表示“成功返回0个字符长的字符串”或“错误,请检索错误代码”。
0:003> s -u **006b0c68** **006b0c68**+1000 "somevalue"
**006b1214**  0073 006f 006d 0065 0076 0061 006c 0075  s.o.m.e.v.a.l.u.
0:003> ba r 1 **006b1214**
0:000> du 6b1214
006b1214  "somevalue"
0:000> g
Breakpoint 1 hit
eax=00000000 ebx=006b1228 ecx=006b1214 edx=00000000 esi=00000000 edi=006b293a
eip=77ce37ad esp=003df094 ebp=003df108 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!RtlSetEnvironmentVar+0x516:
77ce37ad 83c102          add     ecx,2
0:000> du 6b1214
006b1214  ""
0:000> kv
ChildEBP RetAddr  Args to Child              
003df108 7774029e 00000000 01fe2b00 00000005 ntdll!RtlSetEnvironmentVar+0x516 (FPO: [SEH])
003df12c 005d04a3 01fe2b00 01fe1230 ed4737ca KERNELBASE!SetEnvironmentVariableW+0x47 (FPO: [Non-Fpo])
WARNING: Frame IP not in any known module. Following frames may be wrong.
003df1f8 73cc2552 0068e698 003df258 73ccf237 0x5d04a3
003df204 73ccf237 003df29c 003df248 73e18ad2 clr!CallDescrWorkerInternal+0x34
003df258 73ccff60 00000000 00000001 003df2b8 clr!CallDescrWorkerWithHandler+0x6b (FPO: [Non-Fpo])
003df2d0 73de671c 003df3cc efea5369 004e37fc clr!MethodDescCallSite::CallTargetWorker+0x152 (FPO: [Non-Fpo])
003df3f4 73de6840 01fe2a68 00000000 efea5495 clr!RunMain+0x1aa (FPO: [Non-Fpo])
003df668 73e23dc5 00000000 efea56e5 00250000 clr!Assembly::ExecuteMainMethod+0x124 (FPO: [1,149,0])
003dfb68 73e23e68 efea5b5d 00000000 00000000 clr!SystemDomain::ExecuteMainMethod+0x63c (FPO: [0,313,0])
003dfbc0 73e23f7a efea5c9d 00000000 00000000 clr!ExecuteEXE+0x4c (FPO: [Non-Fpo])
003dfc00 73e26b86 efea5ca1 00000000 00000000 clr!_CorExeMainInternal+0xdc (FPO: [Non-Fpo])
003dfc3c 7436ffcc eff9e4d3 7573980c 74360000 clr!_CorExeMain+0x4d (FPO: [Non-Fpo])
003dfc74 743ebbb7 003dfc8c 743ebbcc 00000000 mscoreei!_CorExeMain+0x10a (FPO: [0,10,4])
003dfc7c 743ebbcc 00000000 00000000 003dfc98 MSCOREE!_CorExeMain_Exported+0x77 (FPO: [Non-Fpo])
003dfc8c 7573919f 7f40d000 003dfcdc 77cda8cb MSCOREE!_CorExeMain_Exported+0x8c (FPO: [Non-Fpo])
003dfc98 77cda8cb 7f40d000 eea2cea9 00000000 KERNEL32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
003dfcdc 77cda8a1 ffffffff 77ccf663 00000000 ntdll!__RtlUserThreadStart+0x20 (FPO: [SEH])
003dfcec 00000000 743ebb40 7f40d000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
0:000> !clrstack
OS Thread Id: 0x3464 (0)
Child SP       IP Call Site
003df140 77ce37ad [InlinedCallFrame: 003df140] 
003df13c 005d04a3 *** WARNING: Unable to verify checksum for ConsoleApplicationTest.exe
DomainBoundILStubClass.IL_STUB_PInvoke(System.String, System.String)
003df140 005d0106 [InlinedCallFrame: 003df140] ConsoleApplication1.Program.SetEnvironmentVariable(System.String, System.String)
003df1a4 005d0106 ConsoleApplication1.Program.Main(System.String[]) [c:\Users\mccafferym\Documents\Visual Studio 2013\Projects\ConsoleApplicationTest\ConsoleApplicationTest\Program.cs @ 32]
003df378 73cc2552 [GCFrame: 003df378] 
0:000> g
Breakpoint 1 hit
eax=0000003d ebx=006b1212 ecx=006b1214 edx=01fe2b0a esi=006b1208 edi=00000001
eip=77cda2f7 esp=003def5c ebp=003def74 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!RtlpScanEnvironment+0xa7:
77cda2f7 75f7            jne     ntdll!RtlpScanEnvironment+0xa0 (77cda2f0) [br=0]
0:000> kv
ChildEBP RetAddr  Args to Child              
003def74 77cda138 01fe2b0a 003df000 00000080 ntdll!RtlpScanEnvironment+0xa7 (FPO: [Non-Fpo])
003defc8 77732b88 00000000 01fe2b00 00000005 ntdll!RtlQueryEnvironmentVariable+0xa7 (FPO: [SEH])
003defec 005d0682 01fe2b00 003df000 00000080 KERNELBASE!GetEnvironmentVariableW+0x39 (FPO: [Non-Fpo])
KERNELBASE!GetEnvironmentVariableW+0x52:
77732ba9 c20c00          ret     0Ch
006f0682 8b4d98          mov     ecx,dword ptr [ebp-68h] ss:002b:0033f040=004ee698
006f0685 c6410801        mov     byte ptr [ecx+8],1         ds:002b:004ee6a0=00
006f0689 833d64a32f7400  cmp     dword ptr [clr!g_TrapReturningThreads (742fa364)],0 ds:002b:742fa364=00000000
006f0690 7407            je      006f0699                                [br=1]
006f0699 c7458000000000  mov     dword ptr [ebp-80h],0 ss:002b:0033f028=006f0682
006f06a0 8945ac          mov     dword ptr [ebp-54h],eax ss:002b:0033f054=0033f288
006f06a3 e801545e73      call    clr!StubHelpers::SetLastError (73cd5aa9)