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

Commit

Permalink
Merge pull request #387 from vatsanm/master
Browse files Browse the repository at this point in the history
Issue #385: SetWindowLongPtr on 32-bit
  • Loading branch information
AArnott committed May 11, 2018
2 parents 08a441a + 9f637a0 commit bc4c683
Show file tree
Hide file tree
Showing 6 changed files with 380 additions and 84 deletions.
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

0 comments on commit bc4c683

Please sign in to comment.