Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ GetMenu
GetMenuItemCount
GetMessagePos
GetMessageTime
GetModuleFileName
GetModuleHandle
GetNearestColor
GetObjectType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<char>.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<char>.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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/// <summary>
/// Cached value for MaxPath.
/// </summary>
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;

/// <summary>
/// Gets the windows max path limit.
/// </summary>
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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,7 @@ public static string ExecutablePath
{
get
{
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;
}
}
Expand Down
20 changes: 8 additions & 12 deletions src/System.Windows.Forms/src/System/Windows/Forms/Control.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -2541,7 +2540,7 @@ public int Height
set => SetBounds(_x, _y, _width, value, BoundsSpecified.Height);
}

internal bool HostedInWin32DialogManager
internal unsafe bool HostedInWin32DialogManager
{
get
{
Expand All @@ -2556,22 +2555,19 @@ internal bool HostedInWin32DialogManager
{
HWND parentHandle = PInvoke.GetParent(this);
HWND lastParentHandle = parentHandle;

StringBuilder sb = new StringBuilder(32);

SetState(States.HostedInDialog, false);

Span<char> 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<char> className = "#32770";
if (MemoryExtensions.Equals(className, buffer.SliceAtFirstNull(), StringComparison.Ordinal))
{
SetState(States.HostedInDialog, true);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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 " +
Expand Down Expand Up @@ -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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this move to the PInvoke namespace?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there is another place where we call PInvoke.GetClassName in our codebase. If this is the case, we should move this to PInvoke namespace and call to this method in the other places we are using PInvoke.GetClassName

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't move this because its a wrapper and isn't used in the other calls. I am happy to still move it. The other uses avoid string allocations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you think this is a dup, feel free to remove this. I can't recall exact reasons why I copied it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since its a wrapper of the pinvoke and we don't reuse it, I think it can stay here.

{
const int MaxClassName = 256;
StringBuilder sb = new StringBuilder(MaxClassName);
UnsafeNativeMethods.GetClassName(new HandleRef(null, hWnd), sb, MaxClassName);
return sb.ToString();
Span<char> buf = stackalloc char[PInvoke.MAX_CLASS_NAME];
fixed (char* lpClassName = buf)
{
_ = PInvoke.GetClassName(hWnd, lpClassName, buf.Length);
}

return buf.SliceAtFirstNull().ToString();
}

/// <summary>
Expand Down