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

Commit bc4c683

Browse files
authored
Merge pull request #387 from vatsanm/master
Issue #385: SetWindowLongPtr on 32-bit
2 parents 08a441a + 9f637a0 commit bc4c683

File tree

6 files changed

+380
-84
lines changed

6 files changed

+380
-84
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright (c) All contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
using PInvoke;
7+
8+
/// <content>
9+
/// Contains the inner class <see cref="HwndSubClass"/>
10+
/// </content>
11+
public partial class User32Facts
12+
{
13+
/// <summary>
14+
/// Helper to subclass an HWND using SetWindowLongPtr
15+
/// </summary>
16+
private class HwndSubClass : IDisposable
17+
{
18+
/// <summary>
19+
/// Keeps track of whether this instnace has been disposed, or not.
20+
/// </summary>
21+
private bool disposed = false;
22+
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="HwndSubClass"/> class.
25+
/// </summary>
26+
/// <param name="hWnd">The HWND being subclassed</param>
27+
internal HwndSubClass(IntPtr hWnd)
28+
{
29+
this.HWnd = hWnd;
30+
31+
unsafe
32+
{
33+
this.WindowProc = new User32.WndProc(this.HookProc);
34+
this.WindowProcPointer = Marshal.GetFunctionPointerForDelegate(this.WindowProc);
35+
36+
this.OldWindowProcPointer =
37+
User32.SetWindowLongPtr(this.HWnd, User32.WindowLongIndexFlags.GWLP_WNDPROC, this.WindowProcPointer);
38+
}
39+
}
40+
41+
/// <summary>
42+
/// Finalizes an instance of the <see cref="HwndSubClass"/> class.
43+
/// </summary>
44+
~HwndSubClass()
45+
{
46+
this.Dispose(false);
47+
}
48+
49+
/// <summary>
50+
/// Event fired in response to every window message
51+
/// </summary>
52+
internal event EventHandler<WindowMessage> WindowMessage;
53+
54+
/// <summary>
55+
/// Gets the new Window proceduce's address that has been replaced in the HWND
56+
/// </summary>
57+
internal IntPtr WindowProcPointer { get; }
58+
59+
/// <summary>
60+
/// Gets the Window procedure delegate that has been used to subclass the supplied HWND
61+
/// </summary>
62+
internal User32.WndProc WindowProc { get; }
63+
64+
private IntPtr HWnd { get; set; }
65+
66+
/// <summary>
67+
/// Gets or sets the original window procedures address. This will be replaced back
68+
/// to the HWND when this instance of <see cref="HwndSubClass"/> is disposed
69+
/// </summary>
70+
private IntPtr OldWindowProcPointer { get; set; }
71+
72+
/// <summary>
73+
/// Frees resources
74+
/// </summary>
75+
public void Dispose()
76+
{
77+
this.Dispose(true);
78+
GC.SuppressFinalize(this);
79+
}
80+
81+
/// <summary>
82+
/// This is the replaces Window Procedure which will be used to track all window messages,
83+
/// and generate events
84+
/// </summary>
85+
/// <param name="hWnd">The window handle</param>
86+
/// <param name="msg">Message ID</param>
87+
/// <param name="wParam">The wParam value</param>
88+
/// <param name="lParam">The lParam value</param>
89+
/// <returns>Message specific return value</returns>
90+
internal unsafe IntPtr HookProc(IntPtr hWnd, User32.WindowMessage msg, void* wParam, void* lParam)
91+
{
92+
this.WindowMessage?.Invoke(this, new WindowMessage(msg, new IntPtr(wParam), new IntPtr(lParam)));
93+
return User32.DefWindowProc(hWnd, msg, new IntPtr(wParam), new IntPtr(lParam));
94+
}
95+
96+
/// <summary>
97+
/// Cleanup - replaces the HWND with its original Window Procedure and marks this
98+
/// instance of <see cref="HwndSubClass"/> as disposed.
99+
/// </summary>
100+
/// <param name="disposing">When true, indicates that this call is coming from a <see cref="Dispose()"/> call,
101+
/// and when false, indicates that this call is coming from the finalizer</param>
102+
protected virtual void Dispose(bool disposing)
103+
{
104+
if (!this.disposed)
105+
{
106+
this.disposed = true;
107+
if (this.HWnd != IntPtr.Zero && this.OldWindowProcPointer != IntPtr.Zero)
108+
{
109+
User32.SetWindowLongPtr(this.HWnd, User32.WindowLongIndexFlags.GWLP_WNDPROC, this.OldWindowProcPointer);
110+
this.HWnd = IntPtr.Zero;
111+
this.OldWindowProcPointer = IntPtr.Zero;
112+
}
113+
}
114+
}
115+
}
116+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) All contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using PInvoke;
6+
7+
/// <content>
8+
/// Contains the nested class <see cref="WindowMessage"/>
9+
/// </content>
10+
public partial class User32Facts
11+
{
12+
/// <summary>
13+
/// A simple wrapper representig a Window Message's payload
14+
/// </summary>
15+
private class WindowMessage
16+
{
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="WindowMessage"/> class.
19+
/// </summary>
20+
/// <param name="msg">Window Message ID</param>
21+
/// <param name="wParam">The wParam value</param>
22+
/// <param name="lParam">The lparam value</param>
23+
internal WindowMessage(User32.WindowMessage msg, IntPtr wParam, IntPtr lParam)
24+
{
25+
this.Message = msg;
26+
this.WParam = wParam;
27+
this.LParam = lParam;
28+
}
29+
30+
/// <summary>
31+
/// Gets the Window Message ID
32+
/// </summary>
33+
internal User32.WindowMessage Message { get; }
34+
35+
/// <summary>
36+
/// Gets the wParam value
37+
/// </summary>
38+
internal IntPtr WParam { get; }
39+
40+
/// <summary>
41+
/// Gets the lParam value
42+
/// </summary>
43+
internal IntPtr LParam { get; }
44+
}
45+
}

src/User32.Tests/User32Facts.cs

+55-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Diagnostics;
66
using System.IO;
77
using System.Linq;
8-
using System.Runtime.InteropServices;
98
using System.Threading;
109
using PInvoke;
1110
using Xunit;
@@ -92,4 +91,58 @@ public void GetWindowTextHelper_WithNonzeroLastError()
9291
}
9392
}
9493
}
95-
}
94+
95+
/// <summary>
96+
/// Validates that User32.SetWindowLongPtr works as intended.
97+
/// SetWindowLongPtr is implemented as a call into User32.SetWindowLong on 32-bit platforms. This
98+
/// test...
99+
/// ... a. Creates a window
100+
/// ... b. Subclasses it by calling SetWindowLongPtr.
101+
/// On 32-bit processes, it will indirectly call into SetWindowLong, and validate that our implementation is accurate.
102+
/// c. Wait a few seconds for window messages to appear in the new wndow procedure, and declare success as soon as the first message
103+
/// appears, which would indicate that the subclassing was successful.
104+
/// </summary>
105+
[Fact]
106+
[STAThread]
107+
public void SetWindowLongPtr_Test()
108+
{
109+
IntPtr hwnd = CreateWindow(
110+
"static",
111+
"Window",
112+
WindowStyles.WS_BORDER | WindowStyles.WS_CAPTION | WindowStyles.WS_OVERLAPPED | WindowStyles.WS_VISIBLE,
113+
0,
114+
0,
115+
100,
116+
100,
117+
IntPtr.Zero,
118+
IntPtr.Zero,
119+
Process.GetCurrentProcess().Handle,
120+
IntPtr.Zero);
121+
122+
if (hwnd == IntPtr.Zero)
123+
{
124+
throw new Win32Exception();
125+
}
126+
127+
SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
128+
129+
var hwndSubClass = new HwndSubClass(hwnd);
130+
hwndSubClass.WindowMessage += (_, __) =>
131+
{
132+
semaphore.Release();
133+
};
134+
135+
try
136+
{
137+
Assert.True(semaphore.Wait(TimeSpan.FromSeconds(5)));
138+
}
139+
finally
140+
{
141+
hwndSubClass.Dispose();
142+
if (hwnd != IntPtr.Zero)
143+
{
144+
DestroyWindow(hwnd);
145+
}
146+
}
147+
}
148+
}

src/User32/PublicAPI.Unshipped.txt

+6-4
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ PInvoke.User32.SendMessageTimeoutFlags.SMTO_BLOCK = 1 -> PInvoke.User32.SendMess
3333
PInvoke.User32.SendMessageTimeoutFlags.SMTO_ERRORONEXIT = 32 -> PInvoke.User32.SendMessageTimeoutFlags
3434
PInvoke.User32.SendMessageTimeoutFlags.SMTO_NORMAL = 0 -> PInvoke.User32.SendMessageTimeoutFlags
3535
PInvoke.User32.SendMessageTimeoutFlags.SMTO_NOTIMEOUTIFNOTHUNG = 8 -> PInvoke.User32.SendMessageTimeoutFlags
36-
PInvoke.User32.WindowMessage.WM_DPICHANGED_AFTERPARENT = 739 -> PInvoke.User32.WindowMessage
37-
PInvoke.User32.WindowMessage.WM_DPICHANGED_BEFOREPARENT = 738 -> PInvoke.User32.WindowMessage
38-
PInvoke.User32.WindowMessage.WM_GETDPISCALEDSIZE = 740 -> PInvoke.User32.WindowMessage
3936
PInvoke.User32.WindowLongIndexFlags.GWLP_ID = PInvoke.User32.WindowLongIndexFlags.GWL_STYLE | PInvoke.User32.WindowLongIndexFlags.DWLP_DLGPROC -> PInvoke.User32.WindowLongIndexFlags
4037
PInvoke.User32.WindowLongIndexFlags.GWLP_USERDATA = -21 -> PInvoke.User32.WindowLongIndexFlags
4138
PInvoke.User32.WindowLongIndexFlags.GWLP_WNDPROC = PInvoke.User32.WindowLongIndexFlags.GWL_STYLE | PInvoke.User32.WindowLongIndexFlags.DWLP_DLGPROC | PInvoke.User32.WindowLongIndexFlags.DWLP_USER -> PInvoke.User32.WindowLongIndexFlags
39+
PInvoke.User32.WindowMessage.WM_DPICHANGED_AFTERPARENT = 739 -> PInvoke.User32.WindowMessage
40+
PInvoke.User32.WindowMessage.WM_DPICHANGED_BEFOREPARENT = 738 -> PInvoke.User32.WindowMessage
41+
PInvoke.User32.WindowMessage.WM_GETDPISCALEDSIZE = 740 -> PInvoke.User32.WindowMessage
4242
PInvoke.User32.mouse_eventFlags
4343
PInvoke.User32.mouse_eventFlags.MOUSEEVENTF_ABSOLUTE = 32768 -> PInvoke.User32.mouse_eventFlags
4444
PInvoke.User32.mouse_eventFlags.MOUSEEVENTF_HWHEEL = 4096 -> PInvoke.User32.mouse_eventFlags
@@ -52,13 +52,16 @@ PInvoke.User32.mouse_eventFlags.MOUSEEVENTF_RIGHTUP = 16 -> PInvoke.User32.mouse
5252
PInvoke.User32.mouse_eventFlags.MOUSEEVENTF_WHEEL = 2048 -> PInvoke.User32.mouse_eventFlags
5353
PInvoke.User32.mouse_eventFlags.MOUSEEVENTF_XDOWN = 128 -> PInvoke.User32.mouse_eventFlags
5454
PInvoke.User32.mouse_eventFlags.MOUSEEVENTF_XUP = 256 -> PInvoke.User32.mouse_eventFlags
55+
static PInvoke.User32.AdjustWindowRectEx(System.IntPtr lpRect, PInvoke.User32.WindowStyles dwStyle, bool bMenu, PInvoke.User32.WindowStylesEx dwExStyle) -> bool
5556
static PInvoke.User32.AdjustWindowRectExForDpi(System.IntPtr lpRect, PInvoke.User32.WindowStyles dwStyle, bool bMenu, PInvoke.User32.WindowStylesEx dwExStyle, int dpi) -> bool
5657
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
5758
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
5859
static PInvoke.User32.GetNextWindow(System.IntPtr hWnd, PInvoke.User32.GetNextWindowCommands wCmd) -> System.IntPtr
5960
static PInvoke.User32.SetWindowLongPtr(System.IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, System.IntPtr dwNewLong) -> System.IntPtr
61+
static PInvoke.User32.SetWindowLongPtr(System.IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, void* dwNewLong) -> void*
6062
static PInvoke.User32.SystemParametersInfoForDpi(PInvoke.User32.SystemParametersInfoAction uiAction, int uiParam, System.IntPtr pvParam, PInvoke.User32.SystemParametersInfoFlags fWinIni, int dpi) -> bool
6163
static PInvoke.User32.mouse_event(PInvoke.User32.mouse_eventFlags dwFlags, int dx, int dy, int dwData, System.IntPtr dwExtraInfo) -> void
64+
static extern PInvoke.User32.AdjustWindowRectEx(PInvoke.RECT* lpRect, PInvoke.User32.WindowStyles dwStyle, bool bMenu, PInvoke.User32.WindowStylesEx dwExStyle) -> bool
6265
static extern PInvoke.User32.AdjustWindowRectExForDpi(PInvoke.RECT* lpRect, PInvoke.User32.WindowStyles dwStyle, bool bMenu, PInvoke.User32.WindowStylesEx dwExStyle, int dpi) -> bool
6366
static extern PInvoke.User32.AreDpiAwarenessContextsEqual(System.IntPtr dpiContextA, System.IntPtr dpiContextB) -> bool
6467
static extern PInvoke.User32.DestroyWindow(System.IntPtr hWnd) -> bool
@@ -82,7 +85,6 @@ static extern PInvoke.User32.SendMessageTimeout(System.IntPtr hWnd, PInvoke.User
8285
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
8386
static extern PInvoke.User32.SetDialogDpiChangeBehavior(System.IntPtr hDlg, PInvoke.User32.DIALOG_DPI_CHANGE_BEHAVIORS mask, PInvoke.User32.DIALOG_DPI_CHANGE_BEHAVIORS values) -> bool
8487
static extern PInvoke.User32.SetLastErrorEx(uint dwErrCode, uint dwType) -> void
85-
static extern PInvoke.User32.SetWindowLongPtr(System.IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, void* dwNewLong) -> void*
8688
static extern PInvoke.User32.SetProcessDpiAwarenessContext(System.IntPtr dpiAWarenessContext) -> bool
8789
static extern PInvoke.User32.SetThreadDpiAwarenessContext(System.IntPtr dpiContext) -> System.IntPtr
8890
static extern PInvoke.User32.SetThreadDpiHostingBehavior(PInvoke.User32.DPI_HOSTING_BEHAVIOR dpiHostingBehavior) -> PInvoke.User32.DPI_HOSTING_BEHAVIOR

0 commit comments

Comments
 (0)