惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
SegmentFault 最新的问题
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Attack and Defense Labs
Attack and Defense Labs
F
Full Disclosure
Vercel News
Vercel News
N
News | PayPal Newsroom
The GitHub Blog
The GitHub Blog
H
Hacker News: Front Page
H
Heimdal Security Blog
P
Privacy International News Feed
博客园 - 司徒正美
Google DeepMind News
Google DeepMind News
N
Netflix TechBlog - Medium
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cisco Blogs
L
Lohrmann on Cybersecurity
D
Docker
Recent Announcements
Recent Announcements
Security Archives - TechRepublic
Security Archives - TechRepublic
人人都是产品经理
人人都是产品经理
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Proofpoint News Feed
T
Tailwind CSS Blog
C
Check Point Blog
博客园 - 叶小钗
Google Online Security Blog
Google Online Security Blog
Martin Fowler
Martin Fowler
Stack Overflow Blog
Stack Overflow Blog
博客园 - 聂微东
S
Secure Thoughts
博客园 - Franky
博客园_首页
阮一峰的网络日志
阮一峰的网络日志
P
Palo Alto Networks Blog
Latest news
Latest news
量子位
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 三生石上(FineUI控件)
The Cloudflare Blog
Last Week in AI
Last Week in AI
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Cyberwarzone
Cyberwarzone
小众软件
小众软件
Cisco Talos Blog
Cisco Talos Blog
Hacker News: Ask HN
Hacker News: Ask HN
T
Threatpost
T
Tenable Blog
P
Privacy & Cybersecurity Law Blog
WordPress大学
WordPress大学

The Old New Thing

A compatibility note on the abuse of Windows window class extra bytes - The Old New Thing The case of the DLL that was not present in memory despite not being formally unloaded, part 2 - The Old New Thing Raymond's hot take on Hainanese chicken - The Old New Thing The case of the DLL that was not present in memory despite not being formally unloaded, part 1 - The Old New Thing Cancellation of Windows Runtime activities is asynchronous - The Old New Thing Microspeak elaborated: Isn't escrow just a release candidate by another name? - The Old New Thing In memory of the man who put red and green squiggles under words - The Old New Thing What does it mean when the bottom bit of my HMODULE is set? - The Old New Thing Why doesn't Get­Last­Input­Info() return info for the user I'm impersonating? - The Old New Thing Windows stack limit checking retrospective, follow-up - The Old New Thing Retrofitting the WM_COPY­DATA message onto Windows 3.1 - The Old New Thing The time the x86 emulator team found code so bad that they fixed it during emulation - The Old New Thing How can I schedule work on a thread pool with low latency? Understanding the rationale behind a rule when trying to circumvent it What’s the opposite of Clip­Cursor that lets me exclude the cursor from a region? The Microsoft Company Party where everybody played name tag swap Rotation revisited: Shuffling more than three blocks, and other small notes The back cover of C++: The Programming Language also raises questions not answered by the front cover Rotation revisited: Avoiding having to calculate the gcd when doing cycle decomposition Rotation revisited: Cycle decomposition in clang’s libcxx Rotation revisited: A shocking discovery about gcc’s unidirectional rotation algorithm Rotation revisited: Another unidirectional algorithm The placeholder name for the Windows 8 experience was “modern” Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 3 Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 2 Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 1 If C# and JavaScript lets me await a Windows Runtime asynchronous operation more than once, why not C++/WinRT? A hypothetical redesign of System.Diagnostics.Process to avoid confusion over properties that are valid only when you are the one who called Start Why do you say that a COM STA thread must pump messages if I see sample code creating STA threads and not pumping messages? How do I use Win32 structures from the Windows Runtime? What is the history of the ERROR_ARENA_TRASHED error code? The classic TreeView control lets me sort by name or by lParam, but why not both? Just shows that nobody cares about debugging the parity flag any more The case of the Create­File­Mapping that always reported ERROR_ALREADY_EXISTS
The evolution of window and class extra bytes in Windows - The Old New Thing
Raymond Chen · 2026-06-29 · via The Old New Thing

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 Size Accessor Notes
GCW_MENUNAME int16_t GetClassWord  
GCW_HBRBACKGROUND int16_t GetClassWord  
GCW_HCURSOR int16_t GetClassWord  
GCW_HICON int16_t GetClassWord  
GCW_HMODULE int16_t GetClassWord  
GCW_CBWNDEXTRA int16_t GetClassWord  
GCW_CBCLSEXTRA int16_t GetClassWord  
GCL_WNDPROC int32_t GetClassLong  
GCW_STYLE int16_t GetClassWord  
GCW_ATOM int16_t GetClassWord Added in Windows 3.1
GWL_WNDPROC int32_t GetWindowLong  
GWW_HINSTANCE int16_t GetWindowWord  
GWW_HWNDPARENT int16_t GetWindowWord  
GWW_ID int16_t GetWindowWord  
GWL_STYLE int32_t GetWindowLong  
GWL_EXSTYLE int32_t GetWindowLong Added in Windows 3.0
DWL_MSGRESULT int32_t GetWindowLong For dialog windows
DWL_DLGPROC int32_t GetWindowLong For dialog windows
DWL_USER int32_t GetWindowLong 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 16-bit prefix/size 32-bit prefix/size
MENUNAME GCW_ int16_t GCL_ int32_t ◱
HBRBACKGROUND GCW_ int16_t GCL_ int32_t ◱
HCURSOR GCW_ int16_t GCL_ int32_t ◱
HICON GCW_ int16_t GCL_ int32_t ◱
HMODULE GCW_ int16_t GCL_ int32_t ◱
CBWNDEXTRA GCW_ int16_t GCL_ int32_t ◱
CBCLSEXTRA GCW_ int16_t GCL_ int32_t ◱
WNDPROC GCL_ int32_t GCL_ int32_t ◱
STYLE GCW_ int16_t GCL_ int32_t ◱
ATOM GCW_ int16_t GCW_ int16_t
HICONSM   GCL_ int32_t 💥
WNDPROC GWL_ int32_t GWL_ int32_t ◱
HWNDPARENT GWW_ int16_t GWL_ int32_t ◱
ID GWW_ int16_t GWL_ int32_t ◱
STYLE GWL_ int32_t GWL_ int32_t
EXSTYLE GWL_ int32_t GWL_ int32_t
USERDATA   GWL_ int32_t 💥
MSGRESULT DWL_ int32_t DWL_ int32_t
DLGPROC DWL_ int32_t DWL_ int32_t
USER DWL_ int32_t 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 on.

Name 16-bit prefix/size 32-bit prefix/size 32/64-bit prefix/size
MENUNAME GCW_ int16_t GCL_ int32_t ◱ GCLP_ intptr_t ◱
HBRBACKGROUND GCW_ int16_t GCL_ int32_t ◱ GCLP_ intptr_t ◱
HCURSOR GCW_ int16_t GCL_ int32_t ◱ GCLP_ intptr_t ◱
HICON GCW_ int16_t GCL_ int32_t ◱ GCLP_ intptr_t ◱
HMODULE GCW_ int16_t GCL_ int32_t ◱ GCLP_ intptr_t ◱
CBWNDEXTRA GCW_ int16_t GCL_ int32_t ◱ GCL_ int32_t
CBCLSEXTRA GCW_ int16_t GCL_ int32_t ◱ GCL_ int32_t
WNDPROC GCL_ int32_t GCL_ int32_t ◱ GCLP_ intptr_t ◱
STYLE GCW_ int16_t GCL_ int32_t ◱ GCL_ int32_t
ATOM GCW_ int16_t GCW_ int16_t GCW_ int16_t
HICONSM   GCL_ int32_t 💥 GCLP_ intptr_t ◱
WNDPROC GWL_ int32_t GWL_ int32_t ◱ GWLP_ intptr_t ◱
HWNDPARENT GWW_ int16_t GWL_ int32_t ◱ GWLP_ intptr_t ◱
ID GWW_ int16_t GWL_ int32_t ◱ GWLP_ intptr_t ◱
STYLE GWL_ int32_t GWL_ int32_t GWL_ int32_t
EXSTYLE GWL_ int32_t GWL_ int32_t GWL_ int32_t
USERDATA   GWL_ int32_t 💥 GWLP_ intptr_t ◱
MSGRESULT DWL_ int32_t DWL_ int32_t DWLP_ intptr_t ◱
DLGPROC DWL_ int32_t DWL_ int32_t DWLP_ intptr_t ◱
USER DWL_ int32_t DWL_ int32_t DWLP_ intptr_t ◱

From the prefix on the name of the extra bytes, you can read off which function it is meant to be used with.

Prefix Function
GCW_ ↔ GetClassWord GWW_ ↔ GetWindowWord
GCL_ ↔ GetClassLong GWL_ ↔ GetWindowLong
GCLP_ ↔ GetClassLongPtr GWLP_ ↔ GetWindowLongPtr

The weirdo is DWLP_ because it needs to encode both the type of window that it can be used with (D = dialog) as well as the function name it goes with (WindowLongPtr).

As a concession, Windows lets you pass GCL_ and GWL_ values to GetClassLongPtr and GetWindowLongPtr (respectively) even though they are intended to be used with GetClassLong and GetWindowLong (respectively). If you do that, you get the corresponding 32-bit value zero-extended if necessary to be the size of a pointer.² This is seen primarily in the case of GWL_ID because most people don’t use the full range of IDs, so if you’re willing to live within the 32-bit subset, you can just pretend that the values are not pointer-sized.³

“Why bother changing all the prefixes? Doesn’t that just create a lot of busy work for people porting from 32-bit code to 64-bit code?”

Yes, but it’s good busy work. The point is to force build breaks at places where you need to make fixes, because you have to call the function that accesses a pointer-sized integer rather than a 32-bit integer; otherwise you suffer from integer truncation bugs.

¹ This is a common prefixing convention for classic Win32. For example, the operation parameter to ShowWindow is prefixed SW_; the flags to SetWindowPos are prefixed SWP_; and the relationship parameter for GetWindow is prefixed GW_.

² The use of the GWL_ values with SetWindowLongPtr is a bit more problematic. It looks like you’re storing a pointer-sized integer, but only the bottom 32 bits are honored.

³ The ID is unusual in that it is defined both as GWL_ID and GWLP_ID. All of the other values are defined with only one prefix.

Category

Topics

Author

Raymond Chen

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.