Geoff Chappell, Software Analyst
The CLS structure (formally tagCLS) is, perhaps unsurprisingly, how WIN32K.SYS—and before it, WINSRV.DLL—represents a window class.
The CLS is not documented. Though symbol files for WIN32K.SYS in Windows 8 and higher name the CLS in the C++ decorations of internal routines, type information for the structure is present only in symbol files for Windows 7—not before and not since.
Among undocumented WIN32K structures that have more than just a handful of members, the CLS is striking for its stability. No variation is known since Windwos XP. The following changes of size are known:
Version | Size (x86) | Size (x64) |
---|---|---|
3.10 | 0x60 | |
3.51 | 0x68 | |
4.0 | 0x6C | |
5.0 | 0x58 | |
5.1 to 10.0 | 0x5C | 0xA0 |
It is well known that the CLS itself is not the whole of its size. Each CLS is followed in its allocation by a number of “extra” bytes that is specified as cbClsExtra in the WNDCLASS or WNDCLASSEX when registering the class.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x00 | 0x00 |
CLS *pclsNext; |
all | |
0x04 | 0x08 |
ATOM atomClassName; |
all | |
0x06 | 0x0A |
ATOM atomNVClassName; |
5.1 and higher | |
0x06 (4.0 to 5.0); 0x08 |
0x0C |
USHORT fnid; |
4.0 and higher | |
0x08 (3.10 to 4.0) |
HANDLE hheapDesktop; |
3.10 to 4.0 | ||
0x0C (3.10 to 4.0); 0x08 (5.0); 0x0C |
0x10 |
DESKTOP *rpdeskParent; |
3.51 and higher | |
0x10 (3.51 to 4.0); 0x0C (5.0); 0x10 |
0x18 |
DCE *pdce; |
3.51 and higher | |
0x10 (3.10); 0x14 (3.51 to 4.0) |
INT cWndReferenceCount; |
3.10 to 4.0 | next at 0x28 | |
0x14 (3.10) | unaccounted four bytes | 3.10 only | ||
0x10 (5.0); 0x14 |
0x20 |
USHORT hTaskWow; |
5.0 and higher | previously ULONG at 0x2C |
0x18 (3.10 to 4.0); 0x12 (5.0); 0x16 |
0x22 |
USHORT flags; |
3.10 to 4.0 | |
USHORT CSF_flags; |
5.0 and higher | |||
0x1C (3.10 to 4.0); 0x14 (5.0); 0x18 |
0x28 |
PSTR lpszClientAnsiMenuName; |
all | |
0x20 (3.10 to 4.0); 0x18 (5.0); 0x1C |
0x30 |
PWSTR lpszClientUnicodeMenuName; |
all | |
0x24 (3.10 to 4.0) |
DWORD adwWOW [2]; |
3.10 to 4.0 | next after extra data | |
0x2C (3.10 to 3.51) |
DWORD dwExpWinVer; |
3.10 to 3.51 | ||
0x30 (3.10 to 3.51); 0x2C (4.0) |
ULONG hTaskWow; |
3.10 to 4.0 | next as USHORT at 0x14 | |
0x34 (3.10 to 3.51); 0x30 (4.0); 0x1C (5.0); 0x20 |
0x38 |
CALLPROCDATA *spcpdFirst; |
all | |
0x38 (3.51); 0x34 (4.0); 0x20 (5.0); 0x24 |
0x40 |
CLS *pclsBase; |
3.51 and higher | |
0x3C (3.51); 0x38 (4.0); 0x24 (5.0); 0x28 |
0x48 |
CLS *pclsClone; |
3.51 and higher | |
0x3C (4.0) |
<unknown-type> lpfnWorker; |
4.0 only | ||
0x28 (5.0); 0x2C |
0x50 |
INT cWndReferenceCount; |
5.0 and higher | previously at 0x14 |
For its !dcls command, the USEREXTS debugger extension for Windows NT 3.51 has pDCE and just flags for what symbol files have later as pdce as CSF_flags. It also simplifies to lpszClientMenuName from lpszClientUnicodeMenuName. That said, if the !dso command in the update for Windows NT 4.0 does reliably reproduce names from Microsoft’s headers, then flags is what the CSF_flags actually were named.
More helpfully, these debugger extensions give the name adwWOW for what would otherwise be presented above as an unknown eight-byte structure. Later versions place it after the extra class data, if it is allowed for at all: it is meaningful only for classes that are registered by threads that double as 16-bit tasks.
Not only does the next run of members have the layout of the documented WNDCLASS structure but they are copied as a block from a WNDCLASS in version 3.10 and in later versions from that part of a WNDCLASSEX that follows the cbSize. The debugger extensions for Windows NT 4.0 confirm that they are formally a COMMON_WNDCLASS structure as an unnamed CLS member.
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x38 (3.10); 0x40 (3.51 to 4.0); 0x2C (5.0); 0x30 |
0x54 |
UINT style; |
all |
0x3C (3.10); 0x44 (3.51 to 4.0); 0x30 (5.0); 0x34 |
0x58 |
WNDPROC lpfnWndProc; |
all |
0x40 (3.10); 0x48 (3.51 to 4.0); 0x34 (5.0); 0x38 |
0x60 |
INT cbClsExtra; |
all |
0x44 (3.10); 0x4C (3.51 to 4.0); 0x38 (5.0); 0x3C |
0x64 |
INT cbWndExtra; |
all |
0x48 (3.10); 0x50 (3.51 to 4.0); 0x3C (5.0); 0x40 |
0x68 |
HINSTANCE hModule; |
all |
0x4C (3.10); 0x54 (3.51 to 4.0); 0x40 (5.0); 0x44 |
0x70 |
CURSOR *spicn; |
all |
0x50 (3.10); 0x58 (3.51 to 4.0); 0x44 (5.0); 0x48 |
0x78 |
CURSOR *spcur; |
all |
0x54 (3.10); 0x5C (3.51 to 4.0); 0x48 (5.0); 0x4C |
0x80 |
HBRUSH hbrBackground; |
all |
0x58 (3.10); 0x60 (3.51 to 4.0); 0x4C (5.0); 0x50 |
0x88 |
PWSTR lpszMenuName; |
all |
0x5C (3.10); 0x64 (3.51 to 4.0); 0x50 (5.0); 0x54 |
0x90 |
PSTR lpszAnsiClassName; |
all |
0x68 (4.0); 0x54 (5.0); 0x58 |
0x98 |
CURSOR *spicnSm; |
4.0 and higher |
Debugger extensions from early development kits helpfully have descriptive strings for the bits within the CSF_flags. It seems highly plausible that these are the macros that are used for the bits in the source code.
Mask | Name | Versions (Tentative) |
---|---|---|
0x0001 | CSF_SERVERSIDEPROC | all |
0x0002 | CSF_ANSIPROC | all |
0x0004 | CSF_WOWDEFERDESTROY | 3.51 and higher |
0x0008 | CSF_SYSTEMCLASS | 3.51 and higher |
0x0010 | CSF_WOWCLASS | 5.0 and higher |
0x0020 | CSF_WOWEXTRA | 5.0 and higher |
0x0040 | CSF_CACHEDSMICON | 5.0 and higher |
0x0080 | CSF_WIN40COMPAT | 5.0 and higher |