2014-11-18 1 views
3

.NET 4.5.1 또는 .NET 4.5.2 (동일한 결과)에서 실행 된 다음 코드 샘플에서는 코드가 "존재하지 않는"변수를 쿼리 할 때 이상한 상황이 발생합니다. 값이 빈 문자열 인 완벽하게 존재하는 또 다른 변수 "myvar"는 GetEnvironmentVariable에 대한 호출을 통해 볼 수 없지만 전체 환경의 반복을 통해 여전히 볼 수 있습니다.`Environment.GetEnvironmentVariable` 호출은 후속 호출에 영향을줍니다.

이 동작은 환경 변수를 빈 문자열로 설정할 수 없기 때문에 .NET API를 사용하여 순전히 다시 만들 수 없습니다. 하지만 네이티브 API는이를 허용합니다.

Environment.GetEnvironmentVariable을 호출하면 다른 변수가 환경에서 사라지거나 절반이 사라집니다.

대상 프레임 워크가 .NET 2.0으로 설정된 동작은 약간 다릅니다. 직접 myvar을 얻는 것과 반복하는 것의 불일치는 SetEnvironmentVariable에 대한 기본 호출 직후에 발생합니다. 다른 변수를 쿼리 할 필요가 없습니다.

편집 : 고정 유니 코드에서 떨어져, 이전 단락에 설명 된대로 Charset=CharSet.Auto 추가, 같은 친절 정확히 (! 감사는) 한스 옆모습에 의해 제안 .NET 2.0의 그것과 .NET의 4.5.x의 막무가내 수준을 감소 손질. 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"); 
     } 
    } 
} 
+0

관찰 : 네이티브'SetEnvironmentVariable' 결과를 확인하지 마십시오. 모든 통화에서 올바르게 작동하는지 어떻게 알 수 있습니까? – leppie

+1

'Environment.GetEnvironmentVariable ("nonexistent")'다음에 별다른 결과가 없다. myVar은 공백을 계속한다. "" " – Luizgrs

+0

그리고 나는 많은 명령 행/박쥐 연산에서'String.Empty'와 정의 된 하나는 똑같은 것입니다 (이것은 제가 규칙으로 캠핑하는 것입니다 :)). 변수가 비어 있는지 확인하기 위해 사용자에게 확인을 지시 할 때 MS가 말하는 것은 다음과 같습니다. http://support.microsoft.com/kb/121170. – Luizgrs

답변

2

이유 :

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

myvar is not set 

.NET이 kernel32!GetEnvironmentVariableW ("myvar", <pointer>, 128)를 호출하고 0 GetLastError()가 203로 설정되어 반환 = 입력 된 환경 옵션을 찾을 수 없습니다.

이 API는 여기 http://msdn.microsoft.com/en-us/library/windows/desktop/ms683188(v=vs.85).aspx

iteration gives value of myvar as "" 

단계 환경 문자열의 블록에 대한 포인터를 반환이 전화 GetEnvironmentStringsW()를 설명되어 있습니다. 여기에 MyVar가 있습니다.

이 API는 여기 http://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx

설명되어이 정보는 다음 API 모니터를 연결, 응용 프로그램의 시작 부분에 일시 중지를 넣어해야 할 수도 있습니다 닷넷 EXE에 대한 API 모니터를 사용하는 경우 Rohitab API는 http://www.rohitab.com/apimonitor을 모니터링하여 얻은 후에. 이 경우 활성화 된 모니터링 시스템 서비스에서

-> 프로세스와 스레드 -> 프로세스 ->의 Kernel32.dll 및 선택 해제 GetCurrentProcess()는

라는 .NET 소스 코드는 여기 http://referencesource.microsoft.com/#mscorlib/system/environment.cs

Win32Native.ERROR_ENVVAR_NOT_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 :

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 호출

가 발생

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
public static extern bool SetEnvironmentVariable(string lpName, string lpValue); 

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
static extern uint GetEnvironmentVariable(string lpName, [Out] StringBuilder lpBuffer, uint nSize); 

StringBuilder buffer = new StringBuilder(128); 

SetEnvironmentVariable("myvar", String.Empty); 
uint result=GetEnvironmentVariable("myvar", buffer, 128); 
if (result==0) 
{ 
    Console.WriteLine(string.Format("Error: {0}", Marshal.GetLastWin32Error())); 
} 

이 : 서로 직접 후이 경우 IP/호출 SetEnvironmentVariable 및 GetEnvironmentVariable에서 .NET 4에서 호출

ConsoleTest.exe!SetEnvironmentVariableW ("myvar", "") TRUE   
    KERNELBASE.dll!RtlSetEnvironmentVar (NULL, "myvar", 5, "", 0) STATUS_SUCCESS  
ConsoleTest.exe!GetEnvironmentVariableW ("myvar", <pointer 1>, 128) 0 0 = The operation completed successfully. 
    KERNELBASE.dll!RtlQueryEnvironmentVariable (NULL, "myvar", 5, <pointer 1>, 128, <pointer 2>) STATUS_SUCCESS 

이 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 
내가 clr.dll이 GetEnvironmentVariableW의 반환 값에 영향을 미치는 것을 볼 수 있습니다로 .NET 버전에서

통화 KERNELBASE.dll! RtlQueryEnvironmentVariable는 지금까지

을 성공합니다.

당신은 WinDbg를에서의 .NET EXE를 시작하고 당신의 주소를 찾기 위해 "someValue와"

로드 .NET 디버깅 확장

0:003> .loadby sos clr 

표시 프로세스 환경 블록 (PEB)를 설정 한 후 휴식 경우 메모리에 우리의 값의 위치에 대한 환경

0:003> !peb 
PEB at 7f40d000 
    InheritedAddressSpace: No 
    ReadImageFileExecOptions: No 
    BeingDebugged:   Yes 
    ImageBaseAddress:   00250000 
    Ldr      77d891e0 
     etc... 
    Environment: **006b0c68** 
     etc.. 

검색 ...

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]) 

, 여기에서 onwords 실행을 추적 ... 우리가 값을 다시 읽을 때 우리는 지금 중단 점

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 

그 메모리 위치에 읽기 중단 점을 ... 설정 우리 참조 오류가 여기에 설정됩니다

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) 

이 문제는 내가 RAIS을 권 해드립니다 변경 필요한 경우 Microsoft 지원 사례.

+0

.NET이하는 일에 대한 통찰에 감사드립니다. .NET 디버깅을 아직 설정할 수 없었으며 참조 소스 만 사용하고있었습니다. 답안의 첫 번째 하이퍼 링크는이 정보를 가리 킵니다 : "* 함수가 실패하면 반환 값은 0이고 지정된 환경 변수가 환경 블록에 없으면 GetLastError는 ERROR_ENVVAR_NOT_FOUND. *"를 반환합니다. ERROR_ENVVAR_NOT_FOUND는 230입니다. "myvar"가이 환경 블록에 존재하고 실제로 Win32 API 호출을 통해 발견되면 왜이 환경 블록에서 발견되지 않습니까? –

+0

몇 가지 추가 설명과 문제를 자세히 조사하는 데 사용할 수있는 기술을 추가했습니다. –

+0

필자는 바보 같아야하지만 여러 번 시도한 후에도 심층적 인 대답을 다시 읽은 후에도 Microsoft 코드가 무엇을하고 있다고 생각하는지 현명하지 않습니다. 어쨌든 여기에 게시하기 전에 의심되는 버그를 Microsoft에보고했습니다. [link] (https://connect.microsoft.com/VisualStudio/feedback/details/1032585/environment-getenvironment-call-affects-the-visibility-of - 기타 - 환경 변수). 나는 주변에서 일할 수있다. 단지 나를 혼란스럽게 만든다. 'SetEnvironmentVariable '에 대한 P/Invoke는 .NET 1.1의 유물이었으며 더 이상 필요하지 않았습니다. –

관련 문제