Window编程初探(一)

参考:https://www.bilibili.com/video/BV1By4y1r7Cq

写一个简单的窗口程序,流程如下:

启动WinMain
├─ 定义窗口

├─ 注册窗口类(RegisterClassW)

├─ 创建窗口(CreateWindowW)→ 发送WM_CREATE → WndProc弹窗

├─ 显示窗口(ShowWindow)

└─ 进入消息循环:

├─ 用户操作 → 生成消息 → GetMessage捕获

├─ TranslateMessage转换键盘输入

├─ DispatchMessage → 调用WndProc处理

└─ WM_CLOSE → DestroyWindow

前置知识

Windows 消息处理机制总结

Windows 是一个基于消息驱动的操作系统,应用程序通过消息循环(Message Loop)接收并处理系统或用户输入的事件(如鼠标、键盘、窗口绘制等)。以下是其核心流程和关键概念:


1. 消息处理的核心流程

(1) 消息产生

消息由以下来源产生:

  • 用户输入(如鼠标点击 WM_LBUTTONDOWN、键盘按下 WM_KEYDOWN)。
  • 系统事件(如窗口大小调整 WM_SIZE、窗口关闭 WM_CLOSE)。
  • 应用程序自身(通过 SendMessage 或 PostMessage 发送)。
(2) 消息进入消息队列
  • 系统队列(System Queue):硬件输入(如键盘、鼠标)先进入系统队列,再由 Windows 分发到对应线程的消息队列。
  • 线程消息队列(Thread Message Queue):每个 GUI 线程维护自己的消息队列,存储待处理的消息。
(3) 消息循环(Message Loop)

应用程序通过 GetMessage → TranslateMessage → DispatchMessage 循环处理消息:

MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);  // 转换键盘消息(如 WM_KEYDOWN → WM_CHAR)
    DispatchMessage(&msg);   // 将消息发送到窗口过程(lpfnWndProc)
}
  • GetMessage:从消息队列取出消息,若队列为空,线程会等待(不占用 CPU)。
  • TranslateMessage:将键盘按键消息转换为字符消息(如 WM_KEYDOWN + WM_KEYUP → WM_CHAR)。
  • DispatchMessage:调用窗口的 lpfnWndProc 处理消息。
(4) 窗口过程(Window Procedure)

每个窗口都有一个 lpfnWndProc(窗口过程函数),负责处理消息:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_CLOSE:
            DestroyWindow(hwnd); // 关闭窗口
            break;
        case WM_DESTROY:
            PostQuitMessage(0); // 退出消息循环
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam); // 默认处理
    }
    return 0;
}
  • 若消息未被处理,必须调用 DefWindowProc 执行默认行为(如窗口拖动、最小化等)。

2. 消息的类型

消息类型说明
输入消息WM_KEYDOWNWM_MOUSEMOVE(用户交互)
窗口管理消息WM_CREATEWM_SIZEWM_CLOSE(窗口生命周期)
绘制消息WM_PAINT(窗口需要重绘)
系统命令消息WM_COMMAND(菜单/按钮点击)
自定义消息WM_USER 或 WM_APP + 自定义值(SendMessage/PostMessage 发送)

3. 消息的发送方式

函数行为
PostMessage将消息放入队列后立即返回(异步),由消息循环处理。
SendMessage直接调用窗口过程,等待处理完成(同步),阻塞调用线程。
PeekMessage检查消息队列但不阻塞(适用于游戏等实时应用)。

过程

首先是WinMain,相当于main函数,参数含义已由注释给出。

int WINAPI WinMain(
	HINSTANCE hInstance,      // 当前程序实例句柄(由系统分配)
	HINSTANCE hPreHinstance,  // 前一个实例句柄(已废弃,总是NULL)
	LPSTR lpCmdLine,          // 命令行参数(ANSI字符串)
	int nCmdShow              // 窗口初始显示状态(最小化/正常等)
)
{
	return 0; 
}

第一步,定义并初始化窗口类,windows.h给了WNDCLASS以及WNDCLASSEXW、WNDCLASSW、WNDCLASSA等变种,WNDCLASSEXW比较常用,此处我们使用WNDCLASSW。

详见WNDCLASSW (winuser.h) – Win32 apps | Microsoft Learn

需要注意的是初始化时候需要将 lpfnWndProc 设置为自定义的窗口过程函数(此处是WndProc), 此后,所有基于该窗口类创建的窗口都会使用 lpfnWndProc(WndProc) 处理消息。可以理解为,当消息传到某个窗口时,这个窗口就会调用lpfnWndProc(WndProc)来处理消息。

int WINAPI WinMain(
	HINSTANCE hInstance,      // 当前程序实例句柄(由系统分配)
	HINSTANCE hPreHinstance,  // 前一个实例句柄(已废弃,总是NULL)
	LPSTR lpCmdLine,          // 命令行参数(ANSI字符串)
	int nCmdShow              // 窗口初始显示状态(最小化/正常等)
)
{
	// 1. 定义并初始化窗口类结构体
	WNDCLASSW myclass = { 0 };  // 清零初始化
	myclass.lpszClassName = L"n0ps1ed";  // 窗口类名称(唯一标识)
	myclass.lpfnWndProc = WndProc;       // 设置窗口消息处理函数

	return 0; 
}

第二步,注册窗口类,使用的是RegisterClassW函数:RegisterClassW(&myclass);

int WINAPI WinMain(
	HINSTANCE hInstance,      // 当前程序实例句柄(由系统分配)
	HINSTANCE hPreHinstance,  // 前一个实例句柄(已废弃,总是NULL)
	LPSTR lpCmdLine,          // 命令行参数(ANSI字符串)
	int nCmdShow              // 窗口初始显示状态(最小化/正常等)
)
{
	// 1. 定义并初始化窗口类结构体
	WNDCLASSW myclass = { 0 };  // 清零初始化
	myclass.lpszClassName = L"n0ps1ed";  // 窗口类名称(唯一标识)
	myclass.lpfnWndProc = WndProc;       // 设置窗口消息处理函数

	// 2. 注册窗口类到操作系统
	RegisterClassW(&myclass);  // 注册后才能创建该类的窗口

	return 0; 
}

这里可能会有疑惑,为什么不能直接创建窗口实例,而要先注册?这个注册窗口的意义是啥?

答案:当主线程从消息列队获取消息后,会根据句柄(Hwnd)得到一个窗口指针,这个指针指向窗口树的一个节点(系统会把所有的窗口用一个树形链表表示),这个节点含有该窗口的信息(如何窗口的类名称),同时系统会维护一张hash表,作用也就是为每个窗口类找对应的消息处理函数指针,其实你所做的注册窗口类的工作,就是向这张表写数据,然后系统就会为该窗口执行这个消息处理函数。

然后是第三步,创建窗口实例:

// 3. 创建窗口实例
	HWND hwindow = CreateWindowW(
		myclass.lpszClassName,  // 使用已注册的窗口类名
		L"n0ps1ed",            // 窗口标题文本
		WS_OVERLAPPEDWINDOW,   // 标准窗口样式(带标题栏、边框等)
		CW_USEDEFAULT,         // 初始x位置(使用默认值)
		0,                    // 初始y位置
		CW_USEDEFAULT,        // 窗口宽度(默认)
		0,                    // 窗口高度
		NULL,                 // 父窗口句柄(无)
		NULL,                 // 菜单句柄(无)
		hInstance,            // 程序实例句柄
		0                     // 创建参数(无)
	);

第四步,显示窗口:

// 4. 显示窗口
ShowWindow(hwindow, SW_SHOWNORMAL);  // 以普通状态显示窗口

第五步,消息循环处理:

// 5. 消息循环(程序核心)
MSG msg = { 0 };
while (GetMessageW(&msg, NULL, 0, 0))  // 获取消息,NULL改成hwindow也可以
{
    DispatchMessageW(&msg);  // 将消息分发到窗口过程WndProc
}

其中,GetMessageW原型如下:

BOOL GetMessageW(
  LPMSG lpMsg,          // 存储消息的缓冲区
  HWND  hWnd,           // 目标窗口句柄(NULL表示接收所有窗口消息)
  UINT  wMsgFilterMin,  // 最小消息号(过滤范围起点)
  UINT  wMsgFilterMax   // 最大消息号(过滤范围终点)
);

而DispatchMessageW是负责消息分发的,我们点击鼠标键盘对窗口产生的消息不是直接传给窗口,而是需要先传给系统,系统再分发给对应窗口句柄。所以才需要GetMessageW先获取消息然后再DispatchMessageW发消息。(也可以直接发给回调函数 lpfnWndProc

当调用 DispatchMessageW(&msg) 时,系统首先从 MSG 结构体中提取关键信息(目标窗口句柄、消息类型(如WM_PAINT)、附加信息(如按键码)、附加信息(如鼠标坐标)),然后通过 hwnd 找到对应的窗口类(WNDCLASS),接着获取注册时指定的 lpfnWndProc(即WndProc 函数)并执行。

写到这一步,我们已经创建了一个窗口实例并显示出来,然后使用GetMessageW来获取发往hwindow句柄(即所创建的窗口实例)的信息,然后使用DispatchMessageW函数把消息分发给句柄,调用WndProc 函数执行,所以接下来我们完善一下WndProc 函数:

// 窗口过程函数 - 处理所有发送到窗口的消息
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)  // 根据消息类型进行分支处理
	{
	case WM_CREATE:  // 窗口创建完成时收到的第一个消息
		MessageBoxW(hwnd, L"窗口创建了", L"提示", MB_OK);  // 显示创建提示
		break;

	case WM_CLOSE:   // 当用户点击关闭按钮时触发
		MessageBoxW(hwnd, L"窗口关闭了", L"提示", MB_OK);  // 显示关闭提示
		DestroyWindow(hwnd); // 销毁窗口(会触发WM_DESTROY消息)
		break;

	case WM_MOUSEMOVE:   // 鼠标移动时触发
		MessageBoxW(hwnd, L"鼠标移动", L"提示", MB_OK);  // 显示关闭提示
		break;

	default:  // 其他未处理的消息交给默认处理函数
		return DefWindowProc(hwnd, message, wParam, lParam);
	}
	return 0;  // 已处理的消息返回0
}

此时就已经完成了一个简单的窗口程序,实现的功能是打开窗口时候会弹窗提示“窗口创建了”,关闭窗口时会提示“窗口关闭了”,鼠标在窗口上移动时弹窗提示“鼠标移动”效果图如下:

完整代码:

#include <Windows.h> 


// 窗口过程函数 - 处理所有发送到窗口的消息
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)  // 根据消息类型进行分支处理
	{
	case WM_CREATE:  // 窗口创建完成时收到的第一个消息
		MessageBoxW(hwnd, L"窗口创建了", L"提示", MB_OK);  // 显示创建提示
		break;

	case WM_CLOSE:   // 当用户点击关闭按钮时触发
		MessageBoxW(hwnd, L"窗口关闭了", L"提示", MB_OK);  // 显示关闭提示
		DestroyWindow(hwnd); // 销毁窗口(会触发WM_DESTROY消息)
		break;

	case WM_MOUSEMOVE:   // 鼠标移动时触发
		MessageBoxW(hwnd, L"鼠标移动", L"提示", MB_OK);  // 显示关闭提示
		break;

	default:  // 其他未处理的消息交给默认处理函数
		return DefWindowProc(hwnd, message, wParam, lParam);
	}
	return 0;  // 已处理的消息返回0
}

// Windows应用程序入口点(不同于控制台程序的main)
int WINAPI WinMain(
	HINSTANCE hInstance,      // 当前程序实例句柄(由系统分配)
	HINSTANCE hPreHinstance,  // 前一个实例句柄(已废弃,总是NULL)
	LPSTR lpCmdLine,          // 命令行参数(ANSI字符串)
	int nCmdShow              // 窗口初始显示状态(最小化/正常等)
)
{
	// 1. 定义并初始化窗口类结构体
	WNDCLASSW myclass = { 0 };  // 清零初始化
	myclass.lpszClassName = L"n0ps1ed";  // 窗口类名称(唯一标识)
	myclass.lpfnWndProc = WndProc;       // 设置窗口消息处理函数


	// 2. 注册窗口类到操作系统
	RegisterClassW(&myclass);  // 注册后才能创建该类的窗口

	// 3. 创建窗口实例
	HWND hwindow = CreateWindowW(
		myclass.lpszClassName,  // 使用已注册的窗口类名
		L"n0ps1ed",            // 窗口标题文本
		WS_OVERLAPPEDWINDOW,   // 标准窗口样式(带标题栏、边框等)
		CW_USEDEFAULT,         // 初始x位置(使用默认值)
		0,                    // 初始y位置
		CW_USEDEFAULT,        // 窗口宽度(默认)
		0,                    // 窗口高度
		NULL,                 // 父窗口句柄(无)
		NULL,                 // 菜单句柄(无)
		hInstance,            // 程序实例句柄
		0                     // 创建参数(无)
	);

	// 4. 显示窗口
	ShowWindow(hwindow, SW_SHOWNORMAL);  // 以普通状态显示窗口


	// 5. 消息循环(程序核心)
	MSG msg = { 0 };
	while (GetMessageW(&msg, hwindow, 0, 0))  // 获取消息
	{
		DispatchMessageW(&msg);  // 将消息分发到窗口过程WndProc
	}

	return 0;  // 程序结束(当GetMessage收到WM_QUIT时退出循环)
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇