Geoff Chappell, Software Analyst
The WND (formally tagWND) is the primary representation of a window. It is in some ways a very important structure, certainly to access quickly, and especially for USER32 to read directly in user mode.
The WND is not documented. Though symbol files for WIN32K.SYS in Windows 8 and higher name the WND in the C++ decorations of internal routines, type information for the structure is present in symbol files for Windows 7 only—not before and not since.
That said, many of Microsoft’s names for both the WND and its members were long known in public from the plain-text output of meta-commands that are implemented in debugger extensions that Microsoft supplied with its debuggers in various kits for both kernel-mode and user-mode programming. See, for instance, USEREXTS.DLL from as far back as the Device Driver Kit (DDK) for Windows NT 3.51. Though the WND structure is not formally documented, Microsoft certainly has understood that knowledge of it may help programmers in the depths of debugging what they’re doing with Windows!
Perhaps not surprisingly, given its need to be accessible from both kernel-mode and user-mode components, the WND does not vary nearly as much as do other undocumented structures. Almost all of the members that are the most useful to know in practice, e.g., while debugging, have been stable since version 5.1. No member has shifted position since version 6.0. Additions since version 6.2 have all been made by appending to the structure. The following changes of size are known:
Version | Size (x86) | Size (x64) |
---|---|---|
3.10 | 0x9C | |
3.51 | 0xA8 | |
4.0 | 0xB0 | |
5.0 | 0x98 | |
5.1 to 5.2 | 0xA0 | 0x0108 |
6.0 to 6.1 | 0xB0 | 0x0128 |
6.2 | 0xC8 | 0x0150 |
6.3 | 0xD8 | 0x0170 |
10.0 | 0xE0 | 0x0178 |
It is well known that the WND is not the whole of its size. Each WND is followed in its allocation by a number of “extra” bytes that is specified as cbWndExtra in the WNDCLASS or WNDCLASSEX when registering the window class that the window is created from.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x00 | 0x00 |
HEAD head; |
3.10 only | |
THROBJHEAD head; |
3.51 to 4.0 | |||
THRDESKHEAD head; |
5.0 and higher | |||
0x0C (3.10); 0x10 (3.51) |
unknown heap handle | 3.10 to 3.51 | ||
0x14 | 0x28 |
WW; |
5.0 and higher | previously at 0x90 |
0x10 (3.10); 0x14 (3.51 to 4.0); 0x2C |
0x48 |
WND *spwndNext; |
all | |
0x30 | 0x50 |
WND *spwndPrev; |
5.1 and higher | |
0x14 (3.10); 0x18 (3.51 to 4.0); 0x30 (5.0); 0x34 |
0x58 |
WND *spwndParent; |
all | |
0x18 (3.10); 0x1C (3.51 to 4.0); 0x34 (5.0); 0x38 |
0x60 |
WND *spwndChild; |
all | |
0x1C (3.10); 0x20 (3.51 to 4.0); 0x38 (5.0); 0x3C |
0x68 |
WND *spwndOwner; |
all | |
0x20 (3.10) | unaccounted | 3.10 only | ||
0x24 (3.10 to 3.51) |
DESKTOP *spdeskParent; |
3.10 to 3.51 | next as rpdesk in head | |
0x28 (3.51); 0x24 (4.0) |
WND *pwo; |
3.51 to 4.0 | next as SysWNDO property | |
0x28 (3.10); 0x2C (3.51); 0x28 (4.0); 0x3C (5.0); 0x40 |
0x70 |
RECT rcWindow; |
all | |
0x38 (3.10); 0x3C (3.51); 0x38 (4.0); 0x4C (5.0); 0x50 |
0x80 |
RECT rcClient; |
all | |
0x48 (3.10); 0x4C (3.51); 0x48 (4.0); 0x5C (5.0); 0x60 |
0x90 |
WNDPROC lpfnWndProc; |
all | |
0x4C (3.10); 0x50 (3.51); 0x4C (4.0); 0x60 (5.0); 0x64 |
0x98 |
CLS *pcls; |
all | |
0x54 (3.51); 0x50 (4.0); |
INT cbwndExtra; |
3.51 to 4.0 | next at 0x88 | |
0x50 (3.10); 0x58 (3.51); 0x54 (4.0); 0x64 (5.0); 0x68 |
0xA0 |
HRGN hrgnUpdate; |
all | |
0x54 (3.10); 0x5C (3.51); 0x58 (4.0) |
WND *spwndLastActive; |
3.10 to 4.0 | next at 0x8C | |
0x58 (3.10); 0x60 (3.51); 0x5C (4.0); 0x68 (5.0); 0x6C |
0xA8 |
PROPLIST *ppropList; |
all | |
0x5C (3.10); 0x64 (3.51); 0x60 (4.0); 0x6C (5.0); 0x70 |
0xB0 |
SBINFO *pSBInfo; |
all | |
0x60 (3.10); 0x68 (3.51); 0x64 (4.0); 0x70 (5.0); 0x74 |
0xB8 |
MENU *spmenuSys; |
all | |
0x64 (3.10); 0x6C (3.51); 0x68 (4.0); 0x74 (5.0); 0x78 |
0xC0 |
MENU *spmenu; |
all | |
0x78 (5.0); 0x7C |
0xC8 (6.1) |
HRGN hrgnClip; |
5.0 and higher | previously at 0x88 |
0x80 | 0xD0 (6.1) |
HRGN hrgnNewFrame; |
6.0 and higher | |
0x68 (3.10); 0x70 (3.51); 0x6C (4.0); 0x7C (5.0); 0x80 (5.1 to 5.2); 0x84 |
0xD8 (6.1) | unknown PWSTR | 3.10 to 3.51 | |
LARGE_UNICODE_STRING strName; |
4.0 and higher | |||
0x6C (3.10); 0x74 (3.51); 0x78 (4.0) |
CHAR bFullScreen; |
3.10 to 4.0 | next as FullScreenMode in state2 | |
0x6D (3.10); 0x75 (3.51); 0x79 (4.0) |
CHAR cDC; |
3.10 to 4.0 | ||
0x6E (3.10); 0x76 (3.51); 0x7A (4.0) |
USHORT fnid; |
3.10 to 4.0 | next at 0x16 in WW | |
0x70 (3.10); 0x78 (3.51); 0x7C (4.0) |
DWORD dwExpWinVer; |
3.10 to 4.0 | ||
0x74 (3.10); 0x7C (3.51); 0x80 (4.0) |
DWORD dwUserData; |
3.10 to 4.0 | next at 0x94 | |
0x78 (3.10); 0x80 (3.51); 0x84 (4.0) |
HDC hdcOwn; |
3.10 to 4.0 | ||
0x84 (3.51); 0x88 (4.0) |
HRGN hrgnClip; |
3.51 to 4.0 | next at 0x78 | |
0x8C (4.0) |
INT iHungRedraw; |
4.0 only | ||
0x7C (3.10); 0x88 (3.51); 0x90 (4.0) |
WW; |
3.10 to 4.0 | next at 0x14; last member in 3.10 to 4.0 |
|
0x88 (5.0); 0x8C (5.1 to 5.2); 0x90 |
0xE0 (5.2); 0xE8 |
INT cbwndExtra; |
5.0 and higher | previously at 0x50 |
0x8C (5.0); 0x90 (5.1 to 5.2); 0x94 |
0xE8 (5.2); 0xF0 |
WND *spwndLastActive; |
5.0 and higher | previously at 0x58 |
0x90 (5.0); 0x94 (5.1 to 5.2); 0x98 |
0xF0 (5.2); 0xF8 |
HIMC hImc; |
5.0 and higher | |
0x94 (5.0); 0x98 (5.1 to 5.2); 0x9C |
0xF8 (5.2); 0x0100 |
DWORD dwUserData; |
5.0 and higher | previously at 0x80; last member in 5.0 |
0x9C (5.1 to 5.2); 0xA0 |
0x0100 (5.2); 0x0108 |
ACTIVATION_CONTEXT *pActCtx; |
5.1 and higher | last member in 5.1 to 5.2 |
0xA4 | 0x0110 |
D3DMATRIX *pTransform; |
6.0 and higher | |
0xA8 | 0x0118 |
WND *spwndClipboardListenerNext; |
6.0 and higher | |
0xAC | 0x0120 |
union { ULONG ExStyle2; /* bit fields, follow link */ }; |
6.0 and higher | last member in 6.0 to 6.1 |
Where its !dw command describes what symbol files later name as pSBInfo, the USEREXTS debugger extension for Windows NT 3.51 uses the name rgwScroll, but what’s pointed to is what’s later known as an SBINFO even if it and its members once had other names.
The spmenu member does a double duty that is not at all suggested by its name but is no surprise from the documented interpretation of the CreateWindow function’s hMenu argument. For a child window, spmenu is not a pointer to a MENU but is instead the window’s ID such as returned by the GetWindowLong function when given the index GWL_ID (-12).
Whatever may have been intended by its Hungarian prefix, the bFullScreen member is not a boolean even in version 3.10. As the FullScreenMode bit field in union with state2 for version 5.0 and higher, it is 3 bits wide.
Microsoft’s names for members that have been added to the WND since Windows 7 may never be known. On the plus side, if only for neatness of presentation, new members have been added simply by appending.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0xB0 | 0x0128 | unaccounted | 6.2 and higher | |
0xB4 | 0x0130 | unknown dword | 6.2 and higher | |
0xB8 | 0x0134 | unknown dword (version number) | 6.2 and higher | |
0xBC | 0x0138 | unknown THREADINFO pointer | 6.2 and higher | |
0xC0 | 0x0140 | unknown dword (bit fields) | 6.2 and higher | |
0xC4 | 0x0148 | unknown pointer | 6.2 and higher | last member in 6.2 |
0xC8 | 0x0150 | unknown INPUTTRANSFORMLIST pointer | 6.3 and higher | |
0xCC | 0x0158 | unknown dword | 6.3 and higher | |
0xD0 | 0x0160 | unknown HMONITOR | 6.3 and higher | |
0xD4 | 0x0168 | unknown word | 6.3 and higher | last member in 6.3 |
0xD8 | 0x016C | unknown dword | 10.0 and higher | |
0xDC | 0x0170 | unknown dword (bit fields) | 10.0 and higher | last member in 10.0 |
The pointer at offsets 0xC4 and 0x0148 is accessible through the GetWindowLongPtr and SetWindowLongPtr functions, using -2 as the index.
The word at offsets 0xD4 and 0x0168 is the window’s DPI scaling, such as posted in both the low and high 16 bits of the wParam for a WM_DPICHANGED (0x02E0) message.
While the WW is thought to be nested in no other structure, it may as well be presented here. It is nowadays 0x18 or 0x20 bytes in 32-bit and 64-bit Windows, respectively, but is 0x20 bytes before version 5.0.
That said, even if the WW never exists except in a WND, or when copied from one, it evidently is intended to be visible in its own right. In all Windows versions, the address of the nested WW is returned by the GetWindowLong function when given the index -1 (which is conspicuously not among the documented inputs). That the structure is named WW and does not originally begin with the bit fields is obscure. The name seems to be public only in the output of the !dso debugger commands as implemented by the USEREXTS and USERKDX debugger extensions from the DDKs for Windows NT 4.0 and Windows 2000.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x00 (3.10 to 4.0) | unaccounted | 3.10 to 4.0 | ||
0x10 (3.10 to 3.51); 0x0C (4.0); 0x00 |
0x00 |
union { ULONG state; /* bit fields, follow link */ }; |
all | |
0x10 (4.0); 0x04 |
0x04 |
union { ULONG state2; /* bit fields, follow link */ }; |
4.0 and higher | |
0x14 (3.10 to 4.0); 0x08 |
0x08 |
union { ULONG ExStyle; /* bit fields, follow link */ }; |
all | |
0x18 (3.10 to 4.0); 0x0C |
0x0C |
union { ULONG style; /* bit fields, follow link */ }; |
all | |
0x1C (3.10 to 4.0); 0x10 |
0x10 |
PVOID hModule; |
all | |
0x14 | 0x18 |
USHORT hMod16; |
5.0 and higher | |
0x16 | 0x1A |
USHORT fnid; |
5.0 and higher | previously at 0x7A in WND |
That some sort of substructure wraps the state, state2 (in applicable versions), ExStyle and style has long been a supportable proposition. Right from the start, WINSRV and then WIN32K has code for accessing these members’ bits by a 16-bit encoding whose high and low bytes are respectively a byte offset from the first member and a byte-wide bit mask. The only known instance of this encoding in WINSRV version 3.10 is 0x0320 for the state bit that symbol files later name bAnsiCreator. Version 3.51, however, has 0x0A10 and 0x0A20 for bits in the style, which puts beyond doubt that these members make a tightly integrated set. Very likely, given the optimisation capability of Microsoft’s compiler even in the early 90s, most of this 16-bit encoding’s use in all versions is more readily visible in the source code than in the binary. By version 4.0, the encoding can be seen more in USER32 than in WIN32K: changes to these flags by USER32 require a transition to WIN32K with the encoding passed as an argument, not interpreted at compile-time and optimised away.
For the output of its !dw command, the USEREXTS debugger extension from the DDK for Windows NT 3.51 has dwExStyle for the extended window styles that symbol files and later versions of that debugger extension present simply as ExStyle. It is here thought that the difference is more of how the debugger extension’s programmer thought of the member, or thought it would better be presented to programmers, than of how it was named in Microsoft’s source code.