Environment.SystemDirectory: Avoid StringBuilder overhead (#14513)
authorJustin Van Patten <jvp@justinvp.com>
Mon, 16 Oct 2017 05:49:53 +0000 (22:49 -0700)
committerJan Kotas <jkotas@microsoft.com>
Mon, 16 Oct 2017 05:49:53 +0000 (22:49 -0700)
* Environment.SystemDirectory: Avoid StringBuilder overhead

Use a stack allocated buffer, with fallback to a char array.

src/mscorlib/System.Private.CoreLib.csproj
src/mscorlib/src/Interop/Windows/Kernel32/Interop.GetSystemDirectoryW.cs [new file with mode: 0644]
src/mscorlib/src/Microsoft/Win32/Win32Native.cs
src/mscorlib/src/System/Environment.Windows.cs [new file with mode: 0644]
src/mscorlib/src/System/Environment.cs

index e267d52..25f605c 100644 (file)
     <Compile Include="$(BclSourcesRoot)\System\TimeZoneInfo.Unix.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetsWindows)' == 'true'">
+    <Compile Include="$(BclSourcesRoot)\Interop\Windows\Kernel32\Interop.GetSystemDirectoryW.cs" />
     <Compile Include="$(BclSourcesRoot)\Interop\Windows\Normaliz\Interop.Idna.cs" />
     <Compile Include="$(BclSourcesRoot)\Interop\Windows\Normaliz\Interop.Normalization.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Debug.Windows.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Globalization\TextInfo.Windows.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Text\Normalization.Windows.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.Windows.cs" />
+    <Compile Include="$(BclSourcesRoot)\System\Environment.Windows.cs" />
     <Compile Include="$(BclSourcesRoot)\System\TimeZoneInfo.Win32.cs" />
   </ItemGroup>
   <!-- Include additional sources shared files in the compilation -->
diff --git a/src/mscorlib/src/Interop/Windows/Kernel32/Interop.GetSystemDirectoryW.cs b/src/mscorlib/src/Interop/Windows/Kernel32/Interop.GetSystemDirectoryW.cs
new file mode 100644 (file)
index 0000000..25c59d7
--- /dev/null
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class Kernel32
+    {
+        [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true)]
+        private static extern unsafe int GetSystemDirectoryW(char* lpBuffer, int uSize);
+
+        internal static unsafe int GetSystemDirectoryW(Span<char> buffer)
+        {
+            fixed (char* bufferPtr = &buffer.DangerousGetPinnableReference())
+            {
+                return GetSystemDirectoryW(bufferPtr, buffer.Length);
+            }
+        }
+    }
+}
index eaa9ced..4ab7242 100644 (file)
@@ -531,9 +531,6 @@ namespace Microsoft.Win32
         [DllImport(Interop.Libraries.Kernel32, SetLastError = true, CharSet = CharSet.Auto, BestFitMapping = false)]
         internal static extern SafeWaitHandle OpenSemaphore(uint desiredAccess, bool inheritHandle, string name);
 
-        [DllImport(Interop.Libraries.Kernel32, CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
-        internal static extern int GetSystemDirectory([Out]StringBuilder sb, int length);
-
         internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);  // WinBase.h
 
         // Note, these are #defines used to extract handles, and are NOT handles.
diff --git a/src/mscorlib/src/System/Environment.Windows.cs b/src/mscorlib/src/System/Environment.Windows.cs
new file mode 100644 (file)
index 0000000..5bf7bea
--- /dev/null
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+
+namespace System
+{
+    internal static partial class Environment
+    {
+        internal static string SystemDirectory
+        {
+            get
+            {
+                // The path will likely be under 32 characters, e.g. C:\Windows\system32
+                Span<char> buffer = stackalloc char[32];
+                int requiredSize = Interop.Kernel32.GetSystemDirectoryW(buffer);
+
+                if (requiredSize > buffer.Length)
+                {
+                    buffer = new char[requiredSize];
+                    requiredSize = Interop.Kernel32.GetSystemDirectoryW(buffer);
+                }
+
+                if (requiredSize == 0)
+                {
+                    throw Win32Marshal.GetExceptionForLastWin32Error();
+                }
+
+                return new string(buffer.Slice(0, requiredSize));
+            }
+        }
+    }
+}
index 6e85513..a7e16eb 100644 (file)
@@ -115,21 +115,6 @@ namespace System
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
         public static extern void FailFast(String message, Exception exception);
 
-        // Returns the system directory (ie, C:\WinNT\System32).
-        internal static String SystemDirectory
-        {
-            get
-            {
-                StringBuilder sb = new StringBuilder(Path.MaxPath);
-                int r = Win32Native.GetSystemDirectory(sb, Path.MaxPath);
-                Debug.Assert(r < Path.MaxPath, "r < Path.MaxPath");
-                if (r == 0) throw Win32Marshal.GetExceptionForLastWin32Error();
-                String path = sb.ToString();
-
-                return path;
-            }
-        }
-
         public static String ExpandEnvironmentVariables(String name)
         {
             if (name == null)