【Windows编程】第一个Windows程序
Windows 窗口程序运行逻辑笔记(含完整代码)
1. 整体流程概览
一个 Windows 窗口程序的运行分为以下几个核心阶段:
| 阶段 | 核心操作 | 目的 |
|---|---|---|
| 1. 设计窗口类 | 填写 WNDCLASS 结构体 |
定义窗口的”模板”(外观、行为、处理函数) |
| 2. 注册窗口类 | RegisterClass |
将模板告知操作系统 |
| 3. 创建窗口 | CreateWindow |
根据模板在内存中创建实际的窗口对象 |
| 4. 显示窗口 | ShowWindow / UpdateWindow |
让窗口在屏幕上可见 |
| 5. 消息循环 | GetMessage → TranslateMessage → DispatchMessage |
持续接收并分发消息 |
| 6. 窗口过程 | MyWinProc |
处理具体消息(由系统回调) |
2. 为什么需要窗口类(WNDCLASS)?
核心原因:Windows 采用面向对象的设计思想,但 C 语言不支持类和继承,因此通过结构体来模拟”类”的概念。
WNDCLASS 相当于一个窗口模板/蓝图,它描述了:
- 窗口长什么样(图标、光标、背景色、样式)
- 窗口的行为由谁处理(
lpfnWndProc指向窗口过程函数) - 窗口属于哪个应用程序(
hInstance)
2.1 WNDCLASS 结构体详解
1 | typedef struct _WNDCLASS { |
3. 为什么需要注册窗口类(RegisterClass)?
注册窗口类的本质是:将设计好的模板告知操作系统内核,让内核记录下来。
1 | RegisterClass(&wndcls); |
- 注册后,系统内核会维护一个窗口类表,保存
WNDCLASS中的信息(尤其是lpfnWndProc函数地址)。 CreateWindow时,通过类名(className)在表中查找对应的窗口类信息,从而创建窗口实例。
如果不注册:
CreateWindow找不到类名对应的注册信息,创建失败。
3.1 RegisterClass 参数详解
1 | ATOM RegisterClass( |
| 参数 | 说明 |
|---|---|
lpWndClass |
指向已填充好的 WNDCLASS 结构体的指针 |
| 返回值 | 成功返回唯一标识该类的原子(ATOM),失败返回 0 |
| 失败原因 | 类名已注册、参数无效、内存不足等 |
4. 创建窗口(CreateWindow)发生了什么?
调用 CreateWindow 时,系统内核做以下几件事:
| 步骤 | 内核操作 |
|---|---|
| 1 | 通过 className 查找已注册的窗口类 |
| 2 | 从窗口类中读取 lpfnWndProc、背景画刷、图标等属性 |
| 3 | 分配内核对象内存(窗口对象数据结构) |
| 4 | 在调用进程的句柄表中创建一项,返回 HWND(窗口句柄) |
| 5 | 初始化窗口的引用计数等内核对象成员 |
| 6 | 发送 WM_CREATE 消息到窗口过程(允许应用程序做初始化) |
注意:此时窗口尚未在屏幕上显示,只是内核中有了这个窗口对象。
4.1 CreateWindow 参数详解
1 | HWND CreateWindow( |
| 参数 | 说明 | 代码中的值 |
|---|---|---|
lpClassName |
已注册的窗口类名 | className(”MyClassName”) |
lpWindowName |
窗口标题栏文本 | windowName(”MyWindowName”) |
dwStyle |
窗口样式,决定窗口外观和行为 | WS_OVERLAPPEDWINDOW(标准重叠窗口) |
X / Y |
窗口左上角位置 | CW_USEDEFAULT(系统自动选择) |
nWidth / nHeight |
窗口宽高 | CW_USEDEFAULT(系统自动选择) |
hWndParent |
父窗口句柄 | NULL(无父窗口,独立窗口) |
hMenu |
菜单句柄 | NULL(无菜单) |
hInstance |
应用程序实例句柄 | hInstance(WinMain 传入) |
lpParam |
额外创建参数 | NULL(无额外参数) |
4.2 常用窗口样式(dwStyle)
| 样式常量 | 说明 |
|---|---|
WS_OVERLAPPEDWINDOW |
标准重叠窗口(含标题栏、系统菜单、边框、最小化/最大化按钮) |
WS_POPUP |
弹出式窗口(无标题栏边框,常用于对话框) |
WS_CHILD |
子窗口(必须指定父窗口) |
WS_VISIBLE |
创建后立即可见(相当于自动调用 ShowWindow) |
5. 显示窗口
1 | ShowWindow(hwnd, SW_SHOWNORMAL); // 在屏幕上绘制窗口 |
5.1 ShowWindow 参数详解
1 | BOOL ShowWindow( |
nCmdShow 值 |
说明 |
|---|---|
SW_SHOWNORMAL |
正常显示窗口(还原并激活) |
SW_SHOW |
显示窗口(不改变激活状态) |
SW_HIDE |
隐藏窗口 |
SW_MAXIMIZE |
最大化显示 |
SW_MINIMIZE |
最小化显示 |
SW_RESTORE |
恢复到正常大小 |
5.2 UpdateWindow 说明
1 | BOOL UpdateWindow(HWND hWnd); |
- 立即发送
WM_PAINT消息到窗口过程,强制刷新客户区 - 如果窗口没有需要重绘的内容,则不发送
WM_PAINT
6. 消息循环(Message Loop)
1 | MSG msg; |
6.1 MSG 结构体详解
1 | typedef struct tagMSG { |
6.2 三个核心函数
GetMessage
1 | BOOL GetMessage( |
| 参数 | 说明 |
|---|---|
lpMsg |
输出参数,接收取出的消息 |
hWnd |
指定只接收特定窗口的消息(NULL = 所有窗口) |
wMsgFilterMin/Max |
消息范围过滤(0 = 接收所有消息) |
| 返回值 | TRUE(收到非 WM_QUIT 消息)FALSE(收到 WM_QUIT) |
TranslateMessage
1 | BOOL TranslateMessage( |
- 将键盘虚拟键消息(
WM_KEYDOWN)转换为字符消息(WM_CHAR) - 例如:物理按键 ‘A’ →
WM_KEYDOWN→TranslateMessage→WM_CHAR(字符 ‘a’ 或 ‘A’)
DispatchMessage
1 | LRESULT DispatchMessage( |
- 将消息分发给目标窗口的窗口过程函数
- 系统通过
hwnd→ 窗口对象 → 窗口类 →lpfnWndProc找到MyWinProc - 返回值:窗口过程
MyWinProc的返回值
7. 消息分发机制(DispatchMessage 如何调用 MyWinProc)
1 | 应用程序调用 DispatchMessage(&msg) |
关键数据结构关联:
1 | 注册时:className → WNDCLASS → { lpfnWndProc: MyWinProc, ... } |
8. 窗口过程(MyWinProc)
1 | LRESULT CALLBACK MyWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) |
8.1 窗口过程参数详解
| 参数 | 类型 | 说明 |
|---|---|---|
hwnd |
HWND |
接收消息的窗口句柄(用于区分多个窗口实例) |
uMsg |
UINT |
消息标识符(如 WM_PAINT、WM_CLOSE 等) |
wParam |
WPARAM |
第一个消息参数(含义取决于消息类型) |
lParam |
LPARAM |
第二个消息参数(含义取决于消息类型) |
| 返回值 | LRESULT |
消息处理结果(含义取决于消息类型) |
8.2 常见消息详解
| 消息 | 触发时机 | wParam | lParam | 建议处理 |
|---|---|---|---|---|
WM_CHAR |
键盘输入产生字符 | 字符代码(如 ‘A’) | 按键状态信息 | 处理文本输入 |
WM_LBUTTONDOWN |
鼠标左键按下 | 按键状态标志 | 鼠标坐标(X 低位,Y 高位) | 响应点击操作 |
WM_PAINT |
窗口需要重绘 | 0(未使用) | 0(未使用) | 绘制客户区内容 |
WM_CLOSE |
用户点击关闭按钮 | 0(未使用) | 0(未使用) | 询问是否关闭,调用 DestroyWindow |
WM_DESTROY |
窗口被销毁 | 0(未使用) | 0(未使用) | 调用 PostQuitMessage(0) 退出消息循环 |
WM_CREATE |
窗口创建时 | 0(未使用) | CREATESTRUCT* |
初始化窗口数据 |
8.3 为什么需要调用 DefWindowProc?
1 | default: |
| 函数 | 作用 |
|---|---|
DefWindowProc |
执行 Windows 系统默认的消息处理(窗口移动、尺寸调整、最小化等) |
- 窗口过程只处理自己关心的消息(如
WM_PAINT、WM_CHAR) - 所有未处理的消息必须交给
DefWindowProc执行默认处理 - 如果不调用
DefWindowProc,窗口将无法正常工作(无法移动、无法调整大小、无法最小化等)
9. 完整代码
1 |
|
10. 流程图
1 | ┌─────────────────────────────────────────────────────────────────────────────┐ |
11. 关键点总结
| 要点 | 说明 |
|---|---|
| 为什么要写窗口类 | 窗口类相当于”模板”或”蓝图”,定义了窗口的外观和行为。采用面向对象设计,支持多个窗口共享同一类属性。 |
| 为什么要注册 | 将窗口类信息存入系统内核,供 CreateWindow 查询使用。内核需要保存 lpfnWndProc 地址以便后续消息分发。 |
| 创建窗口的本质 | 在系统内核中分配窗口对象内存,返回句柄 HWND。句柄是用户态访问内核对象的唯一标识。 |
| 消息分发机制 | DispatchMessage 通过 hwnd 找到对应的窗口对象,再从窗口对象关联的窗口类中取出 lpfnWndProc,最终回调 MyWinProc。 |
| 消息循环的作用 | 持续从系统消息队列取消息并分发。没有消息循环,窗口无法响应用户操作。 |
| DefWindowProc 的作用 | 处理所有不感兴趣的消息(如窗口移动、尺寸变化),确保窗口具备标准 Windows 窗口的基本行为。 |
| WM_DESTROY 与 PostQuitMessage | 窗口销毁时发送 WM_DESTROY,在其中调用 PostQuitMessage(0) 向消息队列投递 WM_QUIT,让 GetMessage 返回 FALSE 退出消息循环。 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 星の夜!
