CLS

The CLS structure (formally tagCLS) is, perhaps unsurprisingly, how WIN32K.SYS—and before it, WINSRV.DLL—represents a window class.

Documentation Status

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.

Variability

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

Layout

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

Flags

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