Windows中窗口和类额外字节的演变

The Old New Thing (Raymond Chen) 新闻

摘要

本文追溯了Windows中窗口和类额外字节从16位到32位再到64位的演变,详细描述了随着句柄和指针的扩展,函数名称和数据大小的变化。

<p>Windows提供了一系列用于访问所谓“额外字节”的函数。额外字节分为两类:类额外字节(属于窗口类)和窗口额外字节(属于从该类创建的每个窗口)。应用程序可以在注册类时请求额外字节,这些字节从零开始递增偏移量进行访问。系统也定义了一些额外字节,它们使用负偏移量。</p> <p>我们将探讨系统定义的偏移量。</p> <p>在16位Windows中,以下是可用的额外字节及其读取函数:</p> <table style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3"> <tbody> <tr> <th>名称</th> <th>大小</th> <th>访问器</th> <th>备注</th> </tr> <tr> <td><tt>GCW_MENUNAME</tt></td> <td>int16_t</td> <td><tt>GetClassWord</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GCW_HBRBACKGROUND</tt></td> <td>int16_t</td> <td><tt>GetClassWord</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GCW_HCURSOR</tt></td> <td>int16_t</td> <td><tt>GetClassWord</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GCW_HICON</tt></td> <td>int16_t</td> <td><tt>GetClassWord</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GCW_HMODULE</tt></td> <td>int16_t</td> <td><tt>GetClassWord</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GCW_CBWNDEXTRA</tt></td> <td>int16_t</td> <td><tt>GetClassWord</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GCW_CBCLSEXTRA</tt></td> <td>int16_t</td> <td><tt>GetClassWord</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GCL_WNDPROC</tt></td> <td>int32_t</td> <td><tt>GetClassLong</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GCW_STYLE</tt></td> <td>int16_t</td> <td><tt>GetClassWord</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GCW_ATOM</tt></td> <td>int16_t</td> <td><tt>GetClassWord</tt></td> <td>在Windows 3.1中新增</td> </tr> <tr> <td><tt>GWL_WNDPROC</tt></td> <td>int32_t</td> <td><tt>GetWindowLong</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GWW_HINSTANCE</tt></td> <td>int16_t</td> <td><tt>GetWindowWord</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GWW_HWNDPARENT</tt></td> <td>int16_t</td> <td><tt>GetWindowWord</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GWW_ID</tt></td> <td>int16_t</td> <td><tt>GetWindowWord</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GWL_STYLE</tt></td> <td>int32_t</td> <td><tt>GetWindowLong</tt></td> <td>&nbsp;</td> </tr> <tr> <td><tt>GWL_EXSTYLE</tt></td> <td>int32_t</td> <td><tt>GetWindowLong</tt></td> <td>在Windows 3.0中新增</td> </tr> <tr> <td><tt>DWL_MSGRESULT</tt></td> <td>int32_t</td> <td><tt>GetWindowLong</tt></td> <td>用于对话框窗口</td> </tr> <tr> <td><tt>DWL_DLGPROC</tt></td> <td>int32_t</td> <td><tt>GetWindowLong</tt></td> <td>用于对话框窗口</td> </tr> <tr> <td><tt>DWL_USER</tt></td> <td>int32_t</td> <td><tt>GetWindowLong</tt></td> <td>用于对话框窗口</td> </tr> </tbody> </table> <p>这里,类和窗口字节的命名模式非常明显。</p> <p>第一个字母 <tt>G</tt> 代表 <tt>Get</tt>。第二个字母 <tt>C</tt> 或 <tt>W</tt> 代表 <tt>Class</tt> 或 <tt>Window</tt>。第三个字母 <tt>W</tt> 或 <tt>L</tt> 代表 <tt>Word</tt> 或 <tt>Long</tt>。¹</p> <p>对于仅适用于对话框窗口的窗口字节,第一个字母变为 <tt>D</tt>,表示“对话框”。这些值为零或正数,因为它们实际上只是注册到标准对话框类的额外字节。</p> <p>现在,在16位Windows中,句柄是16位值,但在32位Windows中,它们扩展到32位值,因此32位Windows将函数从 <code>Get­Something­<span style="border: solid 1px currentcolor;">Word</span></code> 改为 <code>Get­Something­<span style="border: solid 1px currentcolor;">Long</span></code>,前缀也相应地从 <tt>W</tt> 变为 <tt>L</tt>。因此,我们的表格现在如下所示:</p> <table style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3"> <tbody> <tr> <th>名称</th> <th>16位前缀/大小</th> <th>32位前缀/大小</th> </tr> <tr> <td><tt>MENUNAME</tt></td> <td><tt>GCW_</tt> int16_t</td> <td><tt>GCL_</tt> int32_t ◱</td> </tr> <tr> <td><tt>HBRBACKGROUND</tt></td> <td><tt>GCW_</tt> int16_t</td> <td><tt>GCL_</tt> int32_t ◱</td> </tr> <tr> <td><tt>HCURSOR</tt></td> <td><tt>GCW_</tt> int16_t</td> <td><tt>GCL_</tt> int32_t ◱</td> </tr> <tr> <td><tt>HICON</tt></td> <td><tt>GCW_</tt> int16_t</td> <td><tt>GCL_</tt> int32_t ◱</td> </tr> <tr> <td><tt>HMODULE</tt></td> <td><tt>GCW_</tt> int16_t</td> <td><tt>GCL_</tt> int32_t ◱</td> </tr> <tr> <td><tt>CBWNDEXTRA</tt></td> <td><tt>GCW_</tt> int16_t</td> <td><tt>GCL_</tt> int32_t ◱</td> </tr> <tr> <td><tt>CBCLSEXTRA</tt></td> <td><tt>GCW_</tt> int16_t</td> <td><tt>GCL_</tt> int32_t ◱</td> </tr> <tr> <td><tt>WNDPROC</tt></td> <td><tt>GCL_</tt> int32_t</td> <td><tt>GCL_</tt> int32_t ◱</td> </tr> <tr> <td><tt>STYLE</tt></td> <td><tt>GCW_</tt> int16_t</td> <td><tt>GCL_</tt> int32_t ◱</td> </tr> <tr> <td><tt>ATOM</tt></td> <td><tt>GCW_</tt> int16_t</td> <td><tt>GCW_</tt> int16_t</td> </tr> <tr> <td><tt>HICONSM</tt></td> <td>&nbsp;</td> <td><tt>GCL_</tt> int32_t <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a5.png" alt="💥" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td> </tr> <tr> <td><tt>WNDPROC</tt></td> <td><tt>GWL_</tt> int32_t</td> <td><tt>GWL_</tt> int32_t ◱</td> </tr> <tr> <td><tt>HWNDPARENT</tt></td> <td><tt>GWW_</tt> int16_t</td> <td><tt>GWL_</tt> int32_t ◱</td> </tr> <tr> <td><tt>ID</tt></td> <td><tt>GWW_</tt> int16_t</td> <td><tt>GWL_</tt> int32_t ◱</td> </tr> <tr> <td><tt>STYLE</tt></td> <td><tt>GWL_</tt> int32_t</td> <td><tt>GWL_</tt> int32_t</td> </tr> <tr> <td><tt>EXSTYLE</tt></td> <td><tt>GWL_</tt> int32_t</td> <td><tt>GWL_</tt> int32_t</td> </tr> <tr> <td><tt>USERDATA</tt></td> <td>&nbsp;</td> <td><tt>GWL_</tt> int32_t <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a5.png" alt="💥" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td> </tr> <tr> <td><tt>MSGRESULT</tt></td> <td><tt>DWL_</tt> int32_t</td> <td><tt>DWL_</tt> int32_t</td> </tr> <tr> <td><tt>DLGPROC</tt></td> <td><tt>DWL_</tt> int32_t</td> <td><tt>DWL_</tt> int32_t</td> </tr> <tr> <td><tt>USER</tt></td> <td><tt>DWL_</tt> int32_t</td> <td><tt>DWL_</tt> int32_t</td> </tr> </tbody> </table> <p>◱ 符号表示值变大了,而 <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a5.png" alt="💥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 符号表示在16位Windows中不存在的值。</p> <p>尽管控件ID通常是小整数,但它们的空间从16位扩展到了32位,因为<a title="为什么计时器ID和对话框控件ID在64位Windows上是64位值?你真的认为人们会创建超过40亿个计时器或对话框控件吗?" href="https://devblogs.microsoft.com/oldnewthing/20191010-00/?p=102978">有些人用它来存储指针或句柄</a>。(一种创建进程内唯一编号的方法是<a title="一个窗口不能有两个相同ID的计时器,那么如何分配一个无人使用的ID?" href="https://devblogs.microsoft.com/oldnewthing/20191009-00/?p=102974">分配内存并使用其地址</a>。)</p> <p>额外字节演变的下一步是从32位到64位Windows的转换。在64位Windows上,指针和句柄扩展到64位值,因此所有用于(或可能用于)保存句柄或指针的额外字节都扩展到了64位版本。</p> <p>为了能够编写同时针对32位和64位Windows的代码,64位Windows的设计没有像32位Windows与16位Windows那样进行硬性断裂。相反,他们引入了接受指针大小整数的新函数,这些整数在32位Windows上是32位值,在64位Windows上是64位值。这样,您只需在所有地方使用这些新函数</p>
查看原文
查看缓存全文

缓存时间: 2026/06/30 05:16

# Windows 中窗口类和窗口额外字节的演变 - 旧事新说 来源:https://devblogs.microsoft.com/oldnewthing/20260629-00?p=112484 Windows 提供了一组函数用于访问所谓的“额外字节”。额外字节分为两类:类额外字节(属于窗口类)和窗口额外字节(属于从该类创建的每个窗口)。应用程序可以在注册类时申请额外字节,并通过从零开始的递增偏移量进行访问。系统也定义了一些额外字节,这些字节使用负偏移量。 我们将重点研究系统定义的偏移量。 在 16 位 Windows 中,可用的额外字节及其读取函数如下: | 名称 | 大小 | 访问函数 | 备注 | |------|------|----------|------| | 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 | 在 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 | 在 Windows 3.0 中添加 | | DWL_MSGRESULT | int32_t | GetWindowLong | 用于对话框窗口 | | DWL_DLGPROC | int32_t | GetWindowLong | 用于对话框窗口 | | DWL_USER | int32_t | GetWindowLong | 用于对话框窗口 | 这里明显存在类字节和窗口字节的命名模式。 首字母 `G` 代表 `Get`(获取)。第二个字母 `C` 或 `W` 代表 `Class`(类)或 `Window`(窗口)。第三个字母 `W` 或 `L` 代表 `Word`(字)或 `Long`(长整型)[^1]。 仅适用于对话框窗口的窗口字节,首字母改为 `D`(代表“对话框”)。这些值为零或正数,因为它们实际上是注册到标准对话框类的额外字节。 在 16 位 Windows 中,句柄是 16 位值,但在 32 位 Windows 中,它们扩展为 32 位值,因此 32 位 Windows 将函数从 `GetSomethingWord` 改为 `GetSomethingLong`,相应的前缀也从 `W` 改为 `L`。于是表格变为: | 名称 | 16 位前缀/大小 | 32 位前缀/大小 | |------|----------------|----------------| | 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 | 其中 ◱ 符号代表值变大,💥 符号代表这些值在 16 位 Windows 中不存在。 尽管控件 ID 通常是较小的整数,但存储它们的空间从 16 位扩展到了 32 位,因为有人用它们来存放指针或句柄[^2]。(创建进程范围内唯一编号的一种方法是分配内存并使用其地址[^3]。) 额外字节演变的下一步是从 32 位 Windows 迁移到 64 位 Windows。在 64 位 Windows 中,指针和句柄扩展为 64 位值,因此所有用于(或可能用于)保存句柄或指针的额外字节都被扩展为 64 位版本。 为了使代码能同时面向 32 位和 64 位 Windows,64 位 Windows 的设计并未像 32 位 Windows 与 16 位 Windows 那样硬性割裂。相反,它们引入了新函数,这些函数接受指针大小的整数——在 32 位 Windows 上是 32 位值,在 64 位 Windows 上是 64 位值。这样,你可以在任何地方使用这些新函数,它们在 64 位系统上会扩展,在 32 位系统上保持不变。 新函数名称类似于 `GetWindowLongPtr`,相应的前缀改为 `GWLP_` 等。 | 名称 | 16 位前缀/大小 | 32 位前缀/大小 | 32/64 位前缀/大小 | |------|----------------|----------------|-------------------| | 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 ◱ | 从额外字节名称的前缀可以看出它应配合哪个函数使用。 | 前缀 | 对应函数 | |------|----------| | GCW_ ↔ | GetClassWord | | GWW_ ↔ | GetWindowWord | | GCL_ ↔ | GetClassLong | | GWL_ ↔ | GetWindowLong | | GCLP_ ↔ | GetClassLongPtr | | GWLP_ ↔ | GetWindowLongPtr | 比较特殊的是 `DWLP_`,因为它需要同时编码可使用的窗口类型(`D` = 对话框)以及对应的函数名(`WindowLongPtr`)。 作为让步,Windows 允许你将 `GCL_` 和 `GWL_` 值分别传递给 `GetClassLongPtr` 和 `GetWindowLongPtr`,即便它们本意是与 `GetClassLong` 和 `GetWindowLong` 配合使用。若这样做,你会得到对应的 32 位值,必要时会零扩展为指针大小[^2]。这种情况主要体现在 `GWL_ID` 上,因为多数人不会用到全部 ID 范围,所以如果你愿意局限于 32 位子集,就可以假装这些值不是指针大小[^3]。 “为什么非要更改所有前缀?这难道不是给从 32 位代码移植到 64 位代码的人制造大量额外工作吗?” 是的,但这是有益的额外工作。目的是在需要修改的地方强制产生构建中断,因为你必须调用访问指针大小整数的函数,而不是 32 位整数;否则就会遭遇整数截断错误。 [^1]: 这是经典 Win32 常见的命名约定。例如,`ShowWindow` 的操作参数前缀为 `SW_`;`SetWindowPos` 的标志前缀为 `SWP_`;`GetWindow` 的关系参数前缀为 `GW_`。 [^2]: 将 `GWL_` 值与 `SetWindowLongPtr` 一起使用时问题稍多。看起来你存储的是指针大小的整数,但实际上只有低 32 位被保留。 [^3]: `ID` 比较特殊,它同时定义为 `GWL_ID` 和 `GWLP_ID`。其他所有值只定义了一个前缀。 ## 分类 ## 主题 ## 作者 **Raymond Chen** Raymond 参与 Windows 的演变已有 30 余年。2003 年,他创办了一个名为“旧事新说(The Old New Thing)”的网站,其受欢迎程度远远超出了他最大胆的想象,这一发展至今仍让他感到不安。该网站还衍生出一本书,巧合的是书名也叫《The Old New Thing》(Addison Wesley 2007)。他偶尔会出现在 Windows 开发文档的 Twitter 账号上,讲述一些毫无用处的故事。

相似文章

关于滥用Windows窗口类额外字节的兼容性说明

The Old New Thing (Raymond Chen)

Raymond Chen 讨论了一个历史性的 Windows 兼容性问题,其中一些 16 位程序滥用窗口类额外字节来存储私有数据,以及微软如何在保持向后兼容性的同时,对 32 位和 64 位程序堵住了这个漏洞。

Let's Decode the Mystery Bytes [video]

Hacker News Top

本视频通过反汇编和WinDbg调试工具,揭示了在x86环境下calloc分配内存时出现的8个神秘字节实际上是Windows堆管理器的条目头部,包含当前块大小、前一块大小等信息。

为Windows 3.1改造WM_COPY­DATA消息

The Old New Thing (Raymond Chen)

本文解释如何利用共享地址空间,将针对32位Windows引入的WM_COPYDATA消息改造到16位Windows 3.1上,使得16位程序间的通信无需任何更改。

Windows UI 演变:点击未关联的文件

Lobsters Hottest

历史回顾:从 Windows 386 到 Windows XP,Windows 操作系统打开未关联文件的用户界面如何演变,展示了对话框的演进以及 Web 服务提示的引入。