diff options
author | Stephen Toub <stoub@microsoft.com> | 2018-11-20 17:03:33 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-11-20 17:03:33 -0500 |
commit | a9b57bd4fe194b30b3c6e9a85a316fc218f474be (patch) | |
tree | d5595388d53a6aed0dac4e6e408dcf794b53e144 /src | |
parent | 5a0af055e4c39f4f49b7ff35141739dc3fda4f9f (diff) | |
download | coreclr-a9b57bd4fe194b30b3c6e9a85a316fc218f474be.tar.gz coreclr-a9b57bd4fe194b30b3c6e9a85a316fc218f474be.tar.bz2 coreclr-a9b57bd4fe194b30b3c6e9a85a316fc218f474be.zip |
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
Diffstat (limited to 'src')
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 55553cc7ea..764bdaf85a 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 b6f5fbec1c..417b71e7f1 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 47cf26662b..6c69268246 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 9887bd4f0b..9698be92e9 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. /// </summary> internal static bool CallStringMethod<TArg1, TArg2, TArg3>( - Func<TArg1, TArg2, TArg3, StringBuilder, Interop.Globalization.ResultCode> interopCall, - TArg1 arg1, - TArg2 arg2, - TArg3 arg3, + SpanFunc<char, TArg1, TArg2, TArg3, Interop.Globalization.ResultCode> 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<char> 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 499329d7d3..8e37b8e74b 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 a0e5f27b47..2d220a1963 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 2ab3ba019e..989d0727c7 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 8d97c03546..509d9a2411 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 d3d575e221..078ebd481d 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 f209d954be..ec25efb9d2 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 @@ <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Advapi32\Interop.RegFlushKey.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Advapi32\Interop.RegistryConstants.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Advapi32\Interop.RegOpenKeyEx.cs" /> - <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Advapi32\Interop.RegQueryInfoKey.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Advapi32\Interop.RegQueryValueEx.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Advapi32\Interop.RegSetValueEx.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeRegistryHandle.cs" /> diff --git a/src/System.Private.CoreLib/shared/System/Action.cs b/src/System.Private.CoreLib/shared/System/Action.cs index 6e3ccff48c..54ca7aaf53 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<T, in TArg>(Span<T> span, TArg arg); public delegate void ReadOnlySpanAction<T, in TArg>(ReadOnlySpan<T> span, TArg arg); + + internal delegate TResult SpanFunc<TSpan, in T1, in T2, in T3, out TResult>(Span<TSpan> 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 c94ac0ae5f..db34cdeeb6 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 4b21f2e7d3..029057ca78 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<char>(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<char> icuFormatString) { - StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY); + Debug.Assert(icuFormatString.Length < ICU_ULOC_FULLNAME_CAPACITY); + Span<char> 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 ebeb301fd1..604aa0365f 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 d81b9f01fd..a6b583e21e 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. /// </summary> - 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 a13543320b..950537a864 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 b1f6b3923f..a81805340d 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<char> 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 |