Skip to content
This repository was archived by the owner on Jul 26, 2023. It is now read-only.

SetWindowLongPtr on 32-bit #387

Merged
merged 4 commits into from
May 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions src/User32.Tests/User32Facts+HwndSubClass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright (c) All contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Runtime.InteropServices;
using PInvoke;

/// <content>
/// Contains the inner class <see cref="HwndSubClass"/>
/// </content>
public partial class User32Facts
{
/// <summary>
/// Helper to subclass an HWND using SetWindowLongPtr
/// </summary>
private class HwndSubClass : IDisposable
{
/// <summary>
/// Keeps track of whether this instnace has been disposed, or not.
/// </summary>
private bool disposed = false;

/// <summary>
/// Initializes a new instance of the <see cref="HwndSubClass"/> class.
/// </summary>
/// <param name="hWnd">The HWND being subclassed</param>
internal HwndSubClass(IntPtr hWnd)
{
this.HWnd = hWnd;

unsafe
{
this.WindowProc = new User32.WndProc(this.HookProc);
this.WindowProcPointer = Marshal.GetFunctionPointerForDelegate(this.WindowProc);

this.OldWindowProcPointer =
User32.SetWindowLongPtr(this.HWnd, User32.WindowLongIndexFlags.GWLP_WNDPROC, this.WindowProcPointer);
}
}

/// <summary>
/// Finalizes an instance of the <see cref="HwndSubClass"/> class.
/// </summary>
~HwndSubClass()
{
this.Dispose(false);
}

/// <summary>
/// Event fired in response to every window message
/// </summary>
internal event EventHandler<WindowMessage> WindowMessage;

/// <summary>
/// Gets the new Window proceduce's address that has been replaced in the HWND
/// </summary>
internal IntPtr WindowProcPointer { get; }

/// <summary>
/// Gets the Window procedure delegate that has been used to subclass the supplied HWND
/// </summary>
internal User32.WndProc WindowProc { get; }

private IntPtr HWnd { get; set; }

/// <summary>
/// Gets or sets the original window procedures address. This will be replaced back
/// to the HWND when this instance of <see cref="HwndSubClass"/> is disposed
/// </summary>
private IntPtr OldWindowProcPointer { get; set; }

/// <summary>
/// Frees resources
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}

/// <summary>
/// This is the replaces Window Procedure which will be used to track all window messages,
/// and generate events
/// </summary>
/// <param name="hWnd">The window handle</param>
/// <param name="msg">Message ID</param>
/// <param name="wParam">The wParam value</param>
/// <param name="lParam">The lParam value</param>
/// <returns>Message specific return value</returns>
internal unsafe IntPtr HookProc(IntPtr hWnd, User32.WindowMessage msg, void* wParam, void* lParam)
{
this.WindowMessage?.Invoke(this, new WindowMessage(msg, new IntPtr(wParam), new IntPtr(lParam)));
return User32.DefWindowProc(hWnd, msg, new IntPtr(wParam), new IntPtr(lParam));
}

/// <summary>
/// Cleanup - replaces the HWND with its original Window Procedure and marks this
/// instance of <see cref="HwndSubClass"/> as disposed.
/// </summary>
/// <param name="disposing">When true, indicates that this call is coming from a <see cref="Dispose()"/> call,
/// and when false, indicates that this call is coming from the finalizer</param>
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
this.disposed = true;
if (this.HWnd != IntPtr.Zero && this.OldWindowProcPointer != IntPtr.Zero)
{
User32.SetWindowLongPtr(this.HWnd, User32.WindowLongIndexFlags.GWLP_WNDPROC, this.OldWindowProcPointer);
this.HWnd = IntPtr.Zero;
this.OldWindowProcPointer = IntPtr.Zero;
}
}
}
}
}
45 changes: 45 additions & 0 deletions src/User32.Tests/User32Facts+WindowMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) All contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using PInvoke;

/// <content>
/// Contains the nested class <see cref="WindowMessage"/>
/// </content>
public partial class User32Facts
{
/// <summary>
/// A simple wrapper representig a Window Message's payload
/// </summary>
private class WindowMessage
{
/// <summary>
/// Initializes a new instance of the <see cref="WindowMessage"/> class.
/// </summary>
/// <param name="msg">Window Message ID</param>
/// <param name="wParam">The wParam value</param>
/// <param name="lParam">The lparam value</param>
internal WindowMessage(User32.WindowMessage msg, IntPtr wParam, IntPtr lParam)
{
this.Message = msg;
this.WParam = wParam;
this.LParam = lParam;
}

/// <summary>
/// Gets the Window Message ID
/// </summary>
internal User32.WindowMessage Message { get; }

/// <summary>
/// Gets the wParam value
/// </summary>
internal IntPtr WParam { get; }

/// <summary>
/// Gets the lParam value
/// </summary>
internal IntPtr LParam { get; }
}
}
57 changes: 55 additions & 2 deletions src/User32.Tests/User32Facts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using PInvoke;
using Xunit;
Expand Down Expand Up @@ -92,4 +91,58 @@ public void GetWindowTextHelper_WithNonzeroLastError()
}
}
}
}

/// <summary>
/// Validates that User32.SetWindowLongPtr works as intended.
/// SetWindowLongPtr is implemented as a call into User32.SetWindowLong on 32-bit platforms. This
/// test...
/// ... a. Creates a window
/// ... b. Subclasses it by calling SetWindowLongPtr.
/// On 32-bit processes, it will indirectly call into SetWindowLong, and validate that our implementation is accurate.
/// c. Wait a few seconds for window messages to appear in the new wndow procedure, and declare success as soon as the first message
/// appears, which would indicate that the subclassing was successful.
/// </summary>
[Fact]
[STAThread]
public void SetWindowLongPtr_Test()
{
IntPtr hwnd = CreateWindow(
"static",
"Window",
WindowStyles.WS_BORDER | WindowStyles.WS_CAPTION | WindowStyles.WS_OVERLAPPED | WindowStyles.WS_VISIBLE,
0,
0,
100,
100,
IntPtr.Zero,
IntPtr.Zero,
Process.GetCurrentProcess().Handle,
IntPtr.Zero);

if (hwnd == IntPtr.Zero)
{
throw new Win32Exception();
}

SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);

var hwndSubClass = new HwndSubClass(hwnd);
hwndSubClass.WindowMessage += (_, __) =>
{
semaphore.Release();
};

try
{
Assert.True(semaphore.Wait(TimeSpan.FromSeconds(5)));
}
finally
{
hwndSubClass.Dispose();
if (hwnd != IntPtr.Zero)
{
DestroyWindow(hwnd);
}
}
}
}
10 changes: 6 additions & 4 deletions src/User32/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ PInvoke.User32.SendMessageTimeoutFlags.SMTO_BLOCK = 1 -> PInvoke.User32.SendMess
PInvoke.User32.SendMessageTimeoutFlags.SMTO_ERRORONEXIT = 32 -> PInvoke.User32.SendMessageTimeoutFlags
PInvoke.User32.SendMessageTimeoutFlags.SMTO_NORMAL = 0 -> PInvoke.User32.SendMessageTimeoutFlags
PInvoke.User32.SendMessageTimeoutFlags.SMTO_NOTIMEOUTIFNOTHUNG = 8 -> PInvoke.User32.SendMessageTimeoutFlags
PInvoke.User32.WindowMessage.WM_DPICHANGED_AFTERPARENT = 739 -> PInvoke.User32.WindowMessage
PInvoke.User32.WindowMessage.WM_DPICHANGED_BEFOREPARENT = 738 -> PInvoke.User32.WindowMessage
PInvoke.User32.WindowMessage.WM_GETDPISCALEDSIZE = 740 -> PInvoke.User32.WindowMessage
PInvoke.User32.WindowLongIndexFlags.GWLP_ID = PInvoke.User32.WindowLongIndexFlags.GWL_STYLE | PInvoke.User32.WindowLongIndexFlags.DWLP_DLGPROC -> PInvoke.User32.WindowLongIndexFlags
PInvoke.User32.WindowLongIndexFlags.GWLP_USERDATA = -21 -> PInvoke.User32.WindowLongIndexFlags
PInvoke.User32.WindowLongIndexFlags.GWLP_WNDPROC = PInvoke.User32.WindowLongIndexFlags.GWL_STYLE | PInvoke.User32.WindowLongIndexFlags.DWLP_DLGPROC | PInvoke.User32.WindowLongIndexFlags.DWLP_USER -> PInvoke.User32.WindowLongIndexFlags
PInvoke.User32.WindowMessage.WM_DPICHANGED_AFTERPARENT = 739 -> PInvoke.User32.WindowMessage
PInvoke.User32.WindowMessage.WM_DPICHANGED_BEFOREPARENT = 738 -> PInvoke.User32.WindowMessage
PInvoke.User32.WindowMessage.WM_GETDPISCALEDSIZE = 740 -> PInvoke.User32.WindowMessage
PInvoke.User32.mouse_eventFlags
PInvoke.User32.mouse_eventFlags.MOUSEEVENTF_ABSOLUTE = 32768 -> PInvoke.User32.mouse_eventFlags
PInvoke.User32.mouse_eventFlags.MOUSEEVENTF_HWHEEL = 4096 -> PInvoke.User32.mouse_eventFlags
Expand All @@ -52,13 +52,16 @@ PInvoke.User32.mouse_eventFlags.MOUSEEVENTF_RIGHTUP = 16 -> PInvoke.User32.mouse
PInvoke.User32.mouse_eventFlags.MOUSEEVENTF_WHEEL = 2048 -> PInvoke.User32.mouse_eventFlags
PInvoke.User32.mouse_eventFlags.MOUSEEVENTF_XDOWN = 128 -> PInvoke.User32.mouse_eventFlags
PInvoke.User32.mouse_eventFlags.MOUSEEVENTF_XUP = 256 -> PInvoke.User32.mouse_eventFlags
static PInvoke.User32.AdjustWindowRectEx(System.IntPtr lpRect, PInvoke.User32.WindowStyles dwStyle, bool bMenu, PInvoke.User32.WindowStylesEx dwExStyle) -> bool
static PInvoke.User32.AdjustWindowRectExForDpi(System.IntPtr lpRect, PInvoke.User32.WindowStyles dwStyle, bool bMenu, PInvoke.User32.WindowStylesEx dwExStyle, int dpi) -> bool
static PInvoke.User32.CreateWindowEx(PInvoke.User32.WindowStylesEx dwExStyle, short lpClassName, string lpWindowName, PInvoke.User32.WindowStyles dwStyle, int x, int y, int nWidth, int nHeight, System.IntPtr hWndParent, System.IntPtr hMenu, System.IntPtr hInstance, System.IntPtr lpParam) -> System.IntPtr
static PInvoke.User32.CreateWindowEx(PInvoke.User32.WindowStylesEx dwExStyle, short lpClassName, string lpWindowName, PInvoke.User32.WindowStyles dwStyle, int x, int y, int nWidth, int nHeight, System.IntPtr hWndParent, System.IntPtr hMenu, System.IntPtr hInstance, void* lpParam) -> System.IntPtr
static PInvoke.User32.GetNextWindow(System.IntPtr hWnd, PInvoke.User32.GetNextWindowCommands wCmd) -> System.IntPtr
static PInvoke.User32.SetWindowLongPtr(System.IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, System.IntPtr dwNewLong) -> System.IntPtr
static PInvoke.User32.SetWindowLongPtr(System.IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, void* dwNewLong) -> void*
static PInvoke.User32.SystemParametersInfoForDpi(PInvoke.User32.SystemParametersInfoAction uiAction, int uiParam, System.IntPtr pvParam, PInvoke.User32.SystemParametersInfoFlags fWinIni, int dpi) -> bool
static PInvoke.User32.mouse_event(PInvoke.User32.mouse_eventFlags dwFlags, int dx, int dy, int dwData, System.IntPtr dwExtraInfo) -> void
static extern PInvoke.User32.AdjustWindowRectEx(PInvoke.RECT* lpRect, PInvoke.User32.WindowStyles dwStyle, bool bMenu, PInvoke.User32.WindowStylesEx dwExStyle) -> bool
static extern PInvoke.User32.AdjustWindowRectExForDpi(PInvoke.RECT* lpRect, PInvoke.User32.WindowStyles dwStyle, bool bMenu, PInvoke.User32.WindowStylesEx dwExStyle, int dpi) -> bool
static extern PInvoke.User32.AreDpiAwarenessContextsEqual(System.IntPtr dpiContextA, System.IntPtr dpiContextB) -> bool
static extern PInvoke.User32.DestroyWindow(System.IntPtr hWnd) -> bool
Expand All @@ -82,7 +85,6 @@ static extern PInvoke.User32.SendMessageTimeout(System.IntPtr hWnd, PInvoke.User
static extern PInvoke.User32.SetDialogControlDpiChangeBehavior(System.IntPtr hwnd, PInvoke.User32.DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS mask, PInvoke.User32.DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS values) -> bool
static extern PInvoke.User32.SetDialogDpiChangeBehavior(System.IntPtr hDlg, PInvoke.User32.DIALOG_DPI_CHANGE_BEHAVIORS mask, PInvoke.User32.DIALOG_DPI_CHANGE_BEHAVIORS values) -> bool
static extern PInvoke.User32.SetLastErrorEx(uint dwErrCode, uint dwType) -> void
static extern PInvoke.User32.SetWindowLongPtr(System.IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, void* dwNewLong) -> void*
static extern PInvoke.User32.SetProcessDpiAwarenessContext(System.IntPtr dpiAWarenessContext) -> bool
static extern PInvoke.User32.SetThreadDpiAwarenessContext(System.IntPtr dpiContext) -> System.IntPtr
static extern PInvoke.User32.SetThreadDpiHostingBehavior(PInvoke.User32.DPI_HOSTING_BEHAVIOR dpiHostingBehavior) -> PInvoke.User32.DPI_HOSTING_BEHAVIOR
Expand Down
Loading