Windows 编程/句柄和数据类型
许多 Win32 API 的初学者都会发现,要处理的旧数据类型数量众多。有时,仅仅记住所有正确的数据类型顺序比编写一个好的程序更难。本页面将介绍一些程序员会遇到的数据类型。
首先,让我们快速了解一下某些数据类型和某些变量的命名约定。Win32 API 使用所谓的“匈牙利命名法”来命名变量。匈牙利命名法要求变量以其数据类型缩写作为前缀,这样您在阅读代码时就可以确切地知道它是哪种类型的变量。Win32 API 实施这种做法的原因是数据类型众多,难以全部记住。此外,有许多本质上定义相同的数据类型,因此某些编译器在错误使用它们时不会发现错误。在讨论每个数据类型时,我们也会说明该数据类型的常用前缀。
在数据类型或变量前加上字母“P”或“p”通常表示该变量是指针。字母“LP”或前缀“lp”代表“长指针”,在 32 位机器上与普通指针完全相同。LP 数据对象仅仅是从 Windows 3.1 或更早版本继承下来的旧对象,当时指针和长指针需要区分。在现代的 32 位系统上,这些前缀可以互换使用。
LPVOID 数据类型被定义为“指向空对象指针”。这对某些人来说可能很奇怪,但是 ANSI-C 标准允许将通用指针定义为“void*”类型。这意味着 LPVOID 指针可以用于指向任何类型的对象,而不会产生编译器错误。但是,程序员有责任跟踪指向的是哪种类型的对象。
此外,某些 Win32 API 函数可能包含标记为“LPVOID lpReserved”的参数。这些保留的数据成员永远不要在程序中使用,因为它们要么依赖于尚未由 Microsoft 实现的功能,要么只在某些应用程序中使用。如果您看到一个带有“LPVOID lpReserved”参数的函数,则必须始终为该参数传递 NULL 值 - 如果您没有这样做,某些函数将会失败。
LPVOID 对象通常没有前缀,尽管在 LPVOID 变量前加字母“p”作为前缀比较常见,因为它是一个指针。
这些数据类型被定义为特定长度,与目标平台无关。头文件中有相当多的额外复杂性才能实现这一点,但结果是代码非常标准化,并且可以很好地移植到不同的硬件平台和不同的编译器。
DWORD(双字),这些数据类型中最常见的,被定义为始终为无符号 32 位量。在任何机器上,无论是 16 位、32 位还是 64 位,DWORD 始终是 32 位长。由于这种严格的定义,DWORD 在 32 位机器上非常普遍且流行,但在 16 位和 64 位机器上不太常见。
WORD(单字)被严格定义为无符号 16 位值,与您正在编程的机器无关。BYTE 被严格定义为无符号 8 位值。QWORD(四字),虽然很少见,但被定义为无符号 64 位量。在这些标识符的前面加上“P”表示该变量是指针。在前面加上两个“P”表示它是指向指针的指针。这些变量可能没有前缀,或者它们可以使用 DWORD 的任何常用前缀。由于编译器的差异,这些数据类型的定义可能不同,但通常使用这些定义。
#include <stdint.h>
typedef uint8_t BYTE; typedef uint16_t WORD; typedef uint32_t DWORD; typedef uint64_t QWORD;
请注意,这些定义在所有编译器中都不相同。众所周知,GNU GCC 编译器使用长和短说明符的方式与 Microsoft C 编译器不同。因此,Windows 头文件通常会根据所使用的编译器对这些数据类型使用条件声明。这样可以使代码更具可移植性。
像往常一样,我们可以将这些类型的指针定义为
#include <stdint.h>
typedef uint8_t * PBYTE; typedef uint16_t * PWORD; typedef uint32_t * PDWORD; typedef uint64_t * PQWORD;
typedef uint8_t ** PPBYTE; typedef uint16_t ** PPWORD; typedef uint32_t ** PPDWORD; typedef uint64_t ** PPQWORD;
DWORD 变量通常以“dw”为前缀。同样,我们有以下前缀
数据类型 | 前缀 |
---|---|
BYTE | "b" |
WORD | "w" |
DWORD | "dw" |
QWORD | "qw" |
这些类型没有定义为特定长度。主机确定每个类型到底有多少位。
- 类型
typedef long LONG; typedef unsigned long ULONG; typedef int INT; typedef unsigned int UINT; typedef short SHORT; typedef unsigned short USHORT; typedef char CHAR; typedef unsigned char UCHAR;
- LONG 表示法
- LONG 变量通常以“l”(小写 L)为前缀。
- UINT 表示法
- UINT 变量通常以“i”或“ui”为前缀,表示它是整数并且是无符号的。
- CHAR、UCHAR 表示法
- 这些变量通常分别以“c”或“uc”为前缀。
如果变量的大小无关紧要,则可以使用这些整数类型。但是,如果您想精确指定变量的大小,使其具有特定数量的位,请使用 BYTE、WORD、DWORD 或 QWORD 标识符,因为它们的长度与平台无关且永不改变。
STR 数据类型是字符串数据类型,已分配存储空间。这种数据类型不如 LPSTR 常用。STR 数据类型用于将字符串视为立即数组,而不是简单字符指针。STR 数据类型的变量名前缀是“sz”,因为它是一个以零结尾的字符串(以空字符结尾)。
大多数程序员不会将变量定义为 STR,而是选择将其定义为字符数组,因为将其定义为数组允许显式设置数组的大小。此外,在堆栈上创建大型字符串会导致非常不希望出现的堆栈溢出问题。
LPSTR 代表“指向 STR 的长指针”,本质上是定义为这样的
#define STR * LPSTR;
LPSTR 可以像其他字符串对象一样使用,只是 LPSTR 被明确定义为 ASCII,而不是 Unicode,并且此定义将在所有平台上生效。LPSTR 变量通常以字母“lpsz”为前缀,表示“指向以零结尾的字符串的长指针”。前缀的“sz”部分很重要,因为 Windows 中的一些字符串(尤其是在谈论 DDK 时)不是以零结尾的。LPSTR 数据类型和以“lpsz”为前缀的变量都可以与标准库 <string.h> 函数无缝使用。
TCHAR 数据类型,如 Unicode 部分所述,是通用字符数据类型。TCHAR 可以保存标准的 1 字节 ASCII 字符或宽的 2 字节 Unicode 字符。由于此数据类型是由宏定义的,并且不是固定的,因此只应将字符数据与该类型一起使用。TCHAR 的定义类似于以下内容(尽管对于不同的编译器可能有所不同)
#ifdef UNICODE #define TCHAR WORD #else #define TCHAR BYTE #endif
TCHAR 的字符串通常称为 TSTR 数据类型。更常见的是,它们被定义为 LPTSTR 类型,如下所示
#define TCHAR * LPTSTR
这些字符串可以是 UNICODE 或 ASCII,具体取决于 UNICODE 宏的状态。LPTSTR 数据类型是指向通用字符串的长指针,可以包含 ASCII 字符串或 Unicode 字符串,具体取决于所使用的环境。LPTSTR 数据类型也以字母“lpsz”为前缀。
HANDLE 数据类型是 Win32 编程中最重要的一些数据对象,也是新程序员最难理解的一些数据对象。在内核内部,Windows 维护着一个包含内核负责的所有不同对象的表格。Windows、按钮、图标、鼠标指针、菜单等等,都在表格中有一个条目,每个条目都被分配了一个唯一的地址,称为 HANDLE。如果你想从表格中选取一个特定的条目,你需要给 Windows 提供 HANDLE 值,Windows 会返回相应的表格条目。
HANDLE 被定义为 void 指针 (void*)。它们用作程序中每个 Windows 对象(如按钮、窗口、图标等)的唯一标识符。具体来说,它们的定义如下:typedef PVOID HANDLE; 和 typedef void *PVOID; 换句话说,HANDLE = void*。
HANDLE 通常以 "h" 为前缀。句柄是 Windows 在内部用来跟踪内存中对象的无符号整数。Windows 会移动内存中的对象,例如内存块,以腾出空间,如果对象在内存中被移动,句柄表就会被更新。
以下是一些值得讨论的特殊句柄
HWND
[edit | edit source]HWND 数据类型是 "指向窗口的句柄",用于跟踪出现在屏幕上的各种对象。要与特定窗口通信,你需要拥有窗口句柄的副本。HWND 变量通常以字母 "hwnd" 为前缀,这样程序员就知道它们很重要。
通常,主窗口被定义为
HWND hwnd;
子窗口被定义为
HWND hwndChild1, hwndChild2...
而对话框句柄被定义为
HWND hDlg;
虽然你可以自由地在自己的程序中为这些变量命名任何你想要的名字,但是当选择了一个特立独行的命名方案(或者更糟的是,根本没有方案)时,可读性和兼容性就会受到影响。
HINSTANCE
[edit | edit source]HINSTANCE 变量是指向程序实例的句柄。每个程序都获得一个实例变量,这很重要,这样内核才能与程序通信。例如,如果你想创建一个新窗口,你需要将你的程序的 HINSTANCE 变量传递给内核,这样内核就知道新窗口属于哪个程序实例。如果你想与另一个程序通信,拥有该程序实例句柄的副本通常非常有用。HINSTANCE 变量通常以 "h" 为前缀,而且由于一个程序中通常只有一个 HINSTANCE 变量,因此将该变量声明为这样的规范是
HINSTANCE hInstance;
将此 HINSTANCE 变量设为全局值通常是有益的,这样你的所有函数在需要时都可以访问它。
HMENU
[edit | edit source]如果你的程序有一个下拉菜单可用(大多数可视化 Windows 程序都有),那么该菜单将有一个与之关联的 HMENU 句柄。要显示菜单或更改其内容,你需要访问此 HMENU 句柄。HMENU 句柄通常以 "h" 为前缀。
WPARAM, LPARAM
[edit | edit source]在 Microsoft Windows 的早期,参数以两种格式之一传递给窗口:WORD 长度 (16 位) 参数和 LONG 长度 (32 位) 参数。这些参数类型被定义为 WPARAM (16 位) 和 LPARAM (32 位)。但是,在现代 32 位和 64 位系统中,WPARAM 和 LPARAM 都能容纳一个指针,因此分别为 32 位和 64 位长。但是,由于历史原因,名称没有改变。
WPARAM 和 LPARAM 变量是通用的函数参数,经常被类型转换为其他数据类型,包括指针和 DWORD。