The evolution of window and class extra bytes in Windows - The Old New Thing
Skip to main content
Dev Blogs
AI
All .NET posts
.NET MAUI<br>ASP.NET Core<br>Blazor<br>Entity Framework
C++<br>C#<br>F#<br>TypeScript
NuGet<br>Servicing<br>.NET Blog in Chinese
Microsoft for Developers<br>Agent Framework<br>Develop from the cloud<br>Xcode<br>ISE Developer<br>TypeScript<br>PowerShell<br>Python<br>Java<br>Java Blog in Chinese<br>Go<br>Microsoft Edge Dev<br>Microsoft 365 Developer<br>Microsoft Entra Identity Developer<br>Microsoft Entra PowerShell
Visual Studio<br>Visual Studio Code<br>Aspire
All things Azure<br>Azure SDK<br>Azure VM Runtime Team<br>Microsoft Azure<br>Azure Cosmos DB<br>Azure DocumentDB<br>Azure Data Studio<br>Azure SQL<br>DevOps<br>DirectX<br>Microsoft Foundry<br>Power Platform
OData<br>Unified Data Model (IDEAs)
Windows Command Line<br>#ifdef Windows<br>Inside MSIX<br>MIDI and music<br>React Native<br>The Old New Thing<br>Windows Developer
Raymond Chen
Windows provides a family of functions for accessing so-called "extra bytes". There are two categories of extra bytes: Class extra bytes (which belong to the window class) and window extra bytes (which belong to each window created from that class). Applications can request extra bytes at class registration, and those are accessed at increasing offsets starting at zero. The system also defines a number of extra bytes, and those use negative offsets.
We’re going to look at the system-defined offsets.
In 16-bit Windows, these were the available extra bytes and the function you used to read them:
Name<br>Size<br>Accessor<br>Notes
GCW_MENUNAME<br>int16_t<br>GetClassWord
GCW_HBRBACKGROUND<br>int16_t<br>GetClassWord
GCW_HCURSOR<br>int16_t<br>GetClassWord
GCW_HICON<br>int16_t<br>GetClassWord
GCW_HMODULE<br>int16_t<br>GetClassWord
GCW_CBWNDEXTRA<br>int16_t<br>GetClassWord
GCW_CBCLSEXTRA<br>int16_t<br>GetClassWord
GCL_WNDPROC<br>int32_t<br>GetClassLong
GCW_STYLE<br>int16_t<br>GetClassWord
GCW_ATOM<br>int16_t<br>GetClassWord<br>Added in Windows 3.1
GWL_WNDPROC<br>int32_t<br>GetWindowLong
GWW_HINSTANCE<br>int16_t<br>GetWindowWord
GWW_HWNDPARENT<br>int16_t<br>GetWindowWord
GWW_ID<br>int16_t<br>GetWindowWord
GWL_STYLE<br>int32_t<br>GetWindowLong
GWL_EXSTYLE<br>int32_t<br>GetWindowLong<br>Added in Windows 3.0
DWL_MSGRESULT<br>int32_t<br>GetWindowLong<br>For dialog windows
DWL_DLGPROC<br>int32_t<br>GetWindowLong<br>For dialog windows
DWL_USER<br>int32_t<br>GetWindowLong<br>For dialog windows
There is clearly a naming pattern here for class and window bytes.
The first letter G stands for Get. The second letter C or W stands for Class or Window. And the third letter W or L stands for Word or Long.¹
For window bytes that apply only to dialog windows, the first letter changes to D for "dialog". These values are zero or positive, since they are really just extra bytes registered to the standard dialog class.
Now, in 16-bit Windows, handles were 16-bit values, but in 32-bit Windows, they expand to 32-bit values, so 32-bit Windows changed the functions from GetSomethingWord to GetSomethingLong, and the prefixes correspondingly changed from W to from L. So our table now looks like this:
Name<br>16-bit prefix/size<br>32-bit prefix/size
MENUNAME<br>GCW_ int16_t<br>GCL_ int32_t ◱
HBRBACKGROUND<br>GCW_ int16_t<br>GCL_ int32_t ◱
HCURSOR<br>GCW_ int16_t<br>GCL_ int32_t ◱
HICON<br>GCW_ int16_t<br>GCL_ int32_t ◱
HMODULE<br>GCW_ int16_t<br>GCL_ int32_t ◱
CBWNDEXTRA<br>GCW_ int16_t<br>GCL_ int32_t ◱
CBCLSEXTRA<br>GCW_ int16_t<br>GCL_ int32_t ◱
WNDPROC<br>GCL_ int32_t<br>GCL_ int32_t ◱
STYLE<br>GCW_ int16_t<br>GCL_ int32_t ◱
ATOM<br>GCW_ int16_t<br>GCW_ int16_t
HICONSM
GCL_ int32_t 💥
WNDPROC<br>GWL_ int32_t<br>GWL_ int32_t ◱
HWNDPARENT<br>GWW_ int16_t<br>GWL_ int32_t ◱
ID<br>GWW_ int16_t<br>GWL_ int32_t ◱
STYLE<br>GWL_ int32_t<br>GWL_ int32_t
EXSTYLE<br>GWL_ int32_t<br>GWL_ int32_t
USERDATA
GWL_ int32_t 💥
MSGRESULT<br>DWL_ int32_t<br>DWL_ int32_t
DLGPROC<br>DWL_ int32_t<br>DWL_ int32_t
USER<br>DWL_ int32_t<br>DWL_ int32_t
The ◱ symbol represents a value that got bigger, and the 💥 symbol represents values that did not exist in 16-bit Windows.
Even though control IDs are typically small integers, the space for them was expanded from a 16-bit value to a 32-bit value because some people were using it to hold pointers or handles. (One way to create a process-wide unique number is to allocate memory and use its address.)
The next step in the evolution of extra bytes is the conversion from 32-bit to 64-bit Windows. Pointers and handles expand to 64-bit values on 64-bit Windows, so all of the extra bytes that are used to (or could be used to) hold a handle or pointer were expanded to a 64-bit version.
To make it possible to write code that targets both 32-bit and 64-bit Windows, the design of 64-bit Windows didn’t make the hard break that 32-bit Windows did from 16-bit Windows. Instead, they introduced new functions that accept pointer-sized integers, which are 32-bit values on 32-bit Windows and 64-bit values on 64-bit Windows. That way, you just use those new functions everywhere, and they will expand on 64-bit systems and remain the same on 32-bit systems.
The new functions have names like GetWindowLongPtr, and the corresponding prefixes were changed to GWLP_ and so...