From 496a47dbb858c8f38e168c885c0a85c5fad76d37 Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Fri, 4 Nov 2022 11:16:41 +1000 Subject: [PATCH 01/15] Initial work to remove string builders from PInvoke --- .../src/NativeMethods.txt | 1 + .../Forms/Internals/UnsafeNativeMethods.cs | 31 ----------- .../PInvoke.GetModuleFileNameLongPath.cs | 48 ++++++++++++++++ .../Windows/Win32/PInvoke.MAX_CLASS_NAME.cs | 11 ++++ .../src/Windows/Win32/PInvoke.MAX_PATH.cs | 55 +++++++++++++++++++ .../src/System/Windows/Forms/Application.cs | 3 +- .../src/System/Windows/Forms/Control.cs | 19 +++---- .../src/System/Windows/Forms/RichTextBox.cs | 3 +- .../src/System/Windows/Forms/TaskDialog.cs | 4 +- .../System/Windows/Forms/RichTextBoxTests.cs | 19 ++++--- 10 files changed, 137 insertions(+), 57 deletions(-) create mode 100644 src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs create mode 100644 src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_CLASS_NAME.cs diff --git a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt index b69035ec591..9c2a39ae58a 100644 --- a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt +++ b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt @@ -180,6 +180,7 @@ GetMenu GetMenuItemCount GetMessagePos GetMessageTime +GetModuleFileName GetModuleHandle GetNearestColor GetObjectType diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/UnsafeNativeMethods.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/UnsafeNativeMethods.cs index ea002904ffe..13f7566625f 100644 --- a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/UnsafeNativeMethods.cs +++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/UnsafeNativeMethods.cs @@ -3,46 +3,15 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using System.Text; using static Interop; namespace System.Windows.Forms { internal static class UnsafeNativeMethods { - [DllImport(Libraries.User32)] -#pragma warning disable CA1838 // Avoid 'StringBuilder' parameters for P/Invokes - public static extern int GetClassName(HandleRef hwnd, StringBuilder? lpClassName, int nMaxCount); -#pragma warning restore CA1838 // Avoid 'StringBuilder' parameters for P/Invokes - [DllImport(Libraries.Comdlg32, SetLastError = true, CharSet = CharSet.Auto)] public static extern HRESULT PrintDlgEx([In, Out] NativeMethods.PRINTDLGEX lppdex); - [DllImport(Libraries.Kernel32, CharSet = CharSet.Auto, SetLastError = true)] -#pragma warning disable CA1838 // Avoid 'StringBuilder' parameters for P/Invokes - public static extern int GetModuleFileName(HandleRef hModule, StringBuilder buffer, int length); -#pragma warning restore CA1838 // Avoid 'StringBuilder' parameters for P/Invokes - - public static StringBuilder GetModuleFileNameLongPath(HandleRef hModule) - { - StringBuilder buffer = new StringBuilder(PInvoke.MAX_PATH); - int noOfTimes = 1; - int length = 0; - // Iterating by allocating chunk of memory each time we find the length is not sufficient. - // Performance should not be an issue for current MAX_PATH length due to this change. - while (((length = GetModuleFileName(hModule, buffer, buffer.Capacity)) == buffer.Capacity) - && Marshal.GetLastWin32Error() == ERROR.INSUFFICIENT_BUFFER - && buffer.Capacity < PInvoke.MAX_UNICODESTRING_LEN) - { - noOfTimes += 2; // Increasing buffer size by 520 in each iteration. - int capacity = noOfTimes * PInvoke.MAX_PATH < PInvoke.MAX_UNICODESTRING_LEN ? noOfTimes * PInvoke.MAX_PATH : PInvoke.MAX_UNICODESTRING_LEN; - buffer.EnsureCapacity(capacity); - } - - buffer.Length = length; - return buffer; - } - [DllImport(Libraries.Oleacc, ExactSpelling = true, CharSet = CharSet.Auto)] public static extern int CreateStdAccessibleObject(HandleRef hWnd, int objID, ref Guid refiid, [In, Out, MarshalAs(UnmanagedType.Interface)] ref object? pAcc); } diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs new file mode 100644 index 00000000000..4e029eefe44 --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs @@ -0,0 +1,48 @@ +// 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.Buffers; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Windows.Win32 +{ + internal static partial class PInvoke + { + public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) + { + const int INSUFFICIENT_BUFFER = 0x007A; + char[] buffer; + + // Try increased buffer sizes if on longpath-enabled Windows + for (int bufferSize = MAX_PATH; bufferSize <= MaxPath; bufferSize *= 2) + { + buffer = ArrayPool.Shared.Rent(bufferSize); + try + { + uint pathLength; + fixed (char* lpFilename = buffer) + { + pathLength = GetModuleFileName(hModule, lpFilename, (uint)bufferSize); + } + + bool isBufferTooSmall = Marshal.GetLastWin32Error() == INSUFFICIENT_BUFFER; + if (pathLength != 0 && !isBufferTooSmall && bufferSize <= int.MaxValue) + { + return new string(buffer, 0, (int)pathLength); + } + } + finally + { + ArrayPool.Shared.Return(buffer); + } + + // Double check that the buffer is not insanely big + Debug.Assert(bufferSize <= int.MaxValue / 2, "Buffer size approaching int.MaxValue"); + } + + return string.Empty; + } + } +} diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_CLASS_NAME.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_CLASS_NAME.cs new file mode 100644 index 00000000000..4f0f20b1f4d --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_CLASS_NAME.cs @@ -0,0 +1,11 @@ +// 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. + +namespace Windows.Win32 +{ + internal static partial class PInvoke + { + public const int MAX_CLASS_NAME = 256; + } +} diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_PATH.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_PATH.cs index 9209858102a..92d82c67557 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_PATH.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_PATH.cs @@ -2,10 +2,65 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.Win32; + namespace Windows.Win32 { internal static partial class PInvoke { + private const string WINDOWS_FILE_SYSTEM_REGISTRY_KEY = @"SYSTEM\CurrentControlSet\Control\FileSystem"; + private const string WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME = "LongPathsEnabled"; + + /// + /// Cached value for MaxPath. + /// + private static int _maxPath; + private static bool IsMaxPathSet { get; set; } + private static readonly object MaxPathLock = new object(); + internal static bool HasMaxPath => MaxPath == MAX_PATH; public const int MAX_PATH = 260; + + /// + /// Gets the windows max path limit. + /// + internal static int MaxPath + { + get + { + if (!IsMaxPathSet) + { + SetMaxPath(); + } + + return _maxPath; + } + } + + private static void SetMaxPath() + { + lock (MaxPathLock) + { + if (!IsMaxPathSet) + { + bool isMaxPathRestricted = IsLongPathsEnabledRegistry(); + _maxPath = isMaxPathRestricted ? MAX_PATH : int.MaxValue; + IsMaxPathSet = true; + } + } + } + + private static bool IsLongPathsEnabledRegistry() + { + using (RegistryKey? fileSystemKey = Registry.LocalMachine.OpenSubKey(WINDOWS_FILE_SYSTEM_REGISTRY_KEY)) + { + if (fileSystemKey is null) + { + return false; + } + + object? longPathsEnabledValue = fileSystemKey?.GetValue(WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME, 0); + return longPathsEnabledValue is not null && Convert.ToInt32(longPathsEnabledValue) == 1; + } + } } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs index 59793973495..d77aee4d25d 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs @@ -294,8 +294,7 @@ public static string ExecutablePath { if (s_executablePath is null) { - StringBuilder sb = UnsafeNativeMethods.GetModuleFileNameLongPath(NativeMethods.NullHandleRef); - s_executablePath = Path.GetFullPath(sb.ToString()); + s_executablePath = Path.GetFullPath(PInvoke.GetModuleFileNameLongPath(default)); } return s_executablePath; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs index ebb3de972be..741ebf7ab07 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs @@ -2541,7 +2541,7 @@ public int Height set => SetBounds(_x, _y, _width, value, BoundsSpecified.Height); } - internal bool HostedInWin32DialogManager + internal unsafe bool HostedInWin32DialogManager { get { @@ -2556,22 +2556,19 @@ internal bool HostedInWin32DialogManager { HWND parentHandle = PInvoke.GetParent(this); HWND lastParentHandle = parentHandle; - - StringBuilder sb = new StringBuilder(32); - SetState(States.HostedInDialog, false); - + Span buffer = stackalloc char[256]; while (!parentHandle.IsNull) { - int len = UnsafeNativeMethods.GetClassName(new HandleRef(null, lastParentHandle), null, 0); - if (len > sb.Capacity) + int length = 0; + fixed (char* lpClassName = buffer) { - sb.Capacity = len + 5; + length = PInvoke.GetClassName(lastParentHandle, lpClassName, buffer.Length); } - UnsafeNativeMethods.GetClassName(new HandleRef(null, lastParentHandle), sb, sb.Capacity); - - if (sb.ToString() == "#32770") + // class name #32770 + ReadOnlySpan className = "#32770"; + if (MemoryExtensions.Equals(className, buffer.SliceAtFirstNull(), StringComparison.Ordinal)) { SetState(States.HostedInDialog, true); break; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/RichTextBox.cs b/src/System.Windows.Forms/src/System/Windows/Forms/RichTextBox.cs index 11740208e17..34c2c0c95ad 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/RichTextBox.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/RichTextBox.cs @@ -297,8 +297,7 @@ protected override CreateParams CreateParams throw new Win32Exception(lastWin32Error, string.Format(SR.LoadDLLError, richEditControlDllVersion)); } - StringBuilder pathBuilder = UnsafeNativeMethods.GetModuleFileNameLongPath(new HandleRef(null, moduleHandle)); - string path = pathBuilder.ToString(); + string path = PInvoke.GetModuleFileNameLongPath(new HINSTANCE(moduleHandle)); FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(path); Debug.Assert(versionInfo is not null && !string.IsNullOrEmpty(versionInfo.ProductVersion), "Couldn't get the version info for the richedit dll"); diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/TaskDialog.cs b/src/System.Windows.Forms/src/System/Windows/Forms/TaskDialog.cs index bcfd8bb3ada..7819eb53fe1 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/TaskDialog.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/TaskDialog.cs @@ -777,9 +777,7 @@ internal void UpdateCaption(string? caption) // executable's name as title if the string is null or empty. if (TaskDialogPage.IsNativeStringNullOrEmpty(caption)) { - caption = Path.GetFileName( - UnsafeNativeMethods.GetModuleFileNameLongPath(NativeMethods.NullHandleRef) - .ToString()); + caption = Path.GetFileName(PInvoke.GetModuleFileNameLongPath(default)); } User32.SetWindowTextW(_handle, caption); diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs index 5ef97f3a04c..0272c68206a 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs @@ -10584,7 +10584,7 @@ public void RichTextBox_CheckDefaultNativeControlVersions() using var control = new RichTextBox(); control.CreateControl(); - Assert.Contains("RICHEDIT50W", GetClassName(control.Handle), StringComparison.InvariantCultureIgnoreCase); + Assert.Contains("RICHEDIT50W", GetClassName(control.HWND), StringComparison.InvariantCultureIgnoreCase); } [WinFormsFact] @@ -10593,13 +10593,13 @@ public void RichTextBox_CheckRichEditWithVersionCanCreateOldVersions() using (var riched32 = new RichEdit()) { riched32.CreateControl(); - Assert.Contains(".RichEdit.", GetClassName(riched32.Handle), StringComparison.InvariantCultureIgnoreCase); + Assert.Contains(".RichEdit.", GetClassName(riched32.HWND), StringComparison.InvariantCultureIgnoreCase); } using (var riched20 = new RichEdit20W()) { riched20.CreateControl(); - Assert.Contains(".RichEdit20W.", GetClassName(riched20.Handle), StringComparison.InvariantCultureIgnoreCase); + Assert.Contains(".RichEdit20W.", GetClassName(riched20.HWND), StringComparison.InvariantCultureIgnoreCase); string rtfString = @"{\rtf1\ansi{" + @"The next line\par " + @@ -10791,12 +10791,15 @@ private class SubRichTextBox : RichTextBox public new void WndProc(ref Message m) => base.WndProc(ref m); } - private static string GetClassName(IntPtr hWnd) + private static unsafe string GetClassName(HWND hWnd) { - const int MaxClassName = 256; - StringBuilder sb = new StringBuilder(MaxClassName); - UnsafeNativeMethods.GetClassName(new HandleRef(null, hWnd), sb, MaxClassName); - return sb.ToString(); + Span buf = stackalloc char[PInvoke.MAX_CLASS_NAME]; + fixed (char* lpClassName = buf) + { + _ = PInvoke.GetClassName(hWnd, lpClassName, buf.Length); + } + + return buf.SliceAtFirstNull().ToString(); } /// From 73ec2f9af5b7f9c08ba05aa7212d2f232c3f7698 Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Fri, 4 Nov 2022 11:46:14 +1000 Subject: [PATCH 02/15] Fix build errors --- .../src/System/Windows/Forms/Application.cs | 6 +----- .../src/System/Windows/Forms/Control.cs | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs index d77aee4d25d..8782f369518 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs @@ -292,11 +292,7 @@ public static string ExecutablePath { get { - if (s_executablePath is null) - { - s_executablePath = Path.GetFullPath(PInvoke.GetModuleFileNameLongPath(default)); - } - + s_executablePath ??= Path.GetFullPath(PInvoke.GetModuleFileNameLongPath(default)); return s_executablePath; } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs index 741ebf7ab07..3963f522a47 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs @@ -12,7 +12,6 @@ using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; -using System.Text; using System.Windows.Forms.Automation; using System.Windows.Forms.Layout; using Microsoft.Win32; From b3860a21e55d8794b97ab80adef4e74ef5393f6c Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Sat, 5 Nov 2022 09:07:38 +1000 Subject: [PATCH 03/15] changes from review --- .../PInvoke.GetModuleFileNameLongPath.cs | 13 +++-- .../src/Windows/Win32/PInvoke.MAX_PATH.cs | 53 ------------------- .../src/System/Windows/Forms/Application.cs | 2 +- .../ComboBox.AutoCompleteDropDownFinder.cs | 5 +- .../src/System/Windows/Forms/Control.cs | 4 +- .../src/System/Windows/Forms/TaskDialog.cs | 2 +- .../System/Windows/Forms/RichTextBoxTests.cs | 19 +++---- 7 files changed, 22 insertions(+), 76 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs index 4e029eefe44..943c0d53805 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs @@ -12,11 +12,10 @@ internal static partial class PInvoke { public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) { - const int INSUFFICIENT_BUFFER = 0x007A; char[] buffer; - - // Try increased buffer sizes if on longpath-enabled Windows - for (int bufferSize = MAX_PATH; bufferSize <= MaxPath; bufferSize *= 2) + int bufferSize = 4096; + // Allocate increasingly larger portions of memory until successful or we hit short.maxvalue + for (int i = 1; bufferSize <= short.MaxValue; i++, bufferSize = 4096 * i) { buffer = ArrayPool.Shared.Rent(bufferSize); try @@ -24,11 +23,11 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) uint pathLength; fixed (char* lpFilename = buffer) { - pathLength = GetModuleFileName(hModule, lpFilename, (uint)bufferSize); + pathLength = GetModuleFileName(hModule, lpFilename, (uint)buffer.Length); } - bool isBufferTooSmall = Marshal.GetLastWin32Error() == INSUFFICIENT_BUFFER; - if (pathLength != 0 && !isBufferTooSmall && bufferSize <= int.MaxValue) + bool isBufferTooSmall = (WIN32_ERROR)Marshal.GetLastWin32Error() == WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER; + if (pathLength > 0 && (pathLength < buffer.Length || Marshal.GetLastWin32Error() != INSUFFICENT_BUFFER)) { return new string(buffer, 0, (int)pathLength); } diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_PATH.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_PATH.cs index 92d82c67557..3199857ddf6 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_PATH.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_PATH.cs @@ -8,59 +8,6 @@ namespace Windows.Win32 { internal static partial class PInvoke { - private const string WINDOWS_FILE_SYSTEM_REGISTRY_KEY = @"SYSTEM\CurrentControlSet\Control\FileSystem"; - private const string WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME = "LongPathsEnabled"; - - /// - /// Cached value for MaxPath. - /// - private static int _maxPath; - private static bool IsMaxPathSet { get; set; } - private static readonly object MaxPathLock = new object(); - internal static bool HasMaxPath => MaxPath == MAX_PATH; public const int MAX_PATH = 260; - - /// - /// Gets the windows max path limit. - /// - internal static int MaxPath - { - get - { - if (!IsMaxPathSet) - { - SetMaxPath(); - } - - return _maxPath; - } - } - - private static void SetMaxPath() - { - lock (MaxPathLock) - { - if (!IsMaxPathSet) - { - bool isMaxPathRestricted = IsLongPathsEnabledRegistry(); - _maxPath = isMaxPathRestricted ? MAX_PATH : int.MaxValue; - IsMaxPathSet = true; - } - } - } - - private static bool IsLongPathsEnabledRegistry() - { - using (RegistryKey? fileSystemKey = Registry.LocalMachine.OpenSubKey(WINDOWS_FILE_SYSTEM_REGISTRY_KEY)) - { - if (fileSystemKey is null) - { - return false; - } - - object? longPathsEnabledValue = fileSystemKey?.GetValue(WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME, 0); - return longPathsEnabledValue is not null && Convert.ToInt32(longPathsEnabledValue) == 1; - } - } } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs index 8782f369518..cfc2c17a5fd 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs @@ -292,7 +292,7 @@ public static string ExecutablePath { get { - s_executablePath ??= Path.GetFullPath(PInvoke.GetModuleFileNameLongPath(default)); + s_executablePath ??= Path.GetFullPath(PInvoke.GetModuleFileNameLongPath(HINSTANCE.Null)); return s_executablePath; } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.AutoCompleteDropDownFinder.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.AutoCompleteDropDownFinder.cs index f234b52e358..8124fd171c8 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.AutoCompleteDropDownFinder.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.AutoCompleteDropDownFinder.cs @@ -41,13 +41,12 @@ internal void FindDropDowns(bool subclass) private unsafe BOOL Callback(HWND hwnd) { Span buffer = stackalloc char[AutoCompleteClassName.Length + 2]; - fixed (char* b = buffer) { - int count = PInvoke.GetClassName(hwnd, (PWSTR)b, buffer.Length); + int length = PInvoke.GetClassName(hwnd, (PWSTR)b, buffer.Length); // Check class name and see if it's visible - if (count == AutoCompleteClassName.Length && buffer.StartsWith(AutoCompleteClassName)) + if (length == AutoCompleteClassName.Length && buffer.StartsWith(AutoCompleteClassName)) { ACNativeWindow.RegisterACWindow(hwnd, _shouldSubClass); } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs index 3963f522a47..fc1b71b1a13 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs @@ -2556,7 +2556,7 @@ internal unsafe bool HostedInWin32DialogManager HWND parentHandle = PInvoke.GetParent(this); HWND lastParentHandle = parentHandle; SetState(States.HostedInDialog, false); - Span buffer = stackalloc char[256]; + Span buffer = stackalloc char[PInvoke.MAX_CLASS_NAME]; while (!parentHandle.IsNull) { int length = 0; @@ -2567,7 +2567,7 @@ internal unsafe bool HostedInWin32DialogManager // class name #32770 ReadOnlySpan className = "#32770"; - if (MemoryExtensions.Equals(className, buffer.SliceAtFirstNull(), StringComparison.Ordinal)) + if (className.Equals(buffer.Slice(0, length), StringComparison.Ordinal)) { SetState(States.HostedInDialog, true); break; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/TaskDialog.cs b/src/System.Windows.Forms/src/System/Windows/Forms/TaskDialog.cs index 7819eb53fe1..0916b1ab798 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/TaskDialog.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/TaskDialog.cs @@ -777,7 +777,7 @@ internal void UpdateCaption(string? caption) // executable's name as title if the string is null or empty. if (TaskDialogPage.IsNativeStringNullOrEmpty(caption)) { - caption = Path.GetFileName(PInvoke.GetModuleFileNameLongPath(default)); + caption = Path.GetFileName(PInvoke.GetModuleFileNameLongPath(HINSTANCE.Null)); } User32.SetWindowTextW(_handle, caption); diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs index 0272c68206a..8ef62bdcdc6 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs @@ -10584,7 +10584,7 @@ public void RichTextBox_CheckDefaultNativeControlVersions() using var control = new RichTextBox(); control.CreateControl(); - Assert.Contains("RICHEDIT50W", GetClassName(control.HWND), StringComparison.InvariantCultureIgnoreCase); + Assert.Contains("RICHEDIT50W", GetClassName(control.HWND), StringComparison.Ordinal); } [WinFormsFact] @@ -10593,13 +10593,13 @@ public void RichTextBox_CheckRichEditWithVersionCanCreateOldVersions() using (var riched32 = new RichEdit()) { riched32.CreateControl(); - Assert.Contains(".RichEdit.", GetClassName(riched32.HWND), StringComparison.InvariantCultureIgnoreCase); + Assert.Contains(".RichEdit.", GetClassName(riched32.HWND), StringComparison.Ordinal); } using (var riched20 = new RichEdit20W()) { riched20.CreateControl(); - Assert.Contains(".RichEdit20W.", GetClassName(riched20.HWND), StringComparison.InvariantCultureIgnoreCase); + Assert.Contains(".RichEdit20W.", GetClassName(riched20.HWND), StringComparison.Ordinal); string rtfString = @"{\rtf1\ansi{" + @"The next line\par " + @@ -10615,8 +10615,8 @@ public void RichTextBox_CheckRichEditWithVersionCanCreateOldVersions() Assert.Equal(riched20.Text, richTextBox.Text); Assert.Equal(richTextBox.Text.Length, richTextBox.TextLength); - int startOfIs = riched20.Text.IndexOf("is"); - int endOfHidden = riched20.Text.IndexOf("hidden") + "hidden".Length; + int startOfIs = riched20.Text.IndexOf("is", StringComparison.Ordinal); + int endOfHidden = riched20.Text.IndexOf("hidden", StringComparison.Ordinal) + "hidden".Length; richTextBox.Select(startOfIs, endOfHidden - startOfIs); Assert.Equal("is ###NOT### hidden", richTextBox.SelectedText); } @@ -10793,13 +10793,14 @@ private class SubRichTextBox : RichTextBox private static unsafe string GetClassName(HWND hWnd) { - Span buf = stackalloc char[PInvoke.MAX_CLASS_NAME]; - fixed (char* lpClassName = buf) + int length = 0; + Span buffer = stackalloc char[PInvoke.MAX_CLASS_NAME]; + fixed (char* lpClassName = buffer) { - _ = PInvoke.GetClassName(hWnd, lpClassName, buf.Length); + length = PInvoke.GetClassName(hWnd, lpClassName, buffer.Length); } - return buf.SliceAtFirstNull().ToString(); + return new string(buffer.Slice(0, length)); } /// From 199bfb5da65153d3d798ed02b5eec3e8d6e7f56a Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Sat, 5 Nov 2022 09:09:51 +1000 Subject: [PATCH 04/15] more changes from review --- .../PInvoke.GetModuleFileNameLongPath.cs | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs index 943c0d53805..fb0909690b7 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs @@ -18,25 +18,20 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) for (int i = 1; bufferSize <= short.MaxValue; i++, bufferSize = 4096 * i) { buffer = ArrayPool.Shared.Rent(bufferSize); - try + uint pathLength; + fixed (char* lpFilename = buffer) { - uint pathLength; - fixed (char* lpFilename = buffer) - { - pathLength = GetModuleFileName(hModule, lpFilename, (uint)buffer.Length); - } - - bool isBufferTooSmall = (WIN32_ERROR)Marshal.GetLastWin32Error() == WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER; - if (pathLength > 0 && (pathLength < buffer.Length || Marshal.GetLastWin32Error() != INSUFFICENT_BUFFER)) - { - return new string(buffer, 0, (int)pathLength); - } + pathLength = GetModuleFileName(hModule, lpFilename, (uint)buffer.Length); } - finally + + if (pathLength > 0 && (pathLength < buffer.Length || (WIN32_ERROR)Marshal.GetLastWin32Error() == WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER)) { - ArrayPool.Shared.Return(buffer); + return new string(buffer, 0, (int)pathLength); } + // Return to array pool + ArrayPool.Shared.Return(buffer); + // Double check that the buffer is not insanely big Debug.Assert(bufferSize <= int.MaxValue / 2, "Buffer size approaching int.MaxValue"); } From 26a3f15ac37336c3befa6c265b4442599d4d31f1 Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Sat, 5 Nov 2022 09:22:35 +1000 Subject: [PATCH 05/15] fix usings --- .../src/Windows/Win32/PInvoke.MAX_PATH.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_PATH.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_PATH.cs index 3199857ddf6..9209858102a 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_PATH.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_PATH.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Win32; - namespace Windows.Win32 { internal static partial class PInvoke From 84f87f27500b656128d1eecef43395365d86f390 Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Sun, 6 Nov 2022 07:01:02 +1000 Subject: [PATCH 06/15] Changes from review --- .../PInvoke.GetModuleFileNameLongPath.cs | 21 ++++++++++++------- .../Windows/Win32/PInvoke.MAX_CLASS_NAME.cs | 11 ---------- .../src/Windows/Win32/PInvoke.MaxClassName.cs | 15 +++++++++++++ .../src/System/Windows/Forms/Application.cs | 10 ++------- .../src/System/Windows/Forms/Control.cs | 8 ++++--- .../System/Windows/Forms/RichTextBoxTests.cs | 4 ++-- 6 files changed, 37 insertions(+), 32 deletions(-) delete mode 100644 src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_CLASS_NAME.cs create mode 100644 src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MaxClassName.cs diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs index fb0909690b7..8716e0df6ef 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Buffers; -using System.Diagnostics; using System.Runtime.InteropServices; namespace Windows.Win32 @@ -14,7 +13,7 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) { char[] buffer; int bufferSize = 4096; - // Allocate increasingly larger portions of memory until successful or we hit short.maxvalue + // Allocate increasingly larger portions of memory until successful or we hit short.maxvalue. for (int i = 1; bufferSize <= short.MaxValue; i++, bufferSize = 4096 * i) { buffer = ArrayPool.Shared.Rent(bufferSize); @@ -24,16 +23,22 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) pathLength = GetModuleFileName(hModule, lpFilename, (uint)buffer.Length); } - if (pathLength > 0 && (pathLength < buffer.Length || (WIN32_ERROR)Marshal.GetLastWin32Error() == WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER)) + if (pathLength == 0) { - return new string(buffer, 0, (int)pathLength); + ArrayPool.Shared.Return(buffer); + return string.Empty; } - // Return to array pool - ArrayPool.Shared.Return(buffer); + if (pathLength > 0 && (pathLength < buffer.Length || (WIN32_ERROR)Marshal.GetLastWin32Error() != WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER)) + { + // Get return value and return buffer to array pool. + string returnValue = new string(buffer, 0, (int)pathLength); + ArrayPool.Shared.Return(buffer); + return returnValue; + } - // Double check that the buffer is not insanely big - Debug.Assert(bufferSize <= int.MaxValue / 2, "Buffer size approaching int.MaxValue"); + // buffer was too small, return to array pool. + ArrayPool.Shared.Return(buffer); } return string.Empty; diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_CLASS_NAME.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_CLASS_NAME.cs deleted file mode 100644 index 4f0f20b1f4d..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MAX_CLASS_NAME.cs +++ /dev/null @@ -1,11 +0,0 @@ -// 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. - -namespace Windows.Win32 -{ - internal static partial class PInvoke - { - public const int MAX_CLASS_NAME = 256; - } -} diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MaxClassName.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MaxClassName.cs new file mode 100644 index 00000000000..3925ff8ac68 --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MaxClassName.cs @@ -0,0 +1,15 @@ +// 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. + +namespace Windows.Win32 +{ + internal static partial class PInvoke + { + /// + /// The maximum length for lpszClassName is 256. If lpszClassName is greater than the maximum length, the RegisterClassEx function will fail. + /// Read more on docs.microsoft.com. + /// + public const int MaxClassName = 256; + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs index cfc2c17a5fd..f32a39f2d13 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs @@ -288,14 +288,8 @@ internal static bool CustomThreadExceptionHandlerAttached /// /// Gets the path for the executable file that started the application. /// - public static string ExecutablePath - { - get - { - s_executablePath ??= Path.GetFullPath(PInvoke.GetModuleFileNameLongPath(HINSTANCE.Null)); - return s_executablePath; - } - } + public static string ExecutablePath => + s_executablePath ??= PInvoke.GetModuleFileNameLongPath(HINSTANCE.Null); /// /// Gets the current mode for the process. diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs index fc1b71b1a13..25dce5fecc3 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs @@ -2556,7 +2556,8 @@ internal unsafe bool HostedInWin32DialogManager HWND parentHandle = PInvoke.GetParent(this); HWND lastParentHandle = parentHandle; SetState(States.HostedInDialog, false); - Span buffer = stackalloc char[PInvoke.MAX_CLASS_NAME]; + Span buffer = stackalloc char[PInvoke.MaxClassName]; + while (!parentHandle.IsNull) { int length = 0; @@ -2565,9 +2566,10 @@ internal unsafe bool HostedInWin32DialogManager length = PInvoke.GetClassName(lastParentHandle, lpClassName, buffer.Length); } - // class name #32770 + // #32770 is the standard windows dialog class name + // https://learn.microsoft.com/en-au/windows/win32/winmsg/about-window-classes#system-classes ReadOnlySpan className = "#32770"; - if (className.Equals(buffer.Slice(0, length), StringComparison.Ordinal)) + if (className.Equals(buffer[..length], StringComparison.Ordinal)) { SetState(States.HostedInDialog, true); break; diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs index 8ef62bdcdc6..72ded647c06 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs @@ -10794,13 +10794,13 @@ private class SubRichTextBox : RichTextBox private static unsafe string GetClassName(HWND hWnd) { int length = 0; - Span buffer = stackalloc char[PInvoke.MAX_CLASS_NAME]; + Span buffer = stackalloc char[PInvoke.MaxClassName]; fixed (char* lpClassName = buffer) { length = PInvoke.GetClassName(hWnd, lpClassName, buffer.Length); } - return new string(buffer.Slice(0, length)); + return new string(buffer[..length]); } /// From 4b51246950555e5aff95c5a2411acdf194c9e59f Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Tue, 8 Nov 2022 08:17:27 +1000 Subject: [PATCH 07/15] changes from review --- .../PInvoke.GetModuleFileNameLongPath.cs | 32 ++++++++++++++++++- .../src/Windows/Win32/PInvoke.MaxClassName.cs | 2 +- .../src/System/Windows/Forms/Control.cs | 2 +- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs index 8716e0df6ef..c118b27a0fb 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs @@ -11,6 +11,11 @@ internal static partial class PInvoke { public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) { + if (TryGetModuleFileName(hModule, out char[] path)) + { + return new string(path); + } + char[] buffer; int bufferSize = 4096; // Allocate increasingly larger portions of memory until successful or we hit short.maxvalue. @@ -29,7 +34,8 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) return string.Empty; } - if (pathLength > 0 && (pathLength < buffer.Length || (WIN32_ERROR)Marshal.GetLastWin32Error() != WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER)) + // If the length equals the buffer size we need to check to see if we were told the buffer was insufficient (it was trimmed) + if (pathLength < buffer.Length - 1 || (WIN32_ERROR)Marshal.GetLastWin32Error() != WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER) { // Get return value and return buffer to array pool. string returnValue = new string(buffer, 0, (int)pathLength); @@ -43,5 +49,29 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) return string.Empty; } + + private static unsafe bool TryGetModuleFileName(HINSTANCE hModule, out char[] bufferOut) + { + Span buffer = stackalloc char[MAX_PATH]; + bufferOut = Array.Empty(); + uint pathLength; + fixed (char* lpFilename = buffer) + { + pathLength = GetModuleFileName(hModule, lpFilename, (uint)buffer.Length); + } + + if (pathLength == 0 || (WIN32_ERROR)Marshal.GetLastWin32Error() == WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER) + { + return false; + } + + if (pathLength < buffer.Length) + { + bufferOut = buffer[..(int)pathLength].ToArray(); + return true; + } + + return false; + } } } diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MaxClassName.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MaxClassName.cs index 3925ff8ac68..afdf0db60fd 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MaxClassName.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.MaxClassName.cs @@ -8,7 +8,7 @@ internal static partial class PInvoke { /// /// The maximum length for lpszClassName is 256. If lpszClassName is greater than the maximum length, the RegisterClassEx function will fail. - /// Read more on docs.microsoft.com. + /// Read more on docs.microsoft.com. /// public const int MaxClassName = 256; } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs index 25dce5fecc3..242cbb3a41b 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs @@ -2567,7 +2567,7 @@ internal unsafe bool HostedInWin32DialogManager } // #32770 is the standard windows dialog class name - // https://learn.microsoft.com/en-au/windows/win32/winmsg/about-window-classes#system-classes + // https://learn.microsoft.com/windows/win32/winmsg/about-window-classes#system-classes ReadOnlySpan className = "#32770"; if (className.Equals(buffer[..length], StringComparison.Ordinal)) { From 1af72908879a2f5b9018ca02d7f4d489b7c037a9 Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Wed, 9 Nov 2022 15:38:08 +1000 Subject: [PATCH 08/15] Convert GetWindowText usage --- .../src/Microsoft/VisualBasic/Interaction.vb | 17 +++-- .../src/NativeMethods.txt | 2 + .../src/Properties/AssemblyInfo.cs | 1 + .../Windows/Win32/PInvoke.GetWindowText.cs | 62 +++++++++++++++++++ 4 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetWindowText.cs diff --git a/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/Interaction.vb b/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/Interaction.vb index bf916f7ffce..62539dd19de 100644 --- a/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/Interaction.vb +++ b/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/Interaction.vb @@ -10,6 +10,8 @@ Imports System.Windows.Forms Imports Microsoft.VisualBasic.CompilerServices Imports Microsoft.VisualBasic.CompilerServices.ExceptionUtils Imports Microsoft.VisualBasic.CompilerServices.Utils +Imports Windows.Win32 +Imports Windows.Win32.Foundation Namespace Microsoft.VisualBasic @@ -136,25 +138,21 @@ Namespace Microsoft.VisualBasic Public Sub AppActivateByTitle(Title As String) 'As an optimization, we will only check the UI permission once we actually know we found the app to activate - we'll do that in AppActivateHelper Dim WindowHandle As IntPtr = NativeMethods.FindWindow(Nothing, Title) 'see if we can find the window using an exact match on the title - Const MAX_TITLE_LENGTH As Integer = 511 ' if no match, search through all parent windows If IntPtr.op_Equality(WindowHandle, IntPtr.Zero) Then Dim AppTitle As String ' Old implementation uses MAX_TITLE_LENGTH characters, INCLUDING NULL character. ' Interop code will extend string builder to handle NULL character. - Dim AppTitleBuilder As New StringBuilder(MAX_TITLE_LENGTH) - Dim AppTitleLength As Integer - Dim TitleLength As Integer = Len(Title) + Dim TitleLength As Integer = Title.Length 'Loop through all children of the desktop WindowHandle = NativeMethods.GetWindow(NativeMethods.GetDesktopWindow(), NativeTypes.GW_CHILD) Do While IntPtr.op_Inequality(WindowHandle, IntPtr.Zero) ' get the window caption and test for a left-aligned substring - AppTitleLength = NativeMethods.GetWindowText(WindowHandle, AppTitleBuilder, AppTitleBuilder.Capacity) - AppTitle = AppTitleBuilder.ToString() + AppTitle = PInvoke.GetWindowText(CType(WindowHandle, HWND)) - If AppTitleLength >= TitleLength Then + If AppTitle.Length >= TitleLength Then If String.Compare(AppTitle, 0, Title, 0, TitleLength, StringComparison.OrdinalIgnoreCase) = 0 Then Exit Do 'found one End If @@ -170,10 +168,9 @@ Namespace Microsoft.VisualBasic Do While IntPtr.op_Inequality(WindowHandle, IntPtr.Zero) ' get the window caption and test for a right-aligned substring - AppTitleLength = NativeMethods.GetWindowText(WindowHandle, AppTitleBuilder, AppTitleBuilder.Capacity) - AppTitle = AppTitleBuilder.ToString() + AppTitle = PInvoke.GetWindowText(CType(WindowHandle, HWND)) - If AppTitleLength >= TitleLength Then + If AppTitle.Length >= TitleLength Then If String.Compare(Right(AppTitle, TitleLength), 0, Title, 0, TitleLength, StringComparison.OrdinalIgnoreCase) = 0 Then Exit Do 'found a match End If diff --git a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt index 9c2a39ae58a..39d24b0850f 100644 --- a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt +++ b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt @@ -241,6 +241,8 @@ GetWindow GetWindowDpiAwarenessContext GetWindowPlacement GetWindowRect +GetWindowText +GetWindowTextLength GetWindowThreadProcessId GetWorldTransform GlobalAlloc diff --git a/src/System.Windows.Forms.Primitives/src/Properties/AssemblyInfo.cs b/src/System.Windows.Forms.Primitives/src/Properties/AssemblyInfo.cs index d59b1128c21..cc7daf473bd 100644 --- a/src/System.Windows.Forms.Primitives/src/Properties/AssemblyInfo.cs +++ b/src/System.Windows.Forms.Primitives/src/Properties/AssemblyInfo.cs @@ -6,6 +6,7 @@ [assembly: System.Runtime.InteropServices.ComVisible(false)] +[assembly: InternalsVisibleTo("Microsoft.VisualBasic.Forms, PublicKey=00000000000000000400000000000000")] [assembly: InternalsVisibleTo("System.Windows.Forms, PublicKey=00000000000000000400000000000000")] [assembly: InternalsVisibleTo("System.Windows.Forms.Design, PublicKey=00000000000000000400000000000000")] [assembly: InternalsVisibleTo("System.Windows.Forms.Design.Editors, PublicKey=00000000000000000400000000000000")] diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetWindowText.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetWindowText.cs new file mode 100644 index 00000000000..e0b73d65aed --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetWindowText.cs @@ -0,0 +1,62 @@ +// 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.Buffers; + +namespace Windows.Win32 +{ + internal static partial class PInvoke + { + //const int MAX_TITLE_LENGTH = 511; + + public static unsafe string GetWindowText(HWND hWnd) + { + int length = GetWindowTextLength(hWnd); + + // If the window has no text, the return value is zero. + if (length == 0) + { + return string.Empty; + } + + // Stackalloc for smaller values + if (length <= 1024) + { + Span buffer = stackalloc char[length + 1]; + fixed (char* lpString = buffer) + { + length = GetWindowText(hWnd, (PWSTR)lpString, buffer.Length); + } + + // If the window has no title bar or text, if the title bar is empty, + // or if the window or control handle is invalid, the return value is zero + if (length == 0) + { + return string.Empty; + } + } + + // Use arraypool for larger than 1024 characters. + char[] lBuffer; + lBuffer = ArrayPool.Shared.Rent(length + 1); + int pathLength; + fixed (char* lpString = lBuffer) + { + pathLength = GetWindowText(hWnd, (PWSTR)lpString, lBuffer.Length); + } + + // If the window has no title bar or text, if the title bar is empty, + // or if the window or control handle is invalid, the return value is zero + if (pathLength == 0) + { + ArrayPool.Shared.Return(lBuffer); + return string.Empty; + } + + // Get return value and return buffer to array pool. + string returnValue = new string(lBuffer, 0, pathLength); + ArrayPool.Shared.Return(lBuffer); + return returnValue; + } + } +} From fb238eaba80f589de268626a8459129642e2cc57 Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Thu, 10 Nov 2022 07:51:17 +1000 Subject: [PATCH 09/15] changes from review to avoid char[] allocation --- .../PInvoke.GetModuleFileNameLongPath.cs | 62 ++++++++----------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs index c118b27a0fb..bb4a6ad8c41 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs @@ -11,67 +11,57 @@ internal static partial class PInvoke { public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) { - if (TryGetModuleFileName(hModule, out char[] path)) + Span buffer = stackalloc char[MAX_PATH]; + uint pathLength; + bool isBufferTooSmall = false; + fixed (char* lpFilename = buffer) + { + pathLength = GetModuleFileName(hModule, lpFilename, (uint)buffer.Length); + isBufferTooSmall = (WIN32_ERROR)Marshal.GetLastWin32Error() == WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER; + } + + if (pathLength == 0) { - return new string(path); + return string.Empty; } - char[] buffer; + if (pathLength < buffer.Length - 1 && !isBufferTooSmall) + { + return new string(buffer[..(int)pathLength]); + } + + char[] lbuffer; int bufferSize = 4096; // Allocate increasingly larger portions of memory until successful or we hit short.maxvalue. for (int i = 1; bufferSize <= short.MaxValue; i++, bufferSize = 4096 * i) { - buffer = ArrayPool.Shared.Rent(bufferSize); - uint pathLength; - fixed (char* lpFilename = buffer) + lbuffer = ArrayPool.Shared.Rent(bufferSize); + fixed (char* lpFilename = lbuffer) { - pathLength = GetModuleFileName(hModule, lpFilename, (uint)buffer.Length); + pathLength = GetModuleFileName(hModule, lpFilename, (uint)lbuffer.Length); + isBufferTooSmall = (WIN32_ERROR)Marshal.GetLastWin32Error() == WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER; } if (pathLength == 0) { - ArrayPool.Shared.Return(buffer); + ArrayPool.Shared.Return(lbuffer); return string.Empty; } // If the length equals the buffer size we need to check to see if we were told the buffer was insufficient (it was trimmed) - if (pathLength < buffer.Length - 1 || (WIN32_ERROR)Marshal.GetLastWin32Error() != WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER) + if (pathLength < lbuffer.Length - 1 && !isBufferTooSmall) { // Get return value and return buffer to array pool. - string returnValue = new string(buffer, 0, (int)pathLength); - ArrayPool.Shared.Return(buffer); + string returnValue = new string(lbuffer, 0, (int)pathLength); + ArrayPool.Shared.Return(lbuffer); return returnValue; } // buffer was too small, return to array pool. - ArrayPool.Shared.Return(buffer); + ArrayPool.Shared.Return(lbuffer); } return string.Empty; } - - private static unsafe bool TryGetModuleFileName(HINSTANCE hModule, out char[] bufferOut) - { - Span buffer = stackalloc char[MAX_PATH]; - bufferOut = Array.Empty(); - uint pathLength; - fixed (char* lpFilename = buffer) - { - pathLength = GetModuleFileName(hModule, lpFilename, (uint)buffer.Length); - } - - if (pathLength == 0 || (WIN32_ERROR)Marshal.GetLastWin32Error() == WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER) - { - return false; - } - - if (pathLength < buffer.Length) - { - bufferOut = buffer[..(int)pathLength].ToArray(); - return true; - } - - return false; - } } } From 09f30732765cc717569aad02342fbd0d77e56145 Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Thu, 10 Nov 2022 09:19:13 +1000 Subject: [PATCH 10/15] changes from review --- .../src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs index bb4a6ad8c41..be28244a006 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs @@ -13,11 +13,9 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) { Span buffer = stackalloc char[MAX_PATH]; uint pathLength; - bool isBufferTooSmall = false; fixed (char* lpFilename = buffer) { pathLength = GetModuleFileName(hModule, lpFilename, (uint)buffer.Length); - isBufferTooSmall = (WIN32_ERROR)Marshal.GetLastWin32Error() == WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER; } if (pathLength == 0) @@ -25,7 +23,7 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) return string.Empty; } - if (pathLength < buffer.Length - 1 && !isBufferTooSmall) + if (pathLength < buffer.Length - 1) { return new string(buffer[..(int)pathLength]); } @@ -39,7 +37,6 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) fixed (char* lpFilename = lbuffer) { pathLength = GetModuleFileName(hModule, lpFilename, (uint)lbuffer.Length); - isBufferTooSmall = (WIN32_ERROR)Marshal.GetLastWin32Error() == WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER; } if (pathLength == 0) @@ -49,7 +46,7 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) } // If the length equals the buffer size we need to check to see if we were told the buffer was insufficient (it was trimmed) - if (pathLength < lbuffer.Length - 1 && !isBufferTooSmall) + if (pathLength < lbuffer.Length - 1) { // Get return value and return buffer to array pool. string returnValue = new string(lbuffer, 0, (int)pathLength); From 1a4999f6b0e549ce866ba71b25b86eca52c730a3 Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Thu, 10 Nov 2022 09:24:51 +1000 Subject: [PATCH 11/15] more changes from review --- .../src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs index be28244a006..157dfdbcf9e 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Buffers; -using System.Runtime.InteropServices; namespace Windows.Win32 { @@ -23,7 +22,7 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) return string.Empty; } - if (pathLength < buffer.Length - 1) + if (pathLength < buffer.Length) { return new string(buffer[..(int)pathLength]); } @@ -46,7 +45,7 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) } // If the length equals the buffer size we need to check to see if we were told the buffer was insufficient (it was trimmed) - if (pathLength < lbuffer.Length - 1) + if (pathLength < lbuffer.Length) { // Get return value and return buffer to array pool. string returnValue = new string(lbuffer, 0, (int)pathLength); From 06ee4059ad3089ea33f0701aaea73b3a6effc6e6 Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Thu, 10 Nov 2022 10:43:37 +1000 Subject: [PATCH 12/15] more changes from review --- .../PInvoke.GetModuleFileNameLongPath.cs | 3 +- .../Windows/Win32/PInvoke.GetWindowText.cs | 51 +++---------------- 2 files changed, 9 insertions(+), 45 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs index 157dfdbcf9e..267c3aef267 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Windows.Forms.Primitives.Resources; namespace Windows.Win32 { @@ -57,7 +58,7 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) ArrayPool.Shared.Return(lbuffer); } - return string.Empty; + throw new InvalidOperationException(); } } } diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetWindowText.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetWindowText.cs index e0b73d65aed..9f42f521771 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetWindowText.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetWindowText.cs @@ -1,62 +1,25 @@ // 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.Buffers; namespace Windows.Win32 { internal static partial class PInvoke { - //const int MAX_TITLE_LENGTH = 511; - public static unsafe string GetWindowText(HWND hWnd) { - int length = GetWindowTextLength(hWnd); - - // If the window has no text, the return value is zero. - if (length == 0) - { - return string.Empty; - } - - // Stackalloc for smaller values - if (length <= 1024) - { - Span buffer = stackalloc char[length + 1]; - fixed (char* lpString = buffer) - { - length = GetWindowText(hWnd, (PWSTR)lpString, buffer.Length); - } - - // If the window has no title bar or text, if the title bar is empty, - // or if the window or control handle is invalid, the return value is zero - if (length == 0) - { - return string.Empty; - } - } - - // Use arraypool for larger than 1024 characters. - char[] lBuffer; - lBuffer = ArrayPool.Shared.Rent(length + 1); - int pathLength; - fixed (char* lpString = lBuffer) + // Old implementation uses MAX_TITLE_LENGTH characters + const int MAX_TITLE_LENGTH = 512; + int length; + Span buffer = stackalloc char[MAX_TITLE_LENGTH]; + fixed (char* lpString = buffer) { - pathLength = GetWindowText(hWnd, (PWSTR)lpString, lBuffer.Length); + length = GetWindowText(hWnd, (PWSTR)lpString, buffer.Length); } // If the window has no title bar or text, if the title bar is empty, // or if the window or control handle is invalid, the return value is zero - if (pathLength == 0) - { - ArrayPool.Shared.Return(lBuffer); - return string.Empty; - } - - // Get return value and return buffer to array pool. - string returnValue = new string(lBuffer, 0, pathLength); - ArrayPool.Shared.Return(lBuffer); - return returnValue; + return length == 0 ? string.Empty : new string(buffer[..length]); } } } From 01345e63d3e5a7152b4c27a23358f43a95006c74 Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Thu, 10 Nov 2022 11:47:55 +1000 Subject: [PATCH 13/15] remove and sort usings --- .../src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs index 267c3aef267..b1f39b9e3b3 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Buffers; -using System.Windows.Forms.Primitives.Resources; namespace Windows.Win32 { From 22c139ed13b067cad899e951f3a9b7d24d3988cb Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Fri, 11 Nov 2022 06:45:01 +1000 Subject: [PATCH 14/15] remove vb changes --- .../src/Microsoft/VisualBasic/Interaction.vb | 17 +++++++------ .../src/NativeMethods.txt | 2 -- .../src/Properties/AssemblyInfo.cs | 1 - .../Windows/Win32/PInvoke.GetWindowText.cs | 25 ------------------- 4 files changed, 10 insertions(+), 35 deletions(-) delete mode 100644 src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetWindowText.cs diff --git a/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/Interaction.vb b/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/Interaction.vb index 62539dd19de..bf916f7ffce 100644 --- a/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/Interaction.vb +++ b/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/Interaction.vb @@ -10,8 +10,6 @@ Imports System.Windows.Forms Imports Microsoft.VisualBasic.CompilerServices Imports Microsoft.VisualBasic.CompilerServices.ExceptionUtils Imports Microsoft.VisualBasic.CompilerServices.Utils -Imports Windows.Win32 -Imports Windows.Win32.Foundation Namespace Microsoft.VisualBasic @@ -138,21 +136,25 @@ Namespace Microsoft.VisualBasic Public Sub AppActivateByTitle(Title As String) 'As an optimization, we will only check the UI permission once we actually know we found the app to activate - we'll do that in AppActivateHelper Dim WindowHandle As IntPtr = NativeMethods.FindWindow(Nothing, Title) 'see if we can find the window using an exact match on the title + Const MAX_TITLE_LENGTH As Integer = 511 ' if no match, search through all parent windows If IntPtr.op_Equality(WindowHandle, IntPtr.Zero) Then Dim AppTitle As String ' Old implementation uses MAX_TITLE_LENGTH characters, INCLUDING NULL character. ' Interop code will extend string builder to handle NULL character. - Dim TitleLength As Integer = Title.Length + Dim AppTitleBuilder As New StringBuilder(MAX_TITLE_LENGTH) + Dim AppTitleLength As Integer + Dim TitleLength As Integer = Len(Title) 'Loop through all children of the desktop WindowHandle = NativeMethods.GetWindow(NativeMethods.GetDesktopWindow(), NativeTypes.GW_CHILD) Do While IntPtr.op_Inequality(WindowHandle, IntPtr.Zero) ' get the window caption and test for a left-aligned substring - AppTitle = PInvoke.GetWindowText(CType(WindowHandle, HWND)) + AppTitleLength = NativeMethods.GetWindowText(WindowHandle, AppTitleBuilder, AppTitleBuilder.Capacity) + AppTitle = AppTitleBuilder.ToString() - If AppTitle.Length >= TitleLength Then + If AppTitleLength >= TitleLength Then If String.Compare(AppTitle, 0, Title, 0, TitleLength, StringComparison.OrdinalIgnoreCase) = 0 Then Exit Do 'found one End If @@ -168,9 +170,10 @@ Namespace Microsoft.VisualBasic Do While IntPtr.op_Inequality(WindowHandle, IntPtr.Zero) ' get the window caption and test for a right-aligned substring - AppTitle = PInvoke.GetWindowText(CType(WindowHandle, HWND)) + AppTitleLength = NativeMethods.GetWindowText(WindowHandle, AppTitleBuilder, AppTitleBuilder.Capacity) + AppTitle = AppTitleBuilder.ToString() - If AppTitle.Length >= TitleLength Then + If AppTitleLength >= TitleLength Then If String.Compare(Right(AppTitle, TitleLength), 0, Title, 0, TitleLength, StringComparison.OrdinalIgnoreCase) = 0 Then Exit Do 'found a match End If diff --git a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt index 39d24b0850f..9c2a39ae58a 100644 --- a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt +++ b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt @@ -241,8 +241,6 @@ GetWindow GetWindowDpiAwarenessContext GetWindowPlacement GetWindowRect -GetWindowText -GetWindowTextLength GetWindowThreadProcessId GetWorldTransform GlobalAlloc diff --git a/src/System.Windows.Forms.Primitives/src/Properties/AssemblyInfo.cs b/src/System.Windows.Forms.Primitives/src/Properties/AssemblyInfo.cs index cc7daf473bd..d59b1128c21 100644 --- a/src/System.Windows.Forms.Primitives/src/Properties/AssemblyInfo.cs +++ b/src/System.Windows.Forms.Primitives/src/Properties/AssemblyInfo.cs @@ -6,7 +6,6 @@ [assembly: System.Runtime.InteropServices.ComVisible(false)] -[assembly: InternalsVisibleTo("Microsoft.VisualBasic.Forms, PublicKey=00000000000000000400000000000000")] [assembly: InternalsVisibleTo("System.Windows.Forms, PublicKey=00000000000000000400000000000000")] [assembly: InternalsVisibleTo("System.Windows.Forms.Design, PublicKey=00000000000000000400000000000000")] [assembly: InternalsVisibleTo("System.Windows.Forms.Design.Editors, PublicKey=00000000000000000400000000000000")] diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetWindowText.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetWindowText.cs deleted file mode 100644 index 9f42f521771..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetWindowText.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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. - -namespace Windows.Win32 -{ - internal static partial class PInvoke - { - public static unsafe string GetWindowText(HWND hWnd) - { - // Old implementation uses MAX_TITLE_LENGTH characters - const int MAX_TITLE_LENGTH = 512; - int length; - Span buffer = stackalloc char[MAX_TITLE_LENGTH]; - fixed (char* lpString = buffer) - { - length = GetWindowText(hWnd, (PWSTR)lpString, buffer.Length); - } - - // If the window has no title bar or text, if the title bar is empty, - // or if the window or control handle is invalid, the return value is zero - return length == 0 ? string.Empty : new string(buffer[..length]); - } - } -} From 3cd1ae7edae26b7fed7fe91cb201965fc4f46c4c Mon Sep 17 00:00:00 2001 From: Lachlan Ennis <2433737+elachlan@users.noreply.github.com> Date: Fri, 11 Nov 2022 06:52:47 +1000 Subject: [PATCH 15/15] changes from review --- .../PInvoke.GetModuleFileNameLongPath.cs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs index b1f39b9e3b3..67770915b52 100644 --- a/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs +++ b/src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetModuleFileNameLongPath.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Diagnostics; namespace Windows.Win32 { @@ -10,11 +11,11 @@ internal static partial class PInvoke { public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) { - Span buffer = stackalloc char[MAX_PATH]; + Span stackBuffer = stackalloc char[MAX_PATH]; uint pathLength; - fixed (char* lpFilename = buffer) + fixed (char* lpFilename = stackBuffer) { - pathLength = GetModuleFileName(hModule, lpFilename, (uint)buffer.Length); + pathLength = GetModuleFileName(hModule, lpFilename, (uint)stackBuffer.Length); } if (pathLength == 0) @@ -22,42 +23,43 @@ public static unsafe string GetModuleFileNameLongPath(HINSTANCE hModule) return string.Empty; } - if (pathLength < buffer.Length) + if (pathLength < stackBuffer.Length) { - return new string(buffer[..(int)pathLength]); + return new string(stackBuffer[..(int)pathLength]); } - char[] lbuffer; + char[] buffer; int bufferSize = 4096; // Allocate increasingly larger portions of memory until successful or we hit short.maxvalue. for (int i = 1; bufferSize <= short.MaxValue; i++, bufferSize = 4096 * i) { - lbuffer = ArrayPool.Shared.Rent(bufferSize); - fixed (char* lpFilename = lbuffer) + buffer = ArrayPool.Shared.Rent(bufferSize); + fixed (char* lpFilename = buffer) { - pathLength = GetModuleFileName(hModule, lpFilename, (uint)lbuffer.Length); + pathLength = GetModuleFileName(hModule, lpFilename, (uint)buffer.Length); } if (pathLength == 0) { - ArrayPool.Shared.Return(lbuffer); + ArrayPool.Shared.Return(buffer); return string.Empty; } // If the length equals the buffer size we need to check to see if we were told the buffer was insufficient (it was trimmed) - if (pathLength < lbuffer.Length) + if (pathLength < buffer.Length) { // Get return value and return buffer to array pool. - string returnValue = new string(lbuffer, 0, (int)pathLength); - ArrayPool.Shared.Return(lbuffer); + string returnValue = new string(buffer, 0, (int)pathLength); + ArrayPool.Shared.Return(buffer); return returnValue; } // buffer was too small, return to array pool. - ArrayPool.Shared.Return(lbuffer); + ArrayPool.Shared.Return(buffer); } - throw new InvalidOperationException(); + Debug.Fail($"Module File Name is greater than {short.MaxValue}."); + return string.Empty; } } }