1 Windows编程模型
- Windows编程模型是一种基于事件驱动的编程架构(程序的执行流程主要依赖于外部事件的发生),它主要用于构建桌面应用程序和其他类型的应用,这些应用运行在Microsoft Windows操作系统之上。
- Windows编程模型强调了窗口、消息处理和系统API的使用,通过这些机制,开发者能够构建出功能丰富、响应用户交互的桌面应用程序。
2 Windows消息循环机制
- Windows消息循环机制是Windows操作系统中GUI编程的核心部分,它是事件驱动编程模型的基础。
- 每个运行中的Windows应用程序都有与之关联的一个消息队列,当产生消息后(比如点击鼠标就会产生鼠标按下的消息),操作系统会将消息放入消息队列,应用程序会循环从消息队列中检索消息并处理。这就是Windows消息循环机制。
- 模型示意图
3 消息
- 消息是如何产生的?当用户与应用程序窗口进行交互或系统事件发生时,硬件中断或者软件事件触发器会生成相应的消息,并将其放入应用程序的消息队列,这一步是由操作系统完成的。
- Windows定义了成百上千个不同的消息类型,常见的有以下几种
- WM_CHAR: 从键盘输入字符
- WM_COMMAND: 用户选择菜单内的某项
- WM_CREATE: 生成窗口
- WM_DESTROY: 撤销窗口
- WM_LBUTTONDOWN: 按下鼠标左键
- WM_LBUTTONUP: 释放鼠标左键
- WM_MOUSEMOVE: 移动鼠标
- WM_PAINT: 窗口需要重新绘制
- WM_QUIT: 应用程序将结束
- WM_SIZE: 窗口尺寸被调整
4 相关API
4.1 入口函数
- 函数原型
-
int WinMain ( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
-
- 参数
- hInstance: 模块实例句柄
- hPrevInstance: Win16遗留,现在基本不使用
- lpCmdLine: 命令行参数
- nShowCmd: 主窗口初始化时的显示方式
4.2 注册窗口
- 函数原型
-
ATOM WINAPI RegisterClassEx(const WNDCLASSEXA *lpwcx);
-
- 参数
- lpwcx: 说明窗口属性的结构体
- cbSize: 结构体大小
- style: 窗口类样式
- lpfnWndProc: 指向窗口过程函数的指针。
- cbClsExtra: 类附加内存的字节数。
- cbWndExtra: 窗口附加内存的字节数。
- hInstance : 应用程序实例句柄。
- hIcon : 窗口左上角图标的句柄
- hCursor : 光标句柄
- hbrBackground : 背景画刷句柄。
- lpszMenuName : 菜单名
- lpszClassName : 窗口类名
- hIconSm : 小图标句柄
- lpwcx: 说明窗口属性的结构体
- 返回值
- 函数成功注册窗口类后,会返回一个 ATOM 类型的值,它是类名称在系统中的内部标识符。如果注册失败,则返回零。
4.3 创建窗口
- 函数原型
-
HWND WINAPI CreateWindowEx( _In_ DWORD dwExStyle, _In_opt_ LPCWSTR lpClassName, _In_opt_ LPCWSTR lpWindowName, _In_ DWORD dwStyle, _In_ int X, _In_ int Y, _In_ int nWidth, _In_ int nHeight, _In_opt_ HWND hWndParent, _In_opt_ HMENU hMenu, _In_opt_ HINSTANCE hInstance, _In_opt_ LPVOID lpParam );
-
- 参数
- dwExStyle: 指定窗口的扩展样式
- lpClassName: 窗口类名
- lpWindowName: 窗口标题
- dwStyle: 指定窗口的基本样式
- WS_BORDER: 创建一个单边框的窗口
- WS_CAPTION: 创建一个有标题框的窗口
- WS_CHILD: 创建一个子窗口
- WS_DISABLED: 创建一个初始状态为禁止的子窗口,该窗口不能接收来自用户的输入信息
- WS_DLGFRAME: 创建一个带对话框边框风格的窗口
- WS_HSCROLL: 创建一个有水平滚动条的窗口
- WS_VSCROLL: 创建一个有垂直滚动条的窗口
- WS_ICONIC: 创建一个初始状态为最小化状态的窗口
- WS_MAXIMIZE: 创建一个具有最大化按钮的窗口
- WS_OVERLAPPED: 创建一个层叠的窗口
- WS_POPUP: 创建一个弹出式窗口
- WS_SIZEBOX: 创建一个可调边框的窗口
- WS_SYSMENU: 创建一个在标题条上带有窗口菜单的窗口,必须同时设定WS_CAPTION风格
- WS_THICKFRAME: 创建一个具有可调边框的窗口
- WS_VISIBLE: 创建一个初始状态为可见的窗口
- WS_MINIMIZEBOX: 创建一个具有显示最小化按钮的窗口
- WS_MAXIMIZEBOX: 创建一个具有显示最大化按钮的窗口
- WS_OVERLAPPEDWINDOW: 创建一个具有WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_THICKFRAME、WS_MINIMIZEBOX、WS_MAXIMIZEBOX风格的层叠窗口
- WS_POPUPWINDOW: 创建一个具有WS_BORDER、WS_POPUP、WS_SYSMENU风格的窗口。必须同时设定WS_CAPTION风格
- X: 初始X坐标
- Y: 初始Y坐标
- nWidth: 窗口宽度
- nHeight: 窗口高度
- hWndParent: 父窗口句柄
- hMenu: 菜单句柄
- hInstance: 程序实例句柄
- lpParam: 用户数据
- 返回值: 窗口创建成功返回窗口句柄,创建失败返回NULL
4.4 显示窗口
- 函数原型
-
BOOL WINAPI ShowWindow(_In_ HWND hWnd, _In_ int nCmdShow);
-
- 参数
- hWnd: 窗口句柄
- nCmdShow: 系统传递给WinMain函数的参数
- 返回值
- 成功返回TRUE,失败返回FALSE
4.5 更新窗口
- 函数原型
-
BOOL WINAPI UpdateWindow(_In_ HWND hWnd);
-
- 参数
- hWnd: 窗口句柄
- 返回值
- 成功返回TRUE,失败返回FALSE
4.6 从消息队列中检索消息
- 函数原型
-
BOOL WINAPI GetMessage(_Out_ LPMSG lpMsg, _In_opt_ HWND hWnd, _In_ UINT wMsgFilterMin, _In_ UINT wMsgFilterMax);
-
- 参数
- lpMsg: 指向 MSG 结构体的指针, 用于接收从消息队列取出的消息
- hWnd: 窗口句柄,如果不为空,则只检索发给该窗口或其子窗口的消息;如果为 NULL,则检索所有线程的消息。
- wMsgFilterMin, wMsgFilterMax: 定义了要检索的消息范围,通常设置为 0 到 0xFFFF,表示检索所有消息类型
- 返回值
- 从消息队列中获取的消息如果不是WM_QUIT, 则返回非零值
4.7 翻译消息
- 函数原型
-
BOOL WINAPI TranslateMessage(_In_ CONST MSG *lpMsg);
-
- 参数
- lpMsg: 指向一个 MSG 结构的指针
- 说明
- 它的主要作用是对来自消息队列的键盘消息(通常是 WM_KEYDOWN 和 WM_SYSKEYDOWN 消息)进行翻译,将其转换成字符消息(如 WM_CHAR 和 WM_SYSCHAR),以便应用程序能够更容易地识别和处理用户的字符输入。
4.8 分发消息
- 函数原型
-
LRESULT WINAPI DispatchMessage(_In_ CONST MSG *lpMsg);
-
- 参数
- lpMsg: 指向一个 MSG 结构的指针
- 备注
- 主要功能是将从消息队列中取出的消息分发给相应的窗口过程进行处理。
4.9 窗口过程函数
- 函数原型
-
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
-
- 参数
- hWnd:窗口句柄,表示该消息是发送给哪个窗口的。
- uMsg:消息标识符,用于指示需要处理的具体消息类型。
- wParam, lParam:这两个参数的内容取决于消息类型,通常包含了消息的附加信息。
- 说明
- 窗口过程函数是每个窗口类注册时必须指定的一个回调函数,它负责处理发送给窗口的所有消息。
4.10 查找窗口
- 函数原型
-
HWND WINAPI FindWindow(LPCSTR lpClassName, LPCSTR lpWindowName);
-
- 参数
- lpClassName:指向一个以NULL结束的宽字符字符串,表示窗口类名。如果该参数为NULL,则函数仅根据窗口标题查找窗口。
- lpWindowName:指向一个以NULL结束的宽字符字符串,表示窗口标题。如果该参数为NULL,则函数仅根据窗口类名查找窗口。
- 返回值
- 如果函数成功找到匹配的窗口,则返回该窗口的句柄。
- 如果没有找到匹配的窗口,则返回 NULL 或 0。
4.11 发送消息-同步接口
- 函数原型
-
LRESULT WINAPI SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
-
- 参数
- hWnd:指向接收消息的窗口的句柄。
- Msg:一个无符号整数,代表要发送的消息标识符,它可以是预定义的标准Windows消息(如 WM_PAINT、WM_KEYDOWN 等),也可以是自定义的消息(从 WM_USER 开始的范围)。
- wParam 和 lParam:这两个参数根据具体的消息类型有不同的含义,通常用来携带额外的消息相关数据。
4.12 发送消息-异步接口
- 函数原型
-
BOOL WINAPI PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
-
- 参数
- hWnd:指向接收消息的窗口的句柄。
- Msg:一个无符号整数,代表要发送的消息标识符,它可以是预定义的标准Windows消息(如 WM_PAINT、WM_KEYDOWN 等),也可以是自定义的消息(从 WM_USER 开始的范围)。
- wParam 和 lParam:这两个参数根据具体的消息类型有不同的含义,通常用来携带额外的消息相关数据。
- 备注
- SendMessage 和 PostMessage 都是发送消息到消息队列中。但SendMessage必须等消息被处理后才会返回,而PostMessage执行后,不管消息是否被处理都会立即返回。
5 编程示例-处理系统消息
- 创建工程时需要创建一个Win32项目
- 源代码
-
#include <windows.h> // 定义窗口过程函数 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_DESTROY: //处理窗口销毁消息 PostQuitMessage(0); // 当窗口被销毁时发送WM_QUIT消息 break; case WM_LBUTTONDOWN: // 处理鼠标左键按下消息 MessageBox(NULL, L"Mouse Left Press", L"Info", MB_OK); break; case WM_KEYDOWN: // 处理键盘按下消息 { UINT keyCode = (UINT)wParam; if (keyCode == VK_RETURN) { // 回车键按下 MessageBox(NULL, L"Enter Press", L"Info", MB_OK); } } break; case WM_CHAR: // 处理键盘输入的字符 { UINT keyCode = (UINT)wParam; if (keyCode == 0x41) { // 大写字母A按下 MessageBox(NULL, L"A Press", L"Info", MB_OK); } } break; } // 其他消息转交默认窗口过程处理 return DefWindowProc(hwnd, msg, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSW wc; // 初始化窗口类结构体 wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = NULL; // 如果没有菜单,则设为NULL wc.lpszClassName = L"MyWndClass"; // 注册窗口类 if (!RegisterClass(&wc)) { MessageBox(NULL, L"Failed to register window class.", L"Error", MB_OK | MB_ICONERROR); return 0; } // 创建窗口 HWND hWnd = CreateWindowW(L"MyWndClass", L"Wnd", WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); if (!hWnd) { MessageBox(NULL, L"Failed to create window.", L"Error", MB_OK | MB_ICONERROR); return 0; } // 显示并更新窗口 ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // 进入消息循环 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { // 翻译消息 TranslateMessage(&msg); // 分发消息(由窗口过程函数处理) DispatchMessage(&msg); } return msg.wParam; }
- 效果演示
6 编程示例-发送和处理自定义消息
- Win32编程时不仅可以处理系统消息,还可以发送和处理自定义消息。
- 我们在进程A中创建一个窗口,并处理对应的自定义消息。在进程B中,查找进程A窗口,并向进程A发送自定义消息,实现两个窗口之间的通信。
- 进程A
-
#include <windows.h> // 定义自定义消息 #define WM_MYCUSTOMMESSAGE (WM_USER + 100) // 定义窗口过程函数 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_MYCUSTOMMESSAGE: // 处理自定义消息 MessageBox(NULL, L"WM_MYCUSTOMMESSAGE", L"Info", MB_OK); break; case WM_DESTROY: //处理窗口销毁消息 PostQuitMessage(0); // 当窗口被销毁时发送WM_QUIT消息 break; case WM_LBUTTONDOWN: // 处理鼠标左键按下消息 MessageBox(NULL, L"Mouse Left Press", L"Info", MB_OK); break; case WM_KEYDOWN: // 处理键盘按下消息 { UINT keyCode = (UINT)wParam; if (keyCode == VK_RETURN) { // 回车键按下 MessageBox(NULL, L"Enter Press", L"Info", MB_OK); } } break; case WM_CHAR: // 处理键盘输入的字符 { UINT keyCode = (UINT)wParam; if (keyCode == 0x41) { // 大写字母A按下 MessageBox(NULL, L"A Press", L"Info", MB_OK); } } break; } // 其他消息转交默认窗口过程处理 return DefWindowProc(hwnd, msg, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSW wc; // 初始化窗口类结构体 wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = NULL; // 如果没有菜单,则设为NULL wc.lpszClassName = L"MyWndClass"; // 注册窗口类 if (!RegisterClass(&wc)) { MessageBox(NULL, L"Failed to register window class.", L"Error", MB_OK | MB_ICONERROR); return 0; } // 创建窗口 HWND hWnd = CreateWindowW(L"MyWndClass", L"Wnd", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); if (!hWnd) { MessageBox(NULL, L"Failed to create window.", L"Error", MB_OK | MB_ICONERROR); return 0; } // 显示并更新窗口 ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // 进入消息循环 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { // 翻译消息 TranslateMessage(&msg); // 分发消息(由窗口过程函数处理) DispatchMessage(&msg); } return msg.wParam; }
- 进程B
-
#include <windows.h> #include <stdio.h> // 定义自定义消息,ID值必须和进程A中的一致 #define WM_MYCUSTOMMESSAGE (WM_USER + 100) int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // 找到进程A的窗口句柄 HWND hwndTarget = FindWindow(TEXT("MyWndClass"), TEXT("Wnd")); if (!hwndTarget) { MessageBox(NULL, L"Failed to find target window.", L"Error", MB_OK | MB_ICONERROR); return -1; } // 发送自定义同步消息 SendMessage(hwndTarget, WM_MYCUSTOMMESSAGE, 0, 0); return 0; }