From a9b57bd4fe194b30b3c6e9a85a316fc218f474be Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 20 Nov 2018 17:03:33 -0500 Subject: [PATCH] Remove remaining StringBuilder marshaling use from Corelib (#21120) * Remove remaining StringBuilder marshaling use from Corelib - Unix globalization functions were using StringBuilder. Replaced them with stackallocs, as the max sizes were all relatively small. - Registry methods were declared to use StringBuilder, but these weren't actually used by corelib, and in fact, corefx isn't using StringBuilder with them either. I've changed the definitions for now to use char[] instead (all the call sites are passing in null), and I'll fix up some corefx usage separately. - Resource-related functions were using StringBuilder, and have been replaced with stackallocs, given reasonably small max buffer sizes. - ExpandEnvironmentVariables was using a StringBuilder, but it's rewritten equivalent code in corefx is not. For now I've copied the corefx function over to coreclr to replace the different implementation, but we can look at subsequently consolidating them into one. * Address PR feedback --- .../Interop.Calendar.cs | 2 +- .../System.Globalization.Native/Interop.Locale.cs | 15 +++--- .../Interop.TimeZoneInfo.cs | 4 +- .../System.Globalization.Native/Interop.Utils.cs | 40 +++++++-------- .../Windows/Advapi32/Interop.RegEnumKeyEx.cs | 2 +- .../Windows/Advapi32/Interop.RegQueryInfoKey.cs | 2 +- .../Windows/Advapi32/Interop.RegQueryValueEx.cs | 9 ---- .../shared/Interop/Windows/Kernel32/Interop.MUI.cs | 3 +- .../Interop/Windows/User32/Interop.LoadString.cs | 3 +- .../shared/System.Private.CoreLib.Shared.projitems | 1 - src/System.Private.CoreLib/shared/System/Action.cs | 2 + .../System/Globalization/CalendarData.Unix.cs | 16 +++--- .../System/Globalization/CultureData.Unix.cs | 57 +++++++++++----------- .../shared/System/TimeZoneInfo.Unix.cs | 15 +++--- .../shared/System/TimeZoneInfo.Win32.cs | 44 ++++++++--------- .../src/Microsoft/Win32/Win32Native.cs | 4 +- .../src/System/Environment.cs | 34 ++++++------- 17 files changed, 115 insertions(+), 138 deletions(-) diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs index 55553cc..764bdaf 100644 --- a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs +++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs @@ -19,7 +19,7 @@ internal static partial class Interop internal static extern int GetCalendars(string localeName, CalendarId[] calendars, int calendarsCapacity); [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetCalendarInfo")] - internal static extern ResultCode GetCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType calendarDataType, [Out] StringBuilder result, int resultCapacity); + internal static extern unsafe ResultCode GetCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType calendarDataType, char* result, int resultCapacity); [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_EnumCalendarInfo")] internal static extern bool EnumCalendarInfo(EnumCalendarInfoCallback callback, string localeName, CalendarId calendarId, CalendarDataType calendarDataType, IntPtr context); diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Locale.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Locale.cs index b6f5fbe..417b71e 100644 --- a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Locale.cs +++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Locale.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.InteropServices; -using System.Text; internal static partial class Interop { @@ -12,29 +11,29 @@ internal static partial class Interop { [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocaleName")] [return: MarshalAs(UnmanagedType.Bool)] - internal static extern unsafe bool GetLocaleName(string localeName, [Out] StringBuilder value, int valueLength); + internal static extern unsafe bool GetLocaleName(string localeName, char* value, int valueLength); [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocaleInfoString")] [return: MarshalAs(UnmanagedType.Bool)] - internal static extern unsafe bool GetLocaleInfoString(string localeName, uint localeStringData, [Out] StringBuilder value, int valueLength); + internal static extern unsafe bool GetLocaleInfoString(string localeName, uint localeStringData, char* value, int valueLength); [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetDefaultLocaleName")] [return: MarshalAs(UnmanagedType.Bool)] - internal static extern unsafe bool GetDefaultLocaleName([Out] StringBuilder value, int valueLength); + internal static extern unsafe bool GetDefaultLocaleName(char* value, int valueLength); [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocaleTimeFormat")] [return: MarshalAs(UnmanagedType.Bool)] - internal static extern unsafe bool GetLocaleTimeFormat(string localeName, bool shortFormat, [Out] StringBuilder value, int valueLength); + internal static extern unsafe bool GetLocaleTimeFormat(string localeName, bool shortFormat, char* value, int valueLength); [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocaleInfoInt")] [return: MarshalAs(UnmanagedType.Bool)] - internal static extern unsafe bool GetLocaleInfoInt(string localeName, uint localeNumberData, ref int value); + internal static extern bool GetLocaleInfoInt(string localeName, uint localeNumberData, ref int value); [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocaleInfoGroupingSizes")] [return: MarshalAs(UnmanagedType.Bool)] - internal static extern unsafe bool GetLocaleInfoGroupingSizes(string localeName, uint localeGroupingData, ref int primaryGroupSize, ref int secondaryGroupSize); + internal static extern bool GetLocaleInfoGroupingSizes(string localeName, uint localeGroupingData, ref int primaryGroupSize, ref int secondaryGroupSize); [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetLocales")] - internal static extern unsafe int GetLocales([Out] char[] value, int valueLength); + internal static extern int GetLocales([Out] char[] value, int valueLength); } } diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.TimeZoneInfo.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.TimeZoneInfo.cs index 47cf266..6c69268 100644 --- a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.TimeZoneInfo.cs +++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.TimeZoneInfo.cs @@ -18,11 +18,11 @@ internal static partial class Interop } [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetTimeZoneDisplayName")] - internal static extern ResultCode GetTimeZoneDisplayName( + internal static extern unsafe ResultCode GetTimeZoneDisplayName( string localeName, string timeZoneId, TimeZoneDisplayNameType type, - [Out] StringBuilder result, + char* result, int resultLength); } } diff --git a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Utils.cs b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Utils.cs index 9887bd4..9698be9 100644 --- a/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Utils.cs +++ b/src/System.Private.CoreLib/shared/Interop/Unix/System.Globalization.Native/Interop.Utils.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; +using System.Buffers; using System.Text; internal static partial class Interop @@ -13,39 +15,33 @@ internal static partial class Interop /// increasing buffer until the size is big enough. /// internal static bool CallStringMethod( - Func interopCall, - TArg1 arg1, - TArg2 arg2, - TArg3 arg3, + SpanFunc interopCall, + TArg1 arg1, TArg2 arg2, TArg3 arg3, out string result) { - const int initialStringSize = 80; - const int maxDoubleAttempts = 5; + const int InitialSize = 256; // arbitrary stack allocation size + const int MaxHeapSize = 1280; // max from previous version of the code, starting at 80 and doubling four times - StringBuilder stringBuilder = StringBuilderCache.Acquire(initialStringSize); + Span buffer = stackalloc char[InitialSize]; + Interop.Globalization.ResultCode resultCode = interopCall(buffer, arg1, arg2, arg3); - for (int i = 0; i < maxDoubleAttempts; i++) + if (resultCode == Interop.Globalization.ResultCode.Success) { - Interop.Globalization.ResultCode resultCode = interopCall(arg1, arg2, arg3, stringBuilder); + result = buffer.Slice(0, buffer.IndexOf('\0')).ToString(); + return true; + } - if (resultCode == Interop.Globalization.ResultCode.Success) + if (resultCode == Interop.Globalization.ResultCode.InsufficentBuffer) + { + // Increase the string size and try again + buffer = new char[MaxHeapSize]; + if (interopCall(buffer, arg1, arg2, arg3) == Interop.Globalization.ResultCode.Success) { - result = StringBuilderCache.GetStringAndRelease(stringBuilder); + result = buffer.Slice(0, buffer.IndexOf('\0')).ToString(); return true; } - else if (resultCode == Interop.Globalization.ResultCode.InsufficentBuffer) - { - // increase the string size and loop - stringBuilder.EnsureCapacity(stringBuilder.Capacity * 2); - } - else - { - // if there is an unknown error, don't proceed - break; - } } - StringBuilderCache.Release(stringBuilder); result = null; return false; } diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Advapi32/Interop.RegEnumKeyEx.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Advapi32/Interop.RegEnumKeyEx.cs index 499329d..8e37b8e 100644 --- a/src/System.Private.CoreLib/shared/Interop/Windows/Advapi32/Interop.RegEnumKeyEx.cs +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Advapi32/Interop.RegEnumKeyEx.cs @@ -22,7 +22,7 @@ internal partial class Interop char[] lpName, ref int lpcbName, int[] lpReserved, - [Out]StringBuilder lpClass, + [Out] char[] lpClass, int[] lpcbClass, long[] lpftLastWriteTime); } diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Advapi32/Interop.RegQueryInfoKey.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Advapi32/Interop.RegQueryInfoKey.cs index a0e5f27..2d220a1 100644 --- a/src/System.Private.CoreLib/shared/Interop/Windows/Advapi32/Interop.RegQueryInfoKey.cs +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Advapi32/Interop.RegQueryInfoKey.cs @@ -18,7 +18,7 @@ internal partial class Interop [DllImport(Libraries.Advapi32, CharSet = CharSet.Unicode, BestFitMapping = false, EntryPoint = "RegQueryInfoKeyW")] internal static extern int RegQueryInfoKey( SafeRegistryHandle hKey, - [Out]StringBuilder lpClass, + [Out] char[] lpClass, int[] lpcbClass, IntPtr lpReserved_MustBeZero, ref int lpcSubKeys, diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Advapi32/Interop.RegQueryValueEx.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Advapi32/Interop.RegQueryValueEx.cs index 2ab3ba0..989d072 100644 --- a/src/System.Private.CoreLib/shared/Interop/Windows/Advapi32/Interop.RegQueryValueEx.cs +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Advapi32/Interop.RegQueryValueEx.cs @@ -50,14 +50,5 @@ internal partial class Interop ref int lpType, [Out] char[] lpData, ref int lpcbData); - - [DllImport(Libraries.Advapi32, CharSet = CharSet.Unicode, BestFitMapping = false, EntryPoint = "RegQueryValueExW")] - internal static extern int RegQueryValueEx( - SafeRegistryHandle hKey, - string lpValueName, - int[] lpReserved, - ref int lpType, - [Out]StringBuilder lpData, - ref int lpcbData); } } diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MUI.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MUI.cs index 8d97c03..509d9a2 100644 --- a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MUI.cs +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.MUI.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.InteropServices; -using System.Text; internal static partial class Interop { @@ -13,6 +12,6 @@ internal static partial class Interop internal const uint MUI_PREFERRED_UI_LANGUAGES = 0x10; [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] - internal static extern bool GetFileMUIPath(uint flags, string filePath, [Out] StringBuilder language, ref int languageLength, [Out] StringBuilder fileMuiPath, ref int fileMuiPathLength, ref long enumerator); + internal static extern unsafe bool GetFileMUIPath(uint dwFlags, string pcwszFilePath, char* pwszLanguage, ref int pcchLanguage, char* pwszFileMUIPath, ref int pcchFileMUIPath, ref long pululEnumerator); } } diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.LoadString.cs b/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.LoadString.cs index d3d575e..078ebd4 100644 --- a/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.LoadString.cs +++ b/src/System.Private.CoreLib/shared/Interop/Windows/User32/Interop.LoadString.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using System.Text; using Microsoft.Win32.SafeHandles; internal partial class Interop @@ -11,6 +10,6 @@ internal partial class Interop internal partial class User32 { [DllImport(Libraries.User32, SetLastError = true, EntryPoint = "LoadStringW", CharSet = CharSet.Unicode)] - internal static extern int LoadString(SafeLibraryHandle handle, int id, [Out] StringBuilder buffer, int bufferLength); + internal static unsafe extern int LoadString(SafeLibraryHandle hInstance, uint uID, char* lpBuffer, int cchBufferMax); } } diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems index f209d95..ec25efb 100644 --- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems @@ -893,7 +893,6 @@ - diff --git a/src/System.Private.CoreLib/shared/System/Action.cs b/src/System.Private.CoreLib/shared/System/Action.cs index 6e3ccff..54ca7aa 100644 --- a/src/System.Private.CoreLib/shared/System/Action.cs +++ b/src/System.Private.CoreLib/shared/System/Action.cs @@ -35,4 +35,6 @@ namespace System.Buffers { public delegate void SpanAction(Span span, TArg arg); public delegate void ReadOnlySpanAction(ReadOnlySpan span, TArg arg); + + internal delegate TResult SpanFunc(Span span, T1 arg1, T2 arg2, T3 arg3); } diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs index c94ac0a..db34cde 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/CalendarData.Unix.cs @@ -104,18 +104,18 @@ namespace System.Globalization // PAL Layer ends here - private static bool GetCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, out string calendarString) + private static unsafe bool GetCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, out string calendarString) { Debug.Assert(!GlobalizationMode.Invariant); return Interop.CallStringMethod( - (locale, calId, type, stringBuilder) => - Interop.Globalization.GetCalendarInfo( - locale, - calId, - type, - stringBuilder, - stringBuilder.Capacity), + (buffer, locale, id, type) => + { + fixed (char* bufferPtr = buffer) + { + return Interop.Globalization.GetCalendarInfo(locale, id, type, bufferPtr, buffer.Length); + } + }, localeName, calendarId, dataType, diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Unix.cs index 4b21f2e..029057c 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Unix.cs @@ -87,35 +87,33 @@ namespace System.Globalization return true; } - internal static bool GetLocaleName(string localeName, out string windowsName) + internal static unsafe bool GetLocaleName(string localeName, out string windowsName) { // Get the locale name from ICU - StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY); - if (!Interop.Globalization.GetLocaleName(localeName, sb, sb.Capacity)) + char* buffer = stackalloc char[ICU_ULOC_FULLNAME_CAPACITY]; + if (!Interop.Globalization.GetLocaleName(localeName, buffer, ICU_ULOC_FULLNAME_CAPACITY)) { - StringBuilderCache.Release(sb); windowsName = null; return false; // fail } // Success - use the locale name returned which may be different than realNameBuffer (casing) - windowsName = StringBuilderCache.GetStringAndRelease(sb); // the name passed to subsequent ICU calls + windowsName = new string(buffer); // the name passed to subsequent ICU calls return true; } - internal static bool GetDefaultLocaleName(out string windowsName) + internal static unsafe bool GetDefaultLocaleName(out string windowsName) { // Get the default (system) locale name from ICU - StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY); - if (!Interop.Globalization.GetDefaultLocaleName(sb, sb.Capacity)) + char* buffer = stackalloc char[ICU_ULOC_FULLNAME_CAPACITY]; + if (!Interop.Globalization.GetDefaultLocaleName(buffer, ICU_ULOC_FULLNAME_CAPACITY)) { - StringBuilderCache.Release(sb); windowsName = null; return false; // fail } // Success - use the locale name returned which may be different than realNameBuffer (casing) - windowsName = StringBuilderCache.GetStringAndRelease(sb); // the name passed to subsequent ICU calls + windowsName = new string(buffer); // the name passed to subsequent ICU calls return true; } @@ -129,7 +127,7 @@ namespace System.Globalization // For LOCALE_SPARENT we need the option of using the "real" name (forcing neutral names) instead of the // "windows" name, which can be specific for downlevel (< windows 7) os's. - private string GetLocaleInfo(string localeName, LocaleStringData type) + private unsafe string GetLocaleInfo(string localeName, LocaleStringData type) { Debug.Assert(localeName != null, "[CultureData.GetLocaleInfo] Expected localeName to be not be null"); @@ -141,17 +139,16 @@ namespace System.Globalization GetLocaleInfo(localeName, LocaleStringData.PositiveInfinitySymbol); } - StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); - - bool result = Interop.Globalization.GetLocaleInfoString(localeName, (uint)type, sb, sb.Capacity); + char* buffer = stackalloc char[ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY]; + bool result = Interop.Globalization.GetLocaleInfoString(localeName, (uint)type, buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); if (!result) { // Failed, just use empty string - StringBuilderCache.Release(sb); Debug.Fail("[CultureData.GetLocaleInfo(LocaleStringData)] Failed"); return string.Empty; } - return StringBuilderCache.GetStringAndRelease(sb); + + return new string(buffer); } private int GetLocaleInfo(LocaleNumberData type) @@ -204,22 +201,22 @@ namespace System.Globalization return GetTimeFormatString(false); } - private string GetTimeFormatString(bool shortFormat) + private unsafe string GetTimeFormatString(bool shortFormat) { Debug.Assert(_sWindowsName != null, "[CultureData.GetTimeFormatString(bool shortFormat)] Expected _sWindowsName to be populated already"); - StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); + char* buffer = stackalloc char[ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY]; - bool result = Interop.Globalization.GetLocaleTimeFormat(_sWindowsName, shortFormat, sb, sb.Capacity); + bool result = Interop.Globalization.GetLocaleTimeFormat(_sWindowsName, shortFormat, buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); if (!result) { // Failed, just use empty string - StringBuilderCache.Release(sb); Debug.Fail("[CultureData.GetTimeFormatString(bool shortFormat)] Failed"); return string.Empty; } - return ConvertIcuTimeFormatString(StringBuilderCache.GetStringAndRelease(sb)); + var span = new ReadOnlySpan(buffer, ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); + return ConvertIcuTimeFormatString(span.Slice(0, span.IndexOf('\0'))); } private int GetFirstDayOfWeek() @@ -261,14 +258,17 @@ namespace System.Globalization return CultureInfo.GetUserDefaultCulture(); } - private static string ConvertIcuTimeFormatString(string icuFormatString) + private static string ConvertIcuTimeFormatString(ReadOnlySpan icuFormatString) { - StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY); + Debug.Assert(icuFormatString.Length < ICU_ULOC_FULLNAME_CAPACITY); + Span result = stackalloc char[ICU_ULOC_FULLNAME_CAPACITY]; + bool amPmAdded = false; + int resultPos = 0; for (int i = 0; i < icuFormatString.Length; i++) { - switch(icuFormatString[i]) + switch (icuFormatString[i]) { case ':': case '.': @@ -276,27 +276,28 @@ namespace System.Globalization case 'h': case 'm': case 's': - sb.Append(icuFormatString[i]); + result[resultPos++] = icuFormatString[i]; break; case ' ': case '\u00A0': // Convert nonbreaking spaces into regular spaces - sb.Append(' '); + result[resultPos++] = ' '; break; case 'a': // AM/PM if (!amPmAdded) { amPmAdded = true; - sb.Append("tt"); + result[resultPos++] = 't'; + result[resultPos++] = 't'; } break; } } - return StringBuilderCache.GetStringAndRelease(sb); + return result.Slice(0, resultPos).ToString(); } private static string LCIDToLocaleName(int culture) diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs index ebeb301..604aa03 100644 --- a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Unix.cs @@ -104,7 +104,7 @@ namespace System ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime); } - private void GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType nameType, ref string displayName) + private unsafe void GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType nameType, ref string displayName) { if (GlobalizationMode.Invariant) { @@ -114,12 +114,13 @@ namespace System string timeZoneDisplayName; bool result = Interop.CallStringMethod( - (locale, id, type, stringBuilder) => Interop.Globalization.GetTimeZoneDisplayName( - locale, - id, - type, - stringBuilder, - stringBuilder.Capacity), + (buffer, locale, id, type) => + { + fixed (char* bufferPtr = buffer) + { + return Interop.Globalization.GetTimeZoneDisplayName(locale, id, type, bufferPtr, buffer.Length); + } + }, CultureInfo.CurrentUICulture.Name, _id, nameType, diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Win32.cs b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Win32.cs index d81b9f0..a6b583e 100644 --- a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Win32.cs +++ b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Win32.cs @@ -806,22 +806,21 @@ namespace System try { - StringBuilder fileMuiPath = StringBuilderCache.Acquire(Interop.Kernel32.MAX_PATH); - fileMuiPath.Length = Interop.Kernel32.MAX_PATH; - int fileMuiPathLength = Interop.Kernel32.MAX_PATH; - int languageLength = 0; - long enumerator = 0; - - bool succeeded = Interop.Kernel32.GetFileMUIPath( - Interop.Kernel32.MUI_PREFERRED_UI_LANGUAGES, - filePath, null /* language */, ref languageLength, - fileMuiPath, ref fileMuiPathLength, ref enumerator); - if (!succeeded) + unsafe { - StringBuilderCache.Release(fileMuiPath); - return string.Empty; + char* fileMuiPath = stackalloc char[Interop.Kernel32.MAX_PATH]; + int fileMuiPathLength = Interop.Kernel32.MAX_PATH; + int languageLength = 0; + long enumerator = 0; + + bool succeeded = Interop.Kernel32.GetFileMUIPath( + Interop.Kernel32.MUI_PREFERRED_UI_LANGUAGES, + filePath, null /* language */, ref languageLength, + fileMuiPath, ref fileMuiPathLength, ref enumerator); + return succeeded ? + TryGetLocalizedNameByNativeResource(new string(fileMuiPath, 0, fileMuiPathLength), resourceId) : + string.Empty; } - return TryGetLocalizedNameByNativeResource(StringBuilderCache.GetStringAndRelease(fileMuiPath), resourceId); } catch (EntryPointNotFoundException) { @@ -836,26 +835,23 @@ namespace System /// "resource.dll" is a language-specific resource DLL. /// If the localized resource DLL exists, LoadString(resource) is returned. /// - private static string TryGetLocalizedNameByNativeResource(string filePath, int resource) + private static unsafe string TryGetLocalizedNameByNativeResource(string filePath, int resource) { - using (SafeLibraryHandle handle = - Interop.Kernel32.LoadLibraryEx(filePath, IntPtr.Zero, Interop.Kernel32.LOAD_LIBRARY_AS_DATAFILE)) + using (SafeLibraryHandle handle = Interop.Kernel32.LoadLibraryEx(filePath, IntPtr.Zero, Interop.Kernel32.LOAD_LIBRARY_AS_DATAFILE)) { if (!handle.IsInvalid) { const int LoadStringMaxLength = 500; + char* localizedResource = stackalloc char[LoadStringMaxLength]; - StringBuilder localizedResource = StringBuilderCache.Acquire(LoadStringMaxLength); - - int result = Interop.User32.LoadString(handle, resource, - localizedResource, LoadStringMaxLength); - - if (result != 0) + int charsWritten = Interop.User32.LoadString(handle, (uint)resource, localizedResource, LoadStringMaxLength); + if (charsWritten != 0) { - return StringBuilderCache.GetStringAndRelease(localizedResource); + return new string(localizedResource, 0, charsWritten); } } } + return string.Empty; } diff --git a/src/System.Private.CoreLib/src/Microsoft/Win32/Win32Native.cs b/src/System.Private.CoreLib/src/Microsoft/Win32/Win32Native.cs index a135433..950537a 100644 --- a/src/System.Private.CoreLib/src/Microsoft/Win32/Win32Native.cs +++ b/src/System.Private.CoreLib/src/Microsoft/Win32/Win32Native.cs @@ -261,8 +261,8 @@ namespace Microsoft.Win32 [DllImport(Interop.Libraries.Ole32)] internal static extern IntPtr CoTaskMemRealloc(IntPtr pv, UIntPtr cb); - [DllImport(Interop.Libraries.Kernel32, CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)] - internal static extern int ExpandEnvironmentStrings(string lpSrc, [Out]StringBuilder lpDst, int nSize); + [DllImport(Interop.Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] + internal static extern uint ExpandEnvironmentStringsW(string lpSrc, ref char lpDst, uint nSize); [DllImport(Interop.Libraries.Kernel32)] internal static extern IntPtr LocalReAlloc(IntPtr handle, IntPtr sizetcbBytes, int uFlags); diff --git a/src/System.Private.CoreLib/src/System/Environment.cs b/src/System.Private.CoreLib/src/System/Environment.cs index b1f6b39..a818053 100644 --- a/src/System.Private.CoreLib/src/System/Environment.cs +++ b/src/System.Private.CoreLib/src/System/Environment.cs @@ -115,38 +115,32 @@ namespace System #if FEATURE_WIN32_REGISTRY // This is only used by RegistryKey on Windows. - public static string ExpandEnvironmentVariables(string name) + internal static string ExpandEnvironmentVariables(string name) { - if (name == null) - throw new ArgumentNullException(nameof(name)); + Debug.Assert(name != null); if (name.Length == 0) { return name; } - int currentSize = 100; - StringBuilder blob = new StringBuilder(currentSize); // A somewhat reasonable default size + Span initialBuffer = stackalloc char[128]; + var builder = new ValueStringBuilder(initialBuffer); - int size; - - blob.Length = 0; - size = Win32Native.ExpandEnvironmentStrings(name, blob, currentSize); - if (size == 0) - Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); - - while (size > currentSize) + uint length; + while ((length = Win32Native.ExpandEnvironmentStringsW(name, ref builder.GetPinnableReference(), (uint)builder.Capacity)) > builder.Capacity) { - currentSize = size; - blob.Capacity = currentSize; - blob.Length = 0; + builder.EnsureCapacity((int)length); + } - size = Win32Native.ExpandEnvironmentStrings(name, blob, currentSize); - if (size == 0) - Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + if (length == 0) + { + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } - return blob.ToString(); + // length includes the null terminator + builder.Length = (int)length - 1; + return builder.ToString(); } #endif // FEATURE_WIN32_REGISTRY -- 2.7.4