## Windows 数据类型

在 C 和 C++ 的经典 Win 32 编程中，大多数函数使用 Windows 特定的 Typedef 和 `#define` 宏（在 `windef.h` 中定义）来指定参数类型和返回值。 这些 Windows 数据类型通常是为 C/C++ 内置类型提供的特殊名称（别名）。 有关这些 typedef 和预处理器定义的完整列表，请参阅 [Windows 数据类型](https://learn.microsoft.com/zh-cn/windows/win32/WinProg/windows-data-types)。 其中一些 typedef（例如 `HRESULT` 和 `LCID`）很有用且具有描述性。 `INT` 等其他类型没有特殊含义，只是基础 C++ 类型的别名。 其他 Windows 数据类型的名称自 C 编程和 16 位处理器得到保留，并且在现代硬件或操作系统中不具有目的和意义。 还有与 Windows 运行时库相关的特定数据类型，它们在列表中显示为 [Windows 运行时基础数据类型](https://learn.microsoft.com/zh-cn/windows/win32/WinRT/base-data-types)。 在新式 C++ 中，一般准则是首选 C++ 基本类型，除非 Windows 类型传达一些有关如何解释值的额外意义。

### 为什么需要句柄？

你可能会问：为什么不直接用指针？因为**指针太危险，也太不稳定**。

在 Windows 这类虚拟内存系统里，对象在物理内存中的地址可能会被系统移动[](https://zhuanlan.zhihu.com/p/331810183?utm_medium=openapi_platform&utm_source=e10c3ad29e50)[](https://cloud.tencent.cn/developer/article/1646020?from=15425)。如果你直接用指针，对象一移动，指针就失效了。而句柄是系统维护的一个**稳定标识**，对象搬到哪里，句柄都能让系统找到它[](https://cloud.tencent.cn/developer/article/1646020?from=15425)[](https://cloud.tencent.cn/developer/article/1603881?from=15425&frompage=seopage)。同时，句柄也起到了**封装和安全隔离**的作用，防止程序随意乱改系统资源[](https://zhuanlan.zhihu.com/p/331810183?utm_medium=openapi_platform&utm_source=e10c3ad29e50)[](https://cloud.tencent.cn/developer/ask/sof/104018331?from=16139)。

## 句柄是什么

英文 `Handle` 本意是“柄、把手”。现实中的锅有一个手柄，抓住手柄就能操作锅，但手柄本身并不是锅，也不能让你完成与锅无关的事情。

Windows 中的句柄也是类似的“操作入口”：

- 窗口有窗口句柄 `HWND`
- 设备上下文有 `HDC`
- 画笔有 `HPEN`
- 模块实例有 `HINSTANCE`
- 文件、进程、线程、事件、互斥体等也都有相应句柄

程序通过句柄告诉 Windows：“我要操作这个对象。”句柄本身不是对象，而是对象的代表或访问入口。

## 句柄和指针的区别

指针保存的是对象在进程地址空间中的内存地址，可以直接通过指针访问对象内容。

句柄则是一种间接引用。程序只知道句柄值，Windows 再根据这个值查找真正的对象。程序通常不能直接访问对象的内部结构，也不能依赖对象当前的内存地址。

可以简单表示为：

```
句柄 -> 系统内部的映射表 -> 对象地址 -> 实际对象
```

这样做有几个好处：

1. 隐藏对象内部结构，防止应用程序随意修改系统对象。
2. 操作系统可以移动或重新组织对象，而应用程序仍然使用原来的句柄。
3. 不必在函数之间复制整个对象，只需传递一个较小的标识值。
4. 操作系统可以通过句柄检查访问权限和对象状态。

因此，句柄更像是一个索引、编号或中间人，而不是对象本身。

## 句柄的实现形式

Windows 中不同句柄的具体定义并不完全相同。

例如某些旧式定义可能类似：

```
#define DECLARE_HANDLE(name) \
    struct name##__ { int unused; }; \
    typedef struct name##__ *name
```

展开 `DECLARE_HANDLE(HDC)` 后，大致相当于：

```
struct HDC__
{
    int unused;
};

typedef struct HDC__* HDC;
```

这类写法使用一个不完整或不透明的结构体指针，让不同类型的句柄不能随意混用。虽然它在语法上像指针，但调用者不应该访问结构体内容。

有些通用句柄则可能定义为：

```
typedef void* PVOID;
typedef PVOID HANDLE;
```

这说明句柄在某些实现中表现为无类型指针；在其他情况下，也可能表现为整数。具体形式取决于 Windows 版本、API 类型和编译环境。

所以不能简单断言“所有句柄都是指针”或“所有句柄都是 32 位整数”。更准确的说法是：

> 句柄是一种不透明的系统标识符，底层可能使用整数、指针或其他编码形式，程序不应依赖其内部表示。

## 为什么不直接使用对象指针

Windows 内核对象通常包含大量状态信息，例如：

- 打开的文件
- 创建的线程
- 进程
- 窗口
- 事件
- 互斥体
- 设备对象

这些对象可能需要几百甚至几千字节来保存。如果函数之间直接传递整个对象，会产生复制开销；如果直接传递对象地址，又会产生安全和稳定性问题：

- 应用程序知道地址后，可能直接修改对象内部数据。
- Windows 可能移动对象在内存中的位置。
- 对象地址暴露后，系统难以控制访问权限。
- 对象销毁后，旧地址可能变成无效地址，甚至被其他对象重新使用。

因此，Windows 让程序保存一个句柄。句柄通常在对象生命周期内保持有效，而对象内部地址由系统负责维护。

## 句柄的生命周期

对象创建时，系统通常会分配一个句柄：

```
创建对象 -> 获得句柄 -> 使用句柄操作对象 -> 销毁对象 -> 句柄失效
```

例如，创建窗口后会得到 `HWND`。只要窗口存在，这个句柄通常可以用来调用窗口相关 API：

```
HWND -> GetWindowText(HWND, ...)
HWND -> ShowWindow(HWND, ...)
HWND -> DestroyWindow(HWND)
```

窗口销毁后，原来的 `HWND` 就不能继续使用。即使之后重新创建一个看起来相同的窗口，也不能假定它会得到相同的句柄。

程序重新启动后，也不能假定系统会再次分配同一个句柄。句柄值由系统动态管理，不能当作永久编号。

## 一个生活中的例子

可以把程序看成去电影院看电影：

- 电影对象相当于系统对象。
- 电影票上的座位号相当于句柄。
- 你通过座位号找到自己的座位，但座位号本身不是座位。
- 下一场电影或下一次入场时，可能会分配到不同座位号。

同样，应用程序通过句柄找到窗口、文件或线程，但不能假定这个句柄永远对应同一个对象。

## MFC 中的相关例子

在 MFC 中，Windows API 的句柄通常会被包装成 C++ 类。例如：

- `HWND` 对应窗口对象
- `HDC` 对应设备上下文
- `CDC` 是 MFC 对设备上下文的封装类
- `CWnd` 是 MFC 对窗口的封装类

`HDC` 是设备上下文句柄，而 `CDC` 是 MFC 类，两者不是同一种东西。

MFC 中常见的转换方式包括：

- `GetSafeHwnd()`：从 MFC 窗口对象获得其 `HWND`
- `CWnd::FromHandle()`：根据 `HWND` 获取或关联一个 MFC 窗口对象

也就是说，MFC 类对象和 Windows 原生句柄之间可以互相配合，但它们仍然是两个不同层次的概念。

## 句柄、进程 ID 和 HINSTANCE

### 句柄

句柄用于代表或访问某个对象。它的有效范围通常受到进程或对象类型限制。一个进程中的句柄值，不能直接当作另一个进程中的句柄使用；如果需要跨进程传递，通常要使用 `DuplicateHandle` 等机制。

### 进程 ID

进程 ID 是系统用来标识进程的编号，用于区分系统中的不同进程。它是一个身份标识，而不一定是访问进程对象的句柄。

程序可以通过进程 ID 查找进程，但执行某些操作时，仍然需要先获得进程句柄，并且该句柄还必须具备相应访问权限。

### HINSTANCE

`HINSTANCE` 用于表示程序模块或实例。在现代 Windows 中，它通常与模块加载地址有关，但使用者应把它当作一个模块句柄使用，而不是自行把它当作普通地址进行运算。

## 函数句柄

广义上，句柄也可以表示一种间接调用接口。例如函数句柄允许程序保存一个与函数相关的标识，再通过这个标识调用函数。

函数指针直接保存函数地址，而函数句柄可以由系统或运行环境管理。这样可以隐藏函数实际位置，并允许系统在需要时改变内部实现。

不过在普通 C/C++ 代码中，函数指针和 Windows 对象句柄是不同概念，不能混为一谈。

## 最准确的理解

可以把句柄总结为：

> 句柄是操作系统分配给对象的、不透明的间接引用。程序通过句柄请求系统访问对象，而不直接访问对象的内部数据或内存地址。

因此：

```
指针：直接表示对象地址
句柄：间接表示对象
对象：由操作系统真正管理的数据结构
```

句柄的重点不在于它底层究竟是整数还是指针，而在于它提供了一个受系统控制的访问入口。