Improve Dictionary TryGetValue size/perfomance (dotnet/coreclr#27195)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Environment.Variables.Windows.cs
blob4847698d6188119e3984b38a5da43ede1912e8b8
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.Buffers;
6 using System.Collections;
7 using System.Diagnostics;
8 using System.Runtime.InteropServices;
10 namespace System
12 public static partial class Environment
14 private static string? GetEnvironmentVariableCore(string variable)
16 Span<char> buffer = stackalloc char[128]; // a somewhat reasonable default size
17 int requiredSize = Interop.Kernel32.GetEnvironmentVariable(variable, buffer);
19 if (requiredSize == 0 && Marshal.GetLastWin32Error() == Interop.Errors.ERROR_ENVVAR_NOT_FOUND)
21 return null;
24 if (requiredSize <= buffer.Length)
26 return new string(buffer.Slice(0, requiredSize));
29 char[] chars = ArrayPool<char>.Shared.Rent(requiredSize);
30 try
32 buffer = chars;
33 requiredSize = Interop.Kernel32.GetEnvironmentVariable(variable, buffer);
34 if ((requiredSize == 0 && Marshal.GetLastWin32Error() == Interop.Errors.ERROR_ENVVAR_NOT_FOUND) ||
35 requiredSize > buffer.Length)
37 return null;
40 return new string(buffer.Slice(0, requiredSize));
42 finally
44 ArrayPool<char>.Shared.Return(chars);
48 private static void SetEnvironmentVariableCore(string variable, string? value)
50 if (!Interop.Kernel32.SetEnvironmentVariable(variable, value))
52 int errorCode = Marshal.GetLastWin32Error();
53 switch (errorCode)
55 case Interop.Errors.ERROR_ENVVAR_NOT_FOUND:
56 // Allow user to try to clear a environment variable
57 return;
59 case Interop.Errors.ERROR_FILENAME_EXCED_RANGE:
60 // The error message from Win32 is "The filename or extension is too long",
61 // which is not accurate.
62 throw new ArgumentException(SR.Argument_LongEnvVarValue);
64 case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY:
65 case Interop.Errors.ERROR_NO_SYSTEM_RESOURCES:
66 throw new OutOfMemoryException(Interop.Kernel32.GetMessage(errorCode));
68 default:
69 throw new ArgumentException(Interop.Kernel32.GetMessage(errorCode));
74 public static unsafe IDictionary GetEnvironmentVariables()
76 char* pStrings = Interop.Kernel32.GetEnvironmentStrings();
77 if (pStrings == null)
79 throw new OutOfMemoryException();
82 try
84 // Format for GetEnvironmentStrings is:
85 // [=HiddenVar=value\0]* [Variable=value\0]* \0
86 // See the description of Environment Blocks in MSDN's
87 // CreateProcess page (null-terminated array of null-terminated strings).
89 // Search for terminating \0\0 (two unicode \0's).
90 char* p = pStrings;
91 while (!(*p == '\0' && *(p + 1) == '\0'))
93 p++;
95 Span<char> block = new Span<char>(pStrings, (int)(p - pStrings + 1));
97 // Format for GetEnvironmentStrings is:
98 // (=HiddenVar=value\0 | Variable=value\0)* \0
99 // See the description of Environment Blocks in MSDN's
100 // CreateProcess page (null-terminated array of null-terminated strings).
101 // Note the =HiddenVar's aren't always at the beginning.
103 // Copy strings out, parsing into pairs and inserting into the table.
104 // The first few environment variable entries start with an '='.
105 // The current working directory of every drive (except for those drives
106 // you haven't cd'ed into in your DOS window) are stored in the
107 // environment block (as =C:=pwd) and the program's exit code is
108 // as well (=ExitCode=00000000).
110 var results = new Hashtable();
111 for (int i = 0; i < block.Length; i++)
113 int startKey = i;
115 // Skip to key. On some old OS, the environment block can be corrupted.
116 // Some will not have '=', so we need to check for '\0'.
117 while (block[i] != '=' && block[i] != '\0')
119 i++;
122 if (block[i] == '\0')
124 continue;
127 // Skip over environment variables starting with '='
128 if (i - startKey == 0)
130 while (block[i] != 0)
132 i++;
135 continue;
138 string key = new string(block.Slice(startKey, i - startKey));
139 i++; // skip over '='
141 int startValue = i;
142 while (block[i] != 0)
144 i++; // Read to end of this entry
147 string value = new string(block.Slice(startValue, i - startValue)); // skip over 0 handled by for loop's i++
150 results.Add(key, value);
152 catch (ArgumentException)
154 // Throw and catch intentionally to provide non-fatal notification about corrupted environment block
157 return results;
159 finally
161 bool success = Interop.Kernel32.FreeEnvironmentStrings(pStrings);
162 Debug.Assert(success);