The evolution of window and class extra bytes in Windows

Stratoscope1 pts0 comments

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 Get­Something­Word to Get­Something­Long, 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 Get­Window­Long­Ptr, and the corresponding prefixes were changed to GWLP_ and so...

int32_t windows int16_t bytes extra gcl_

Related Articles