书籍:《Visual C++ 2017从入门到精通》的2.3.5 消息和窗口
环境:visual studio 2022
内容:【例 2.14】一个基本的Win32应用程序
1. 新建一个工程
新建工程内容与之前写的步骤基本一致,除了一点,不要选择空项目。
2. 新建工程目录
3.解析主函数
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此处放置代码。
// 初始化全局字符串
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_TEST, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
/*关键流程总结
加载加速键表:绑定快捷键与命令(如 Alt + / → IDM_ABOUT)。
消息循环:
取出队列中的消息。
尝试通过加速键表转换快捷键为命令。
若未转换成功,将虚拟键转为字符并分发消息到窗口过程。*/
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TEST));
/***LoadAccelerators** :从应用程序资源中加载加速键表(.rc文件中定义的ACCELERATORS块)。
** hInstance** :应用程序实例句柄,标识资源所属模块。
** MAKEINTRESOURCE(IDC_TEST)** :将整数资源ID IDC_TEST转换为资源名称,指向加速键表定义。
返回值:加速键表的句柄(HACCEL),若加载失败返回 NULL(需检查错误)。*/
MSG msg; //MSG 结构体**:用于存储消息信息,包含窗口句柄、消息类型、参数等字段。
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0)) /*功能:从调用线程的消息队列中取出消息。
参数:
& msg:接收消息的指针。
nullptr:过滤所有窗口的消息(hWnd为 nullptr 表示当前线程所有窗口)。
0, 0:消息范围过滤(此处未限制,默认处理所有消息)。
返回值:
非零:成功获取消息(非 WM_QUIT)。
0:队列为空且无 WM_QUIT 消息(循环结束)。
* *-1 * *:发生错误(需调用 GetLastError())。*/
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
/*功能:将快捷键消息(如 WM_KEYDOWN)转换为对应的命令消息(如 WM_COMMAND)。
参数:
msg.hwnd:目标窗口句柄。
hAccelTable:加速键表句柄。
& msg:指向当前消息的指针(可能被修改)。
返回值:
非零:转换成功(消息已被处理,无需后续步骤)。
零:未匹配到快捷键,继续处理字符消息和分发。*/
{
TranslateMessage(&msg); /*功能:将虚拟键消息(如 WM_KEYDOWN)转换为字符消息(如 WM_CHAR)。
适用场景:当窗口需要响应文本输入时(如编辑框控件)。
注意:若消息已通过 TranslateAccelerator 处理,则无需调用此函数。*/
DispatchMessage(&msg); /*功能:将消息分发给对应的窗口过程函数(WindowProc)处理。
参数:指向 MSG 结构体的指针。
返回值:窗口过程的返回值(通常被忽略)。*/
}
}
return (int) msg.wParam;
}
3.1 未使用参数声明
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
UNREFERENCED_PARAMETER宏的作用是标记函数参数为“未使用”,以避免编译器因检测到参数未被引用而发出警告。这两句代码中的hPrevInstance和lpCmdLine是Windows程序入口函数wWinMain的参数,但在当前代码逻辑中并未实际使用它们,因此通过宏定义显式声明这些参数为未引用状态。
在C++17及更高版本中,可以使用[[maybe_unused]]属性替代该宏,实现相同效果。例如:
int APIENTRY wWinMain(HINSTANCE hInstance, [[maybe_unused]] HINSTANCE hPrevInstance, [[maybe_unused]] LPTSTR lpCmdLine, int nCmdShow)
这种写法更简洁且符合现代C++标准。
该宏常见于Windows API编程场景,尤其在驱动开发或框架封装代码中,用于忽略某些保留参数(如hPrevInstance在现代Windows版本中已无实际作用)。
3.2初始化字符串
// 初始化全局字符串
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_TEST, szWindowClass, MAX_LOADSTRING);
LoadStringW() 是 Windows API 中用于加载字符串资源的函数,主要功能是从指定模块的可执行文件中加载字符串资源到缓冲区或返回只读指针。以下是其功能及用法详解:
功能说明
- 加载字符串资源
从与指定模块(如应用程序或动态链接库)关联的可执行文件中加载字符串资源,支持 Unicode 编码(W 版本)。 - 缓冲区操作
- 若
cchBufferMax
(缓冲区最大字符数)非零,将字符串复制到缓冲区并添加终止空字符(\0
)。 - 若
cchBufferMax
为零,返回指向字符串资源的只读指针(需自行处理终止空字符)。
- 若
- 国际化支持
字符串资源可独立管理,便于程序的本地化和多语言适配。
参数说明
参数 | 类型 | 说明 |
---|---|---|
hInstance |
HINSTANCE |
模块实例句柄(若为应用程序自身,可用 NULL 调用 GetModuleHandle )。 |
uID |
UINT |
要加载的字符串资源标识符(如 IDS_STRING1 )。 |
lpBuffer |
LPWSTR |
接收字符串的缓冲区指针(若 cchBufferMax 非零)或存储指针的变量(若为零)。 |
cchBufferMax |
int |
缓冲区大小(字符数)。若字符串过长则截断,返回值包含实际字符数(不含终止符)。 |
返回值
- 成功:返回复制的字符数(
cchBufferMax
非零时)或资源中的字符数(cchBufferMax
为零时)。 - 失败:返回
0
,可通过GetLastError
获取扩展错误信息。
用法示例
Win32 API 直接调用
#include <windows.h>
int main() {
HINSTANCE hInstance = GetModuleHandle(NULL);
WCHAR buffer[256];
int len = LoadStringW(hInstance, IDS_MY_STRING, buffer, 256);
if (len > 0) {
// 使用 buffer 中的字符串
}
return 0;
}
MFC 框架封装(通过 CString
)
#include <afxstr.h> // 包含 CString 类
void LoadStringExample() {
CString str;
str.LoadStringW(IDS_MY_STRING); // 自动管理缓冲区
// 直接使用 str
}
(注:MFC 的 LoadStringW
是对 Win32 API 的封装,简化了资源管理。)
安全注意事项
- 缓冲区溢出风险
若cchBufferMax
计算错误(如使用sizeof(buffer)
而非sizeof(buffer)/sizeof(TCHAR)
),可能导致溢出。需严格按字符数计算大小。 - 终止空字符处理
当cchBufferMax
为零时,返回的指针指向资源本身,需自行检查字符串是否以空字符结尾。
平台与版本支持
- 最低支持:Windows 2000 Professional/Server。
- Windows CE:需使用特定版本(如 Windows CE 5.0),且需在资源编译时启用
-n
标志。
通过合理使用 LoadStringW(),可提升代码的可维护性和国际化能力,但需注意参数校验和缓冲区安全。
3.3 注册窗口类MyRegisterClass(hInstance);
MyRegisterClass(hInstance)的作用是注册窗口类,用于定义窗口的属性(如样式、消息处理函数、图标等),以便后续创建窗口时使用。
//
// 函数: MyRegisterClass()
//
// 目标: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex; //WNDCLASSEXW 结构体**:定义窗口类的属性(W 版本支持 Unicode)
wcex.cbSize = sizeof(WNDCLASSEX); //cbSize 成员**:必须设置为结构体的大小(sizeof(WNDCLASSEX)),以确保系统正确读取后续成员。
wcex.style = CS_HREDRAW | CS_VREDRAW; /*style 成员**:窗口类样式。
CS_HREDRAW:窗口水平缩放时自动重绘。
CS_VREDRAW:窗口垂直缩放时自动重绘。
作用:确保窗口尺寸变化时界面正常刷新。*/
wcex.lpfnWndProc = WndProc; /*lpfnWndProc 成员** :指向窗口过程函数(WndProc),负责处理窗口消息(如按键、鼠标事件)。
关键点:必须正确绑定窗口过程函数,否则窗口无法响应用户操作。*/
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0; /*** cbClsExtra** :类扩展内存(通常为 0)。
* *cbWndExtra * *:窗口扩展内存(通常为 0)。
用途:仅在需要额外存储数据时才非零。*/
wcex.hInstance = hInstance; //hInstance :关联窗口类与应用程序实例,用于加载资源(如图标、光标)。
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TEST)); /*hIcon:窗口图标,通过 LoadIcon 加载资源 IDI_TEST。
MAKEINTRESOURCE:将整数资源 ID 转换为资源名称。*/
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); /*hCursor 成员** :窗口光标,使用默认箭头光标(IDC_ARROW)。
nullptr 参数** :从系统模块加载资源(而非当前实例)。*/
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); /*hbrBackground 成员** :窗口背景刷子。
COLOR_WINDOW + 1 * *:获取系统默认窗口颜色对应的刷子(白色或浅灰色)。*/
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_TEST); /*lpszMenuName 成员** :菜单资源 ID(IDC_TEST),转换为 Unicode 字符串。
若不需要菜单:可设为 nullptr。*/
wcex.lpszClassName = szWindowClass; /*lpszClassName 成员** :窗口类名称(需全局定义 szWindowClass),必须唯一。
关键点:后续创建窗口时需使用此名称。*/
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); //hIconSm 成员**:小图标(任务栏或标题栏显示),资源 ID 为 IDI_SMALL。
return RegisterClassExW(&wcex); //ATOM: 返回值**:窗口类的唯一标识符(原子值),由系统分配。失败处理:若返回值为 0,需调用 GetLastError 获取错误码。
}
关键注意事项
资源依赖性:
确保 IDI_TEST(图标)、IDC_TEST(菜单)、IDI_SMALL(小图标)在资源文件(.rc)中存在。
若使用自定义资源,需检查资源 ID 是否冲突。
窗口类名称唯一性:szWindowClass 必须全局唯一,否则会覆盖已注册的窗口类。
窗口过程函数绑定:WndProc 需定义为与窗口类匹配的回调函数,参数类型为 HWND, UINT, WPARAM, LPARAM。
作用
该函数通过填充WNDCLASS或WNDCLASSEX结构体,并调用RegisterClass或RegisterClassEx完成窗口类的注册。注册后,系统会为该窗口类分配唯一标识(ATOM值),后续创建窗口时需通过类名或ATOM值引用该类。
是否必须调用
注册窗口类本身是必须的:在Windows编程中,创建窗口前必须先注册窗口类,否则无法生成有效窗口。
MyRegisterClass函数是否必须:取决于实现方式。该函数通常是开发者自定义的封装函数(如网页所示),用于简化代码结构。虽然理论上可以将注册代码直接写在WinMain中,但分离到独立函数(如MyRegisterClass)更符合代码规范和可读性要求。因此,MyRegisterClass函数本身并非强制调用,但注册窗口类的操作必须执行。
总结:MyRegisterClass的作用是注册窗口类,而注册窗口类是创建窗口的必要步骤。实际开发中建议通过此类封装函数实现,但核心逻辑(注册窗口类)不可省略。
3.4 应用程序初始化 InitInstance (hInstance, nCmdShow)
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目标: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中
/*功能:将传入的hInstance(应用程序实例句柄)保存到全局变量hInst中,用于后续资源加载和窗口关联。
关键点:HINSTANCE是Windows特有的句柄类型,标识应用程序的当前实例。*/
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
/*功能:创建主窗口窗口。
参数解析:
szWindowClass:已注册的窗口类名(需提前通过RegisterClass或RegisterClassEx注册)。
szTitle:窗口标题栏文本。
WS_OVERLAPPEDWINDOW:标准窗口样式,包含标题栏、边框等。
CW_USEDEFAULT:系统自动分配默认位置(坐标为(0, 0))。
hInstance:关联窗口类与当前实例句柄。*/
if (!hWnd) //错误处理:若窗口创建失败(如窗口类未注册),返回FALSE终止程序
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow); //根据nCmdShow参数(如SW_SHOW)显示窗口
UpdateWindow(hWnd); //立即发送WM_PAINT消息更新窗口内容
return TRUE; //返回值:成功返回TRUE,表示实例初始化完成.
}
InitInstance (hInstance, nCmdShow) 是 MFC(Microsoft Foundation Class)应用程序中的一个关键成员函数,主要用于初始化应用程序实例并执行核心启动任务。
此函数是Win32应用程序的核心初始化入口,负责窗口创建和显示。关键步骤包括实例句柄保存、窗口类关联、窗口创建及显示更新。
3.5 加载应用程序资源中定义的加速键表 LoadAccelerators()
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TEST));
/***LoadAccelerators** :从应用程序资源中加载加速键表(.rc文件中定义的ACCELERATORS块)。
** hInstance** :应用程序实例句柄,标识资源所属模块。
** MAKEINTRESOURCE(IDC_TEST)** :将整数资源ID IDC_TEST转换为资源名称,指向加速键表定义。
返回值:加速键表的句柄(HACCEL),若加载失败返回 NULL(需检查错误)。*/
LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TEST))的作用是加载程序中定义的加速键表(快捷键表),将组合键(如Ctrl+S)与特定命令(如保存文件)关联起来。其核心功能包括:
资源加载
通过hInstance指定模块实例句柄,MAKEINTRESOURCE(IDC_TEST)将资源ID转换为加速键表标识符,从程序资源中加载预定义的快捷键配置。
映射关系建立
加速键表通常以文本形式定义(如"N", IDM_FILE_NEW, VIRTKEY, CONTROL),表示按下Ctrl+N时触发“新建文件”命令。
非必须调用
该函数仅在程序中包含快捷键资源时需要调用。若无快捷键表(即资源文件中无ACCELERATORS定义),则无需使用此函数。
使用场景:
通常与TranslateAccelerator函数配合,在消息循环中处理快捷键事件。例如:
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TEST));
while (GetMessage(&msg, ...)) {
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
// 处理其他消息
}
}
此流程确保快捷键能被及时捕获并执行对应操作。
3.6 获取消息 GetMessage(&msg, nullptr, 0, 0)
while (GetMessage(&msg, nullptr, 0, 0)) {...}
功能:从调用线程的消息队列中取出消息。
参数:
& msg:接收消息的指针。
nullptr:过滤所有窗口的消息(hWnd为 nullptr 表示当前线程所有窗口)。
0, 0:消息范围过滤(此处未限制,默认处理所有消息)。
返回值:
非零:成功获取消息(非 WM_QUIT)。
0:队列为空且无 WM_QUIT 消息(循环结束)。
* *-1 * *:发生错误(需调用 GetLastError())。
3.7 TranslateAccelerator(msg.hwnd, hAccelTable, &msg)
功能:将快捷键消息(如 WM_KEYDOWN)转换为对应的命令消息(如 WM_COMMAND)。
参数:
msg.hwnd:目标窗口句柄。
hAccelTable:加速键表句柄。
& msg:指向当前消息的指针(可能被修改)。
返回值:
非零:转换成功(消息已被处理,无需后续步骤)。
零:未匹配到快捷键,继续处理字符消息和分发。
3.8 TranslateMessage(&msg);
功能:将虚拟键消息(如 WM_KEYDOWN)转换为字符消息(如 WM_CHAR)。
适用场景:当窗口需要响应文本输入时(如编辑框控件)。
注意:若消息已通过 TranslateAccelerator 处理,则无需调用此函数。
3.9 DispatchMessage(&msg);
功能:将消息分发给对应的窗口过程函数(WndProc)处理。
参数:指向 MSG 结构体的指针。
返回值:窗口过程的返回值(通常被忽略)。
3.10 WndProc(HWND, UINT, WPARAM, LPARAM)
说明:根据书中【例 2.14】一个基本的Win32应用程序,在WM_CREATE和WM_PAINT分支都加了一些内容。
//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目标: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
//** hWnd** :当前接收消息的窗口句柄,用于标识具体窗口。
//** message** :系统或用户发送的消息类型(如WM_CREATE、WM_COMMAND等)。
//** wParam和lParam** :携带消息的附加参数,具体含义由message决定。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
RECT rt;
switch (message)
{
case WM_CREATE:
/*功能:窗口创建时弹出消息框,提示“窗口即将出现”。
技术点:
MessageBoxW:宽字符版本的消息框函数,_T宏用于支持Unicode。
MB_OK:消息框按钮样式为“确定”。*/
MessageBoxW(hWnd, _T("窗口即将出现"), _T("你好"), MB_OK); //说明:这部分代码是我加的。
break;
case WM_COMMAND:
{
/*功能:处理菜单命令(如“关于”和“退出”)。
技术点:
LOWORD(wParam):提取wParam的低字节,获取菜单项ID(如IDM_ABOUT)。
DialogBox:弹出模态对话框,hInst为应用程序实例句柄,IDM_ABOUT对应资源中的对话框模板。
DestroyWindow:销毁窗口并发送WM_DESTROY消息。*/
int wmId = LOWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
/*功能:窗口重绘时在客户区中央显示文本“Hello, MFC”。
技术点:
BeginPaint / EndPaint:获取设备上下文(HDC)并管理重绘区域。
GetClientRect:获取客户区矩形尺寸。
SetTextColor / SetBkColor:设置文本颜色(黄色)和背景颜色(青色)。
DrawText:绘制文本,DT_CENTER等标志控制对齐方式。*/
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此处添加使用 hdc 的任何绘图代码...
//说明:这部分代码是我加的。
GetClientRect(hWnd, &rt);
SetTextColor(hdc, RGB(255, 255, 0));
SetBkColor(hdc, RGB(0, 128, 0));
DrawText(hdc, _T("Hello, MFC"), -1, &rt, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
/*功能:窗口关闭时发送退出消息,结束消息循环。
技术点:PostQuitMessage将WM_QUIT消息放入消息队列,通知应用程序退出。*/
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam); //默认消息处理:未处理的消息通过DefWindowProc调用系统默认处理逻辑,确保窗口行为正常。
}
return 0;
}
说明:以上大部分内容来自腾讯元宝。