Skip to content

Commit a217367

Browse files
Copilotjevansaks
andauthored
Add common Win32 message parameter extraction macros (#1548)
* Initial plan * Add MAKEPOINTS, GET_X_LPARAM, GET_Y_LPARAM, and GET_WHEEL_DELTA_WPARAM macros Co-authored-by: jevansaks <20667023+jevansaks@users.noreply.github.com> * Add GET_APPCOMMAND_LPARAM, GET_DEVICE_LPARAM, GET_FLAGS_LPARAM, GET_KEYSTATE_LPARAM, GET_KEYSTATE_WPARAM, GET_NCHITTEST_WPARAM, and GET_RAWINPUT_CODE_WPARAM macros Co-authored-by: jevansaks <20667023+jevansaks@users.noreply.github.com> * Fix GET_NCHITTEST_WPARAM to return short from LOWORD instead of full uint Co-authored-by: jevansaks <20667023+jevansaks@users.noreply.github.com> * Fix GET_RAWINPUT_CODE_WPARAM to mask with 0xff to extract only lowest 8 bits Co-authored-by: jevansaks <20667023+jevansaks@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jevansaks <20667023+jevansaks@users.noreply.github.com>
1 parent fe382a4 commit a217367

File tree

3 files changed

+318
-0
lines changed

3 files changed

+318
-0
lines changed

src/Microsoft.Windows.CsWin32/templates/PInvokeClassMacros.cs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,105 @@ internal class PInvokeClassMacros
5858
/// <param name="value">The 32-bit value.</param>
5959
/// <returns>The high-order word.</returns>
6060
internal static ushort HIWORD(uint value) => unchecked((ushort)(value >> 16));
61+
62+
/// <summary>
63+
/// Retrieves the signed x-coordinate from the specified <see cref="global::Windows.Win32.Foundation.LPARAM"/> value.
64+
/// </summary>
65+
/// <param name="lParam">The value to be converted.</param>
66+
/// <returns>The signed x-coordinate.</returns>
67+
/// <remarks>
68+
/// Learn more in <see href="https://learn.microsoft.com/windows/win32/api/windowsx/nf-windowsx-get_x_lparam">the documentation for this API</see>.
69+
/// </remarks>
70+
internal static int GET_X_LPARAM(global::Windows.Win32.Foundation.LPARAM lParam) => unchecked((int)(short)LOWORD(unchecked((uint)(nint)lParam)));
71+
72+
/// <summary>
73+
/// Retrieves the signed y-coordinate from the specified <see cref="global::Windows.Win32.Foundation.LPARAM"/> value.
74+
/// </summary>
75+
/// <param name="lParam">The value to be converted.</param>
76+
/// <returns>The signed y-coordinate.</returns>
77+
/// <remarks>
78+
/// Learn more in <see href="https://learn.microsoft.com/windows/win32/api/windowsx/nf-windowsx-get_y_lparam">the documentation for this API</see>.
79+
/// </remarks>
80+
internal static int GET_Y_LPARAM(global::Windows.Win32.Foundation.LPARAM lParam) => unchecked((int)(short)HIWORD(unchecked((uint)(nint)lParam)));
81+
82+
/// <summary>
83+
/// Retrieves a <see cref="global::Windows.Win32.Foundation.POINTS"/> structure from the specified <see cref="global::Windows.Win32.Foundation.LPARAM"/> value.
84+
/// </summary>
85+
/// <param name="lParam">The value to be converted.</param>
86+
/// <returns>A POINTS structure containing the x and y coordinates.</returns>
87+
internal static global::Windows.Win32.Foundation.POINTS MAKEPOINTS(global::Windows.Win32.Foundation.LPARAM lParam) => new global::Windows.Win32.Foundation.POINTS { x = unchecked((short)LOWORD(unchecked((uint)(nint)lParam))), y = unchecked((short)HIWORD(unchecked((uint)(nint)lParam))) };
88+
89+
/// <summary>
90+
/// Retrieves the signed wheel delta value from the specified <see cref="global::Windows.Win32.Foundation.WPARAM"/> value.
91+
/// </summary>
92+
/// <param name="wParam">The value to be converted.</param>
93+
/// <returns>The signed wheel delta value.</returns>
94+
/// <remarks>
95+
/// Learn more in <see href="https://learn.microsoft.com/windows/win32/inputdev/wm-mousewheel">the documentation for this API</see>.
96+
/// </remarks>
97+
internal static short GET_WHEEL_DELTA_WPARAM(global::Windows.Win32.Foundation.WPARAM wParam) => unchecked((short)HIWORD(unchecked((uint)(nuint)wParam)));
98+
99+
/// <summary>
100+
/// Retrieves the application command from the specified <see cref="global::Windows.Win32.Foundation.LPARAM"/> value.
101+
/// </summary>
102+
/// <param name="lParam">The value to be converted.</param>
103+
/// <returns>The application command.</returns>
104+
/// <remarks>
105+
/// Learn more in <see href="https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-get_appcommand_lparam">the documentation for this API</see>.
106+
/// </remarks>
107+
internal static short GET_APPCOMMAND_LPARAM(global::Windows.Win32.Foundation.LPARAM lParam) => unchecked((short)(HIWORD(unchecked((uint)(nint)lParam)) & ~0xF000));
108+
109+
/// <summary>
110+
/// Retrieves the input device type from the specified <see cref="global::Windows.Win32.Foundation.LPARAM"/> value.
111+
/// </summary>
112+
/// <param name="lParam">The value to be converted.</param>
113+
/// <returns>The input device type.</returns>
114+
/// <remarks>
115+
/// Learn more in <see href="https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-get_device_lparam">the documentation for this API</see>.
116+
/// </remarks>
117+
internal static ushort GET_DEVICE_LPARAM(global::Windows.Win32.Foundation.LPARAM lParam) => unchecked((ushort)(HIWORD(unchecked((uint)(nint)lParam)) & 0xF000));
118+
119+
/// <summary>
120+
/// Retrieves the key state flags from the specified <see cref="global::Windows.Win32.Foundation.LPARAM"/> value.
121+
/// </summary>
122+
/// <param name="lParam">The value to be converted.</param>
123+
/// <returns>The key state flags.</returns>
124+
/// <remarks>
125+
/// Learn more in <see href="https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-get_flags_lparam">the documentation for this API</see>.
126+
/// </remarks>
127+
internal static ushort GET_FLAGS_LPARAM(global::Windows.Win32.Foundation.LPARAM lParam) => LOWORD(unchecked((uint)(nint)lParam));
128+
129+
/// <summary>
130+
/// Retrieves the key state from the specified <see cref="global::Windows.Win32.Foundation.LPARAM"/> value.
131+
/// </summary>
132+
/// <param name="lParam">The value to be converted.</param>
133+
/// <returns>The key state.</returns>
134+
/// <remarks>
135+
/// Learn more in <see href="https://learn.microsoft.com/windows/win32/inputdev/wm-appcommand">the documentation for this API</see>.
136+
/// </remarks>
137+
internal static ushort GET_KEYSTATE_LPARAM(global::Windows.Win32.Foundation.LPARAM lParam) => LOWORD(unchecked((uint)(nint)lParam));
138+
139+
/// <summary>
140+
/// Retrieves the key state from the specified <see cref="global::Windows.Win32.Foundation.WPARAM"/> value.
141+
/// </summary>
142+
/// <param name="wParam">The value to be converted.</param>
143+
/// <returns>The key state.</returns>
144+
internal static ushort GET_KEYSTATE_WPARAM(global::Windows.Win32.Foundation.WPARAM wParam) => LOWORD(unchecked((uint)(nuint)wParam));
145+
146+
/// <summary>
147+
/// Retrieves the hit-test value from the specified <see cref="global::Windows.Win32.Foundation.WPARAM"/> value.
148+
/// </summary>
149+
/// <param name="wParam">The value to be converted.</param>
150+
/// <returns>The hit-test value.</returns>
151+
/// <remarks>
152+
/// Learn more in <see href="https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-get_nchittest_wparam">the documentation for this API</see>.
153+
/// </remarks>
154+
internal static short GET_NCHITTEST_WPARAM(global::Windows.Win32.Foundation.WPARAM wParam) => unchecked((short)LOWORD(unchecked((uint)(nuint)wParam)));
155+
156+
/// <summary>
157+
/// Retrieves the input code from the specified <see cref="global::Windows.Win32.Foundation.WPARAM"/> value.
158+
/// </summary>
159+
/// <param name="wParam">The value to be converted.</param>
160+
/// <returns>The input code.</returns>
161+
internal static uint GET_RAWINPUT_CODE_WPARAM(global::Windows.Win32.Foundation.WPARAM wParam) => unchecked((uint)(nuint)wParam) & 0xff;
61162
}

test/GenerationSandbox.Tests/MacroTests.cs

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,209 @@ public void HIWORDTest()
9191
Assert.Equal((ushort)0xFFFF, HIWORD(0xFFFF1234u));
9292
Assert.Equal((ushort)0x8000, HIWORD(0x80000000u));
9393
}
94+
95+
[Fact]
96+
public void GET_X_LPARAMTest()
97+
{
98+
unchecked
99+
{
100+
// Test positive coordinates
101+
Assert.Equal(0, GET_X_LPARAM((LPARAM)(nint)0x00000000));
102+
Assert.Equal(1, GET_X_LPARAM((LPARAM)(nint)0x00000001));
103+
Assert.Equal(0x1234, GET_X_LPARAM((LPARAM)(nint)0x56781234));
104+
105+
// Test negative coordinates (sign extension from short)
106+
Assert.Equal(-1, GET_X_LPARAM((LPARAM)(nint)0x0000FFFF));
107+
Assert.Equal(-32768, GET_X_LPARAM((LPARAM)(nint)0x00008000));
108+
109+
// Test with various high word values
110+
Assert.Equal(unchecked((int)(short)0xABCD), GET_X_LPARAM((LPARAM)(nint)0x1234ABCD));
111+
}
112+
}
113+
114+
[Fact]
115+
public void GET_Y_LPARAMTest()
116+
{
117+
unchecked
118+
{
119+
// Test positive coordinates
120+
Assert.Equal(0, GET_Y_LPARAM((LPARAM)(nint)0x00000000));
121+
Assert.Equal(1, GET_Y_LPARAM((LPARAM)(nint)0x00010000));
122+
Assert.Equal(0x5678, GET_Y_LPARAM((LPARAM)(nint)0x56781234));
123+
124+
// Test negative coordinates (sign extension from short)
125+
Assert.Equal(-1, GET_Y_LPARAM((LPARAM)(nint)0xFFFF0000));
126+
Assert.Equal(-32768, GET_Y_LPARAM((LPARAM)(nint)0x80000000));
127+
128+
// Test with various low word values
129+
Assert.Equal(0x1234, GET_Y_LPARAM((LPARAM)(nint)0x1234ABCD));
130+
}
131+
}
132+
133+
[Fact]
134+
public void MAKEPOINTSTest()
135+
{
136+
unchecked
137+
{
138+
// Test zero coordinates
139+
var p1 = MAKEPOINTS((LPARAM)(nint)0x00000000);
140+
Assert.Equal((short)0, p1.x);
141+
Assert.Equal((short)0, p1.y);
142+
143+
// Test positive coordinates
144+
var p2 = MAKEPOINTS((LPARAM)(nint)0x00010002);
145+
Assert.Equal((short)2, p2.x);
146+
Assert.Equal((short)1, p2.y);
147+
148+
var p3 = MAKEPOINTS((LPARAM)(nint)0x56781234);
149+
Assert.Equal((short)0x1234, p3.x);
150+
Assert.Equal((short)0x5678, p3.y);
151+
152+
// Test negative coordinates
153+
var p4 = MAKEPOINTS((LPARAM)(nint)0xFFFFFFFF);
154+
Assert.Equal((short)-1, p4.x);
155+
Assert.Equal((short)-1, p4.y);
156+
157+
var p5 = MAKEPOINTS((LPARAM)(nint)0x80008000);
158+
Assert.Equal((short)-32768, p5.x);
159+
Assert.Equal((short)-32768, p5.y);
160+
}
161+
}
162+
163+
[Fact]
164+
public void GET_WHEEL_DELTA_WPARAMTest()
165+
{
166+
unchecked
167+
{
168+
// Test positive wheel delta
169+
Assert.Equal((short)120, GET_WHEEL_DELTA_WPARAM((WPARAM)0x00780000));
170+
Assert.Equal((short)240, GET_WHEEL_DELTA_WPARAM((WPARAM)0x00F00000));
171+
172+
// Test negative wheel delta
173+
Assert.Equal((short)-120, GET_WHEEL_DELTA_WPARAM((WPARAM)0xFF880000));
174+
Assert.Equal((short)-240, GET_WHEEL_DELTA_WPARAM((WPARAM)0xFF100000));
175+
176+
// Test zero delta
177+
Assert.Equal((short)0, GET_WHEEL_DELTA_WPARAM((WPARAM)0x00000000));
178+
179+
// Test with non-zero low word (low word should be ignored)
180+
Assert.Equal((short)120, GET_WHEEL_DELTA_WPARAM((WPARAM)0x0078ABCD));
181+
}
182+
}
183+
184+
[Fact]
185+
public void GET_APPCOMMAND_LPARAMTest()
186+
{
187+
unchecked
188+
{
189+
// Test extracting app command (HIWORD & ~FAPPCOMMAND_MASK)
190+
// FAPPCOMMAND_MASK = 0xF000
191+
Assert.Equal((short)0, GET_APPCOMMAND_LPARAM((LPARAM)(nint)0x00000000));
192+
Assert.Equal((short)1, GET_APPCOMMAND_LPARAM((LPARAM)(nint)0x00010000));
193+
Assert.Equal((short)0x0FFF, GET_APPCOMMAND_LPARAM((LPARAM)(nint)0x0FFF0000));
194+
195+
// Test with device bits set (should be masked out)
196+
Assert.Equal((short)1, GET_APPCOMMAND_LPARAM((LPARAM)(nint)0x10010000));
197+
Assert.Equal((short)1, GET_APPCOMMAND_LPARAM((LPARAM)(nint)0x20010000));
198+
Assert.Equal((short)1, GET_APPCOMMAND_LPARAM((LPARAM)(nint)0xF0010000));
199+
}
200+
}
201+
202+
[Fact]
203+
public void GET_DEVICE_LPARAMTest()
204+
{
205+
unchecked
206+
{
207+
// Test extracting device type (HIWORD & FAPPCOMMAND_MASK)
208+
// FAPPCOMMAND_MASK = 0xF000
209+
Assert.Equal((ushort)0x0000, GET_DEVICE_LPARAM((LPARAM)(nint)0x00000000));
210+
Assert.Equal((ushort)0x1000, GET_DEVICE_LPARAM((LPARAM)(nint)0x10000000));
211+
Assert.Equal((ushort)0x2000, GET_DEVICE_LPARAM((LPARAM)(nint)0x20000000));
212+
Assert.Equal((ushort)0xF000, GET_DEVICE_LPARAM((LPARAM)(nint)0xF0000000));
213+
214+
// Test with app command bits set (should be masked out)
215+
Assert.Equal((ushort)0x1000, GET_DEVICE_LPARAM((LPARAM)(nint)0x10FF0000));
216+
}
217+
}
218+
219+
[Fact]
220+
public void GET_FLAGS_LPARAMTest()
221+
{
222+
unchecked
223+
{
224+
// Test extracting flags from low word
225+
Assert.Equal((ushort)0x0000, GET_FLAGS_LPARAM((LPARAM)(nint)0x00000000));
226+
Assert.Equal((ushort)0x0001, GET_FLAGS_LPARAM((LPARAM)(nint)0x00000001));
227+
Assert.Equal((ushort)0xFFFF, GET_FLAGS_LPARAM((LPARAM)(nint)0x0000FFFF));
228+
Assert.Equal((ushort)0x1234, GET_FLAGS_LPARAM((LPARAM)(nint)0x56781234));
229+
230+
// Test that high word is ignored
231+
Assert.Equal((ushort)0xABCD, GET_FLAGS_LPARAM((LPARAM)(nint)0xFFFFABCD));
232+
}
233+
}
234+
235+
[Fact]
236+
public void GET_KEYSTATE_LPARAMTest()
237+
{
238+
unchecked
239+
{
240+
// Test extracting key state from low word
241+
Assert.Equal((ushort)0x0000, GET_KEYSTATE_LPARAM((LPARAM)(nint)0x00000000));
242+
Assert.Equal((ushort)0x0001, GET_KEYSTATE_LPARAM((LPARAM)(nint)0x00000001));
243+
Assert.Equal((ushort)0xFFFF, GET_KEYSTATE_LPARAM((LPARAM)(nint)0x0000FFFF));
244+
Assert.Equal((ushort)0x0004, GET_KEYSTATE_LPARAM((LPARAM)(nint)0x12340004));
245+
246+
// Test that high word is ignored
247+
Assert.Equal((ushort)0x0008, GET_KEYSTATE_LPARAM((LPARAM)(nint)0xFFFF0008));
248+
}
249+
}
250+
251+
[Fact]
252+
public void GET_KEYSTATE_WPARAMTest()
253+
{
254+
unchecked
255+
{
256+
// Test extracting key state from low word of WPARAM
257+
Assert.Equal((ushort)0x0000, GET_KEYSTATE_WPARAM((WPARAM)0x00000000));
258+
Assert.Equal((ushort)0x0001, GET_KEYSTATE_WPARAM((WPARAM)0x00000001));
259+
Assert.Equal((ushort)0xFFFF, GET_KEYSTATE_WPARAM((WPARAM)0x0000FFFF));
260+
Assert.Equal((ushort)0x0008, GET_KEYSTATE_WPARAM((WPARAM)0x12340008));
261+
262+
// Test that high word is ignored
263+
Assert.Equal((ushort)0x0004, GET_KEYSTATE_WPARAM((WPARAM)0xFFFF0004));
264+
}
265+
}
266+
267+
[Fact]
268+
public void GET_NCHITTEST_WPARAMTest()
269+
{
270+
unchecked
271+
{
272+
// Test extracting hit-test value from low word
273+
Assert.Equal((short)0, GET_NCHITTEST_WPARAM((WPARAM)0x00000000));
274+
Assert.Equal((short)1, GET_NCHITTEST_WPARAM((WPARAM)0x00000001));
275+
Assert.Equal((short)-1, GET_NCHITTEST_WPARAM((WPARAM)0x0000FFFF));
276+
Assert.Equal((short)0x1234, GET_NCHITTEST_WPARAM((WPARAM)0x56781234));
277+
278+
// Test that high word is ignored
279+
Assert.Equal((short)0x5678, GET_NCHITTEST_WPARAM((WPARAM)0xFFFF5678));
280+
}
281+
}
282+
283+
[Fact]
284+
public void GET_RAWINPUT_CODE_WPARAMTest()
285+
{
286+
unchecked
287+
{
288+
// Test extracting raw input code (lowest 8 bits only)
289+
Assert.Equal(0x00u, GET_RAWINPUT_CODE_WPARAM((WPARAM)0x00000000));
290+
Assert.Equal(0x01u, GET_RAWINPUT_CODE_WPARAM((WPARAM)0x00000001));
291+
Assert.Equal(0xFFu, GET_RAWINPUT_CODE_WPARAM((WPARAM)0x0000FFFF));
292+
Assert.Equal(0x12u, GET_RAWINPUT_CODE_WPARAM((WPARAM)0xABCDEF12));
293+
294+
// Test that only lowest 8 bits are extracted
295+
Assert.Equal(0xABu, GET_RAWINPUT_CODE_WPARAM((WPARAM)0x123456AB));
296+
Assert.Equal(0x00u, GET_RAWINPUT_CODE_WPARAM((WPARAM)0xFFFFFF00));
297+
}
298+
}
94299
}

test/GenerationSandbox.Tests/NativeMethods.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,20 @@ GetTickCount
2424
GetWindowText
2525
GetWindowTextLength
2626
HDC_UserSize
27+
GET_APPCOMMAND_LPARAM
28+
GET_DEVICE_LPARAM
29+
GET_FLAGS_LPARAM
30+
GET_KEYSTATE_LPARAM
31+
GET_KEYSTATE_WPARAM
32+
GET_NCHITTEST_WPARAM
33+
GET_RAWINPUT_CODE_WPARAM
34+
GET_WHEEL_DELTA_WPARAM
35+
GET_X_LPARAM
36+
GET_Y_LPARAM
2737
HIWORD
2838
HMENU
2939
HRESULT_FROM_WIN32
40+
MAKEPOINTS
3041
ID2D1HwndRenderTarget
3142
IDirectorySearch
3243
IEnumDebugPropertyInfo
@@ -45,6 +56,7 @@ MAX_PATH
4556
NTSTATUS
4657
PAGESET
4758
PathParseIconLocation
59+
POINTS
4860
PROCESS_BASIC_INFORMATION
4961
PZZSTR
5062
PZZWSTR

0 commit comments

Comments
 (0)