这里先介绍几个概念
-
窗口
-
消息循环
-
线程
窗口
本文所指的是通过继承CWindowImpl并调用Create所创建的同类型窗口。 窗口视为一个编程构造,该构造可以:
- 占据屏幕的特定部分。
- 在给定时刻可能可见,也可能不可见。
- 知道如何绘制自身。
- 响应来自用户或操作系统的事件。
消息循环
它是一个无限循环,不断地从操作系统获取消息并将其分发到窗口过程(Window Procedure)来进行处理。
在 Windows 操作系统中,每个窗口都有一个窗口过程,也称为消息处理函数。窗口过程是一个回调函数,用于处理与特定窗口相关的消息,例如鼠标点击、键盘输入、窗口绘制等。
消息循环的主要目的是从系统消息队列中获取消息,然后将消息分发给相应的窗口过程进行处理。这样,当用户进行交互操作或发生系统事件时,消息循环能够及时响应并触发相应的处理逻辑。其形如
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); // 翻译消息(例如将键盘消息转换为字符消息)
DispatchMessage(&msg); // 分发消息到窗口过程进行处理
}
线程
线程我相信大家都不陌生,这里就简单概况。线程是计算机中执行程序的最小单位。在操作系统中,每个进程可以包含多个线程,每个线程都可以独立执行一段程序代码。
疑惑
大家肯定会问,为什么我要单独把上面几个概念拿出来,我们都知道,窗口是事件驱动,依赖于其创建线程的消息循环。下面我们就来讲讲他们之间的关系,也是这次文章的核心,关于Windows窗口的一些疑惑
- 没有消息循环创建窗口
- 消息循环和窗口不在同一个线程创建
- 窗口资源什么时候销毁比较合适
下面我们详细聊一聊
没有消息循环创建窗口
以下为示例程序:
#include <windows.h>
// 窗口过程
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CREATE:
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
// 定义窗口类
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"WindowClass";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
// 注册窗口类
if (!RegisterClassEx(&wc)) {
MessageBox(NULL, L"窗口类注册失败!", L"错误", MB_ICONERROR);
return 0;
}
// 创建窗口
HWND hwnd = CreateWindowEx(0, L"WindowClass", L"我的窗口",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
500, 400, NULL, NULL, hInstance, NULL);
if (!hwnd) {
int err = GetLastError();
MessageBox(NULL, L"窗口创建失败!", L"错误", MB_ICONERROR);
return 0;
}
// 显示窗口
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
PostMessage(hwnd, WM_CLOSE, 0, 0);
return 0;
}
我们将上诉代码运行后发现程序能处理WM_CREATE消息、创建的窗口一闪而过并且快速退出。还有就是WndProc并不能接收到程序退出前发送的WM_CLOSE消息。由此可以看出,此窗口非正常销毁和退出。如果WndProc还在执行某些操作,可能会有不确定的行为(WndProc不一定只是一个窗口处理函数,也有可能是一个窗口类等)
消息循环和窗口不在同一个线程创建
以下为示例程序
#include <windows.h>
#include <thread>
// 窗口过程
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CREATE:
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
void Create(HINSTANCE hInstance) {
// 子线程创建窗口
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"WindowClass";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
// 注册窗口类
if (!RegisterClassEx(&wc)) {
MessageBox(NULL, L"窗口类注册失败!", L"错误", MB_ICONERROR);
return;
}
// 创建窗口
HWND hwnd = CreateWindowEx(0, L"WindowClass", L"我的窗口",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
500, 400, NULL, NULL, hInstance, NULL);
if (!hwnd) {
int err = GetLastError();
MessageBox(NULL, L"窗口创建失败!", L"错误", MB_ICONERROR);
return;
}
// 显示窗口
ShowWindow(hwnd, TRUE);
UpdateWindow(hwnd);
PostMessage(hwnd, WM_CLOSE, 0, 0);
return;
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
std::thread thread(Create, hInstance);
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
thread.join();
return 0;
}
上诉程序通过在新的线程调用Create创建窗口,并在主线程开启消息循环,但我们运行后发现,WndProc依然只能收到WM_CREATE消息,不能处理WM_CLOSE消息,窗口一闪而过并销毁,程序不退出
窗口资源什么时候销毁比较合适
这里我们可以参考微软的官方文档。官方文档
总结
窗口和消息循环是一对孪生兄弟,缺一不可,并且他们只有在同一个线程创建才能更好的工作。
学无止境,知其然知其所以然。