Windows编程深度解析:从WinMain到GDI绘图的完整学习指南

本文系统梳理了Windows桌面程序开发的核心知识点,涵盖入口函数、消息机制、GDI绘图、资源管理等关键技术,适合Win32和MFC开发者深入学习。


目录

  1. Windows程序入口函数详解
  2. MessageBox消息框完全指南
  3. Windows消息机制深度解析
  4. GDI绘图核心知识
  5. 资源管理与实战技巧
  6. 常见问题与解决方案

一、Windows程序入口函数详解

1.1 核心概念:WinMain vs wWinMain

Windows桌面程序有两个版本的入口函数,分别对应不同的字符编码:

函数名 字符编码 命令行参数类型 适用场景
WinMain ANSI(单字节) LPSTR 仅处理英文等单字节字符的老旧程序
wWinMain Unicode(宽字符) LPTSTR 支持全球多语言,现代开发推荐

关键区别

  • wWinMainLPTSTR 参数在 Unicode 环境下解析为 LPWSTR(16位宽字符指针)
  • 现代开发中,Visual Studio 默认开启 Unicode 字符集,wWinMain 是更常用的入口

1.2 函数参数详解

int APIENTRY wWinMain(
    HINSTANCE hInstance,      // 当前程序实例句柄
    HINSTANCE hPrevInstance,  // 历史兼容参数,32/64位系统中永远为NULL
    LPWSTR lpCmdLine,         // 命令行参数(不含程序路径)
    int nCmdShow              // 窗口初始显示方式
);

参数说明

参数 类型 作用 开发中的实际意义
hInstance HINSTANCE 程序实例句柄 程序运行时的唯一标识,用于加载资源(图标、对话框等)
hPrevInstance HINSTANCE 先前实例句柄 32/64位系统中永远为NULL,可忽略
lpCmdLine LPWSTR 命令行参数 仅包含程序名后的参数,不含程序路径
nCmdShow int 显示方式 控制窗口初始状态(最大化、最小化、正常显示)

1.3 调用约定:APIENTRY、WINAPI、CALLBACK

这三个宏最终都等价于 __stdcall 调用约定:

宏名 语义 典型使用场景
CALLBACK 回调函数 窗口过程函数 WndProc
WINAPI Windows API 通用 Windows API 函数
APIENTRY 程序入口 WinMain 入口函数

调用约定规则

  • 参数从右到左依次压入栈中
  • 由被调用函数负责清理栈
  • 保证程序与 Windows 系统之间的调用兼容性

1.4 实际开发建议

  1. 推荐使用 wWinMain:现代 Windows 开发默认使用 Unicode 字符集
  2. 获取完整命令行lpCmdLine 不包含程序路径,需要完整命令行时使用 GetCommandLine()
  3. 忽略 hPrevInstance:在 32/64 位系统中无实际作用

二、MessageBox消息框完全指南

2.1 核心作用

MessageBox 是 Windows API 中最常用的弹窗函数,用于显示模态对话框,通常用于展示提示、确认、错误等信息。

2.2 函数原型与参数

int MessageBox(
    HWND hWnd,          // 父窗口句柄(NULL表示独立窗口)
    LPCTSTR lpText,     // 消息内容
    LPCTSTR lpCaption,  // 标题文字
    UINT uType          // 样式控制(按钮+图标组合)
);

2.3 按钮样式参数

参数 按钮组合 返回值宏
MB_OK 仅「确定」按钮 IDOK
MB_YESNO 「是」+「否」按钮 IDYES / IDNO
MB_OKCANCEL 「确定」+「取消」按钮 IDOK / IDCANCEL
MB_ABORTRETRYIGNORE 「放弃」+「重试」+「跳过」 IDABORT / IDRETRY / IDIGNORE
MB_YESNOCANCEL 「是」+「否」+「取消」 IDYES / IDNO / IDCANCEL
MB_RETRYCANCEL 「重试」+「取消」 IDRETRY / IDCANCEL

2.4 图标样式参数

参数 图标样式 等效别名
MB_ICONEXCLAMATION 黄色警告感叹号 MB_ICONWARNING
MB_ICONINFORMATION 蓝色信息"i"图标 MB_ICONASTERISK
MB_ICONQUESTION 蓝色疑问"?"图标
MB_ICONSTOP 红色错误"×"图标 MB_ICONERROR / MB_ICONHAND

2.5 常用组合示例

// 1. 确认删除操作
MessageBox(NULL, L"是否永久删除该文件?", L"确认删除", 
           MB_YESNO | MB_ICONQUESTION);

// 2. 操作错误提示
MessageBox(NULL, L"文件读取失败,请检查路径。", L"错误", 
           MB_OK | MB_ICONERROR);

// 3. 警告并重试
MessageBox(NULL, L"网络连接不稳定,是否重试?", L"警告", 
           MB_RETRYCANCEL | MB_ICONWARNING);

2.6 返回值处理

int result = MessageBox(NULL, L"是否保存当前修改?", L"确认", 
                       MB_YESNO | MB_ICONQUESTION);
if (result == IDYES) {
    // 执行保存操作
} else if (result == IDNO) {
    // 不保存,直接退出
}

2.7 ANSI vs Unicode 版本

Windows 实际提供了两个独立的函数:

函数名 字符编码 字符串参数类型
MessageBoxA ANSI LPCSTR(8位字符指针)
MessageBoxW Unicode LPCWSTR(16位字符指针)

宏映射逻辑

#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif

开发建议

  • 现代开发直接使用 MessageBoxW,字符串加 L 前缀
  • 需要兼容时使用 _T() 宏:MessageBox(NULL, _T("消息"), _T("标题"), MB_OK)

2.8 常见错误:逻辑或 vs 按位或

错误示例

// 错误:使用逻辑或 ||
MessageBox(NULL, L"内容", L"标题", MB_YESNO || MB_ICONHAND);
// 结果:只显示"确定"按钮,无图标

正确示例

// 正确:使用按位或 |
MessageBox(NULL, L"内容", L"标题", MB_YESNO | MB_ICONHAND);
// 结果:显示"是/否"按钮 + 错误图标

原理说明

  • ||(逻辑或):返回布尔值 true/false(1/0)
  • |(按位或):合并二进制位,保留所有标志位

三、Windows消息机制深度解析

3.1 消息的本质

Windows消息是系统向应用程序发送的事件通知数据包,以 MSG 结构体形式传递:

typedef struct tagMSG {
    HWND   hwnd;      // 接收消息的窗口句柄
    UINT   message;   // 消息标识符(如WM_LBUTTONDOWN)
    WPARAM wParam;    // 32位附加参数
    LPARAM lParam;    // 32位附加参数
    DWORD  time;      // 消息发送时间
    POINT  pt;        // 鼠标位置
} MSG;

3.2 消息系统三要素

3.2.1 消息队列(Message Queue)
  • 作用:系统为每个GUI线程维护的FIFO缓冲区,暂存待处理的MSG结构体
  • 分类
    • 系统消息队列(全局)
    • 线程消息队列(进程内)
  • 关键特性
    • SendMessage 发送的消息直接调用窗口过程,不经过队列
    • PostMessage 发送的消息才会入队,属于异步通信
3.2.2 消息循环(Message Loop)

应用程序的"心脏",负责从队列中取出消息并分发:

MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);    // 翻译虚拟键消息
    DispatchMessage(&msg);     // 分发消息到窗口过程
}
return (int)msg.wParam;

关键特性

  • GetMessage 是阻塞函数,无消息时线程挂起
  • 收到 WM_QUIT 消息时返回0,循环终止
  • PeekMessage 是非阻塞的,适用于需要同时处理后台任务的场景
3.2.3 窗口过程(Window Procedure)

回调函数,负责处理特定窗口的消息:

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_LBUTTONDOWN:
            // 处理鼠标左键按下
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);  // 发送WM_QUIT,终止消息循环
            return 0;
        default:
            // 未处理的消息交给系统默认处理
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

3.3 常见消息类型

消息类型 触发场景 wParam/lParam 含义
WM_CREATE 窗口创建时 lParam指向CREATESTRUCT
WM_DESTROY 窗口销毁时 无特殊含义
WM_PAINT 窗口需要重绘 无特殊含义
WM_SIZE 窗口大小改变 lParam低16位=宽度,高16位=高度
WM_LBUTTONDOWN 鼠标左键按下 lParam低16位=X坐标,高16位=Y坐标
WM_KEYDOWN 键盘按键按下 wParam=虚拟键码
WM_CHAR 字符输入 wParam=字符的Unicode编码
WM_COMMAND 菜单/控件命令 LOWORD(wParam)=命令ID

3.4 消息驱动编程完整流程

用户/系统产生事件 
    ↓
系统构建MSG结构体 
    ↓
{消息类型判断}
    ├─ PostMessage/异步 → 线程消息队列
    └─ SendMessage/同步 → 直接调用目标WndProc
    ↓
消息循环:GetMessage取出MSG 
    ↓
TranslateMessage翻译消息 
    ↓
DispatchMessage分发MSG 
    ↓
窗口过程WndProc处理 
    ↓
{是否处理?}
    ├─ 是 → 返回处理结果
    └─ 否 → 调用DefWindowProc默认处理

3.5 安全攻防视角:消息钩子(Hook)

3.5.1 钩子分类
分类维度 类型 核心特征 安全意义
作用范围 局部钩子 仅拦截当前进程的MSG 合法应用(自身窗口消息监控)
全局钩子 拦截所有进程的MSG 恶意利用(键盘记录)、EPP监控
实现层级 普通钩子(WH_MOUSE) 需将钩子DLL注入目标进程 易被进程防护拦截
低级钩子(WH_MOUSE_LL) 无需DLL注入,运行在设置进程 恶意程序首选、EPP重点监控
3.5.2 全局钩子WH_MOUSE_LL实战
#include <Windows.h>
#include <iostream>

// 钩子过程:拦截鼠标MSG
LRESULT CALLBACK MouseLLProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode >= 0) {
        MSLLHOOKSTRUCT* pMouseHook = (MSLLHOOKSTRUCT*)lParam;
        std::cout << "鼠标消息类型:" << wParam 
                  << " 坐标:(" << pMouseHook->pt.x 
                  << "," << pMouseHook->pt.y << ")" << std::endl;
    }
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

int main() {
    // 设置全局低级鼠标钩子
    HHOOK hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseLLProc, 
                                        GetModuleHandle(NULL), 0);
    if (hMouseHook == NULL) {
        std::cerr << "钩子设置失败,错误码:" << GetLastError() << std::endl;
        return 1;
    }

    // 消息循环:低级钩子依赖设置进程的消息循环
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    UnhookWindowsHookEx(hMouseHook);
    return 0;
}
3.5.3 EPP产品的钩子应用

360等EPP产品通过钩子拦截恶意消息注入:

步骤 钩子类型 操作 检测规则
1 WH_GETMESSAGE(全局) 拦截MSG,筛选WM_COPYDATA 检查源进程可信度
2 WH_CALLWNDPROC(全局) 解析lParam为COPYDATASTRUCT 检测lpData中的恶意数据
3 内核态钩子 联动检查进程句柄权限 检测源进程是否有合法权限
4 行为分析 统计WM_COPYDATA发送频率 高频发送判定为异常IPC

四、GDI绘图核心知识

4.1 GDI基础概念

GDI(Graphics Device Interface):Windows绘图的核心接口,提供统一的图形绘制API。

核心前提:所有绘图操作必须先获取设备内容句柄(HDC)

4.2 绘图核心资源

资源类型 作用 创建函数
画笔(Pen) 绘制图形轮廓/线条 CreatePen
画刷(Brush) 填充封闭区域内部 CreateSolidBrushCreateHatchBrush
位图(Bitmap) 像素的矩形数组 LoadBitmap

4.3 HDC的获取与释放

4.3.1 WM_PAINT中获取(最常用)
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// 执行绘图操作
EndPaint(hWnd, &ps);  // 必须成对调用
4.3.2 其他方式获取
// 获取窗口客户区HDC
HDC hdc = GetDC(hWnd);
// 执行绘图操作
ReleaseDC(hWnd, hdc);  // 必须释放

// 创建兼容DC(用于双缓冲绘图)
HDC hdcMem = CreateCompatibleDC(hdc);
// 执行绘图操作
DeleteDC(hdcMem);  // 必须删除

4.4 基本绘图API

4.4.1 MoveToEx:移动绘图起点
BOOL MoveToEx(HDC hdc, int X, int Y, LPPOINT lpPoint);

功能:将当前绘图位置移动到指定坐标,不绘制任何图形

参数说明

  • hdc:设备上下文句柄
  • X, Y:目标位置坐标(客户区左上角为原点)
  • lpPoint:保存上一个绘图位置(可选,传NULL表示不保存)

使用示例

MoveToEx(hdc, 50, 50, NULL);  // 移动到起点(50,50)
LineTo(hdc, 200, 100);        // 绘制到(200,100)
4.4.2 LineTo:绘制直线
BOOL LineTo(HDC hdc, int X, int Y);

功能:从当前绘图位置到指定坐标绘制直线。

关键注意

  • 终点坐标本身不绘制,仅绘制到该点之前的像素
  • 绘制完成后,当前绘图位置自动更新为终点坐标
4.4.3 Rectangle:绘制矩形
BOOL Rectangle(HDC hdc, int left, int top, int right, int bottom);

功能:绘制矩形轮廓(当前画笔),可选填充内部(当前画刷)。

坐标说明

  • left, top:左上角坐标
  • right, bottom:右下角坐标
4.4.4 Ellipse:绘制椭圆/圆形
BOOL Ellipse(HDC hdc, int left, int top, int right, int bottom);

功能:绘制椭圆轮廓(当前画笔),可选填充内部(当前画刷)。

绘制规则

  • 椭圆由限定矩形决定,椭圆中心=限定矩形中心
  • 绘制圆形:将限定矩形设为正方形(right-left = bottom-top)

4.5 颜色设置

4.5.1 RGB宏
COLORREF RGB(BYTE bRed, BYTE bGreen, BYTE bBlue);

常用颜色

  • 红色:RGB(255, 0, 0)
  • 绿色:RGB(0, 255, 0)
  • 蓝色:RGB(0, 0, 255)
  • 白色:RGB(255, 255, 255)
  • 黑色:RGB(0, 0, 0)
4.5.2 设置画刷颜色

方法1:修改默认画刷颜色

COLORREF oldBrushColor = SetDCBrushColor(hdc, RGB(255, 255, 255));
// 绘图操作
SetDCBrushColor(hdc, oldBrushColor);  // 恢复原始颜色

方法2:创建自定义画刷(推荐)

HBRUSH hWhiteBrush = CreateSolidBrush(RGB(255, 255, 255));
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hWhiteBrush);
// 绘图操作
SelectObject(hdc, hOldBrush);  // 恢复原始画刷
DeleteObject(hWhiteBrush);     // 释放自定义画刷

4.6 SelectObject vs SetDCBrushColor

对比维度 SelectObject SetDCBrushColor
核心操作 替换HDC中的GDI对象 修改默认画刷的颜色属性
操作对象 GDI对象句柄(HBRUSH) 颜色值(COLORREF)
是否创建新对象 是(需先创建)
配套释放函数 需调用DeleteObject
资源隔离性 强(不污染系统默认状态) 弱(需严格恢复)
适用场景 复杂绘图、多次资源切换 简单绘图、单次颜色修改

4.7 窗口重绘机制

4.7.1 WM_PAINT消息
  • 触发时机:窗口被覆盖后恢复、窗口大小改变、手动触发
  • 处理要求:窗口消息处理程序需随时响应WM_PAINT,必要时重新绘制整个显示区域
4.7.2 InvalidateRect函数
BOOL InvalidateRect(HWND hWnd, const RECT* lpRect, BOOL bErase);

功能:将指定区域标记为无效区域,系统自动发送WM_PAINT消息。

参数说明

  • hWnd:窗口句柄
  • lpRect:无效矩形区域(NULL表示整个客户区)
  • bErase:TRUE擦除背景,FALSE保留原有内容

使用示例

// 手动触发重绘
InvalidateRect(hWnd, NULL, TRUE);  // 标记整个客户区为无效

4.8 窗口尺寸自适应绘图

// WM_SIZE消息处理
case WM_SIZE:
{
    int cxClient = LOWORD(lParam);  // 客户区宽度
    int cyClient = HIWORD(lParam);  // 客户区高度
    // 保存宽高值,供WM_PAINT使用
    return 0;
}

// WM_PAINT中使用
case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    
    // 绘制居中十字线
    MoveToEx(hdc, 0, cyClient/2, NULL);
    LineTo(hdc, cxClient, cyClient/2);
    MoveToEx(hdc, cxClient/2, 0, NULL);
    LineTo(hdc, cxClient/2, cyClient);
    
    EndPaint(hWnd, &ps);
    return 0;
}

4.9 绘图实战示例

case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    
    // 1. 绘制红色矩形
    HBRUSH hRedBrush = CreateSolidBrush(RGB(255, 0, 0));
    HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hRedBrush);
    Rectangle(hdc, 20, 20, 180, 120);
    SelectObject(hdc, hOldBrush);
    DeleteObject(hRedBrush);
    
    // 2. 绘制蓝色轮廓、白色填充的椭圆
    HPEN hBluePen = CreatePen(PS_SOLID, 1, RGB(0, 0, 255));
    HBRUSH hWhiteBrush = CreateSolidBrush(RGB(255, 255, 255));
    HPEN hOldPen = (HPEN)SelectObject(hdc, hBluePen);
    hOldBrush = (HBRUSH)SelectObject(hdc, hWhiteBrush);
    Ellipse(hdc, 200, 20, 380, 120);
    SelectObject(hdc, hOldPen);
    SelectObject(hdc, hOldBrush);
    DeleteObject(hBluePen);
    DeleteObject(hWhiteBrush);
    
    EndPaint(hWnd, &ps);
    return 0;
}

五、资源管理与实战技巧

5.1 Win32资源管理基础

核心概念:对话框、菜单、光标等均属于系统资源,需通过专用方式创建、加载、管理。

工程创建要点

  • 使用Visual Studio创建Win32控制台应用程序
  • 必须选择空项目,避免自动生成冗余代码

5.2 菜单资源管理

5.2.1 创建菜单资源
  1. 右键项目 → 选择「添加资源」
  2. 选择 Menu → 点击「新建」
  3. 可视化设计菜单结构
  4. 为每个菜单项设置唯一ID和显示属性
5.2.2 加载菜单资源
// 加载菜单资源
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENUSYS));

// 创建窗口时关联菜单
HWND hWnd = CreateWindowExW(
    0,
    L"MyWindowClass",
    L"我的窗口",
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT,
    800, 600,
    NULL,
    hMenu,  // 关联菜单
    hInstance,
    NULL
);

关键函数

HMENU LoadMenu(HINSTANCE hInstance, LPCTSTR lpMenuName);

参数说明

  • hInstance:程序实例句柄(WinMain的入参)
  • lpMenuName:菜单资源ID,必须通过MAKEINTRESOURCE宏转换
5.2.3 菜单消息处理
case WM_COMMAND:
{
    switch(LOWORD(wParam)) {
        case ID_FILE_MSG:
            MessageBox(hWnd, L"欢迎使用Windows高级编程教材", 
                       L"提示", MB_OK|MB_ICONINFORMATION);
            break;
        case ID_FILE_EXIT:
            DestroyWindow(hWnd);
            break;
        case ID_HELP_INFO:
            MessageBox(hWnd, L"Win32资源管理实操教程", 
                       L"帮助", MB_OK);
            break;
    }
    break;
}

处理流程

  1. 捕获WM_COMMAND消息
  2. 通过LOWORD(wParam)获取菜单项ID
  3. 通过switch-case匹配不同菜单项ID,执行对应逻辑

5.3 对话框资源管理

5.3.1 创建对话框资源
  1. 右键项目 → 「添加资源」→ 选择 Dialog 类型
  2. 通过VS可视化资源编辑器设计对话框布局
  3. 向对话框中添加控件(按钮、文本框、复选框等)
  4. 设置控件属性和唯一ID
5.3.2 资源管理规则
  • 所有资源在VS的资源视图中按类型分类管理
  • 资源创建后,可随时在资源视图中双击打开编辑
  • 修改后自动同步到.rc文件和resource.h

5.4 资源管理最佳实践

5.4.1 命名规范
  • 菜单ID前缀:ID_
  • 文件菜单前缀:ID_FILE_
  • 帮助菜单前缀:ID_HELP_
  • 对话框ID前缀:IDD_
5.4.2 调试技巧
  1. 检查resource.h:确认资源ID是否定义、是否重复
  2. 验证加载结果:判断LoadMenu等函数返回值是否为NULL
  3. 使用GetLastError():资源加载失败后获取错误码
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENUSYS));
if (hMenu == NULL) {
    DWORD dwError = GetLastError();
    WCHAR errMsg[256];
    wsprintfW(errMsg, L"菜单加载失败!错误码:%d", dwError);
    MessageBoxW(NULL, errMsg, L"错误", MB_OK | MB_ICONERROR);
    return 1;
}

5.5 资源文件关系

文件 作用 关系
.rc 实际存储资源的文件 包含所有资源的二进制数据
resource.h 资源ID的声明文件 定义所有资源的唯一ID
代码文件 引用资源ID 通过#include "resource.h"使用资源

关键点

  • .rc文件和resource.h必须同步,缺一不可
  • 代码通过引用resource.h的ID来操作.rc中的资源

六、常见问题与解决方案

6.1 窗口关闭但进程残留

6.1.1 根本原因

Win32程序的生命周期由消息循环控制,窗口关闭≠进程退出:

关闭窗口 → 触发WM_DESTROY → 需要PostQuitMessage(0) → 发送WM_QUIT → 消息循环退出 → 进程结束

核心问题WM_DESTROY不会自动终止消息循环,必须通过PostQuitMessage(0)主动发送WM_QUIT消息。

6.1.2 解决方案

方法1:基础保证(必须做)

case WM_DESTROY:
{
    PostQuitMessage(0);  // 核心:向消息队列发送WM_QUIT
    return 0;
}

方法2:兜底处理(推荐加)

case WM_CLOSE:
{
    // 添加关闭前确认
    if (IDYES == MessageBox(hWnd, L"是否确定关闭窗口?", 
                           L"提示", MB_YESNO | MB_ICONQUESTION))
    {
        DestroyWindow(hWnd);  // 确认关闭,销毁窗口
    }
    return 0;
}

方法3:调试排查

case WM_DESTROY:
{
    MessageBox(NULL, L"WM_DESTROY已执行,即将发送WM_QUIT", 
               L"调试", MB_OK);
    PostQuitMessage(0);
    return 0;
}
6.1.3 验证方法
  1. 运行程序,正常打开窗口
  2. 用两种方式分别关闭窗口:① 点击菜单;② 点击窗口右上角「×」
  3. 每次关闭后查看任务管理器:
    • 若两种方式都残留进程 → WM_DESTROY未执行PostQuitMessage(0)
    • 若仅部分方式残留 → 该方式未触发WM_DESTROY

6.2 HDC资源泄漏

6.2.1 问题表现
  • 程序运行一段时间后绘图变慢
  • 系统资源占用持续增加
  • 最终导致程序崩溃或系统卡顿
6.2.2 原因分析

HDC和GDI对象未正确释放:

// 错误示例
case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    
    HBRUSH hBrush = CreateSolidBrush(RGB(255, 0, 0));
    SelectObject(hdc, hBrush);
    Rectangle(hdc, 0, 0, 100, 100);
    // 忘记恢复原始画刷和释放自定义画刷
    // 忘记调用EndPaint
    
    return 0;
}
6.2.3 正确做法
case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    
    // 创建自定义画刷
    HBRUSH hBrush = CreateSolidBrush(RGB(255, 0, 0));
    // 选入并保存原始画刷
    HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
    
    // 绘图操作
    Rectangle(hdc, 0, 0, 100, 100);
    
    // 恢复原始画刷
    SelectObject(hdc, hOldBrush);
    // 释放自定义画刷
    DeleteObject(hBrush);
    // 释放HDC
    EndPaint(hWnd, &ps);
    
    return 0;
}

6.3 窗口重绘闪烁问题

6.3.1 问题表现
  • 窗口大小改变时闪烁严重
  • 绘图内容出现残影
6.3.2 解决方案:双缓冲绘图
case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    
    // 获取客户区尺寸
    RECT rcClient;
    GetClientRect(hWnd, &rcClient);
    int cxClient = rcClient.right - rcClient.left;
    int cyClient = rcClient.bottom - rcClient.top;
    
    // 创建兼容DC和位图
    HDC hdcMem = CreateCompatibleDC(hdc);
    HBITMAP hbmMem = CreateCompatibleBitmap(hdc, cxClient, cyClient);
    HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, hbmMem);
    
    // 在内存DC中绘图(不显示)
    Rectangle(hdcMem, 0, 0, cxClient, cyClient);
    TextOut(hdcMem, 10, 10, L"双缓冲绘图", 6);
    
    // 一次性复制到屏幕DC
    BitBlt(hdc, 0, 0, cxClient, cyClient, hdcMem, 0, 0, SRCCOPY);
    
    // 清理资源
    SelectObject(hdcMem, hbmOld);
    DeleteObject(hbmMem);
    DeleteDC(hdcMem);
    
    EndPaint(hWnd, &ps);
    return 0;
}

6.4 坐标系统混淆

6.4.1 问题表现
  • 绘图位置与预期不符
  • 图形显示在窗口外部
6.4.2 坐标系统说明

Windows绘图坐标系统

  • 原点:窗口客户区左上角为(0,0)
  • X轴:向右为正方向
  • Y轴:向下为正方向(与数学坐标系相反)

常见错误

// 错误:使用屏幕坐标
POINT pt;
GetCursorPos(&pt);  // 获取屏幕坐标
Rectangle(hdc, pt.x, pt.y, pt.x+100, pt.y+100);  // 错误!

// 正确:转换为客户区坐标
ScreenToClient(hWnd, &pt);  // 转换为客户区坐标
Rectangle(hdc, pt.x, pt.y, pt.x+100, pt.y+100);  // 正确

6.5 消息处理遗漏

6.5.1 问题表现
  • 窗口无法移动
  • 窗口无法关闭
  • 窗口失去基础功能
6.5.2 原因分析

未处理的消息未交给系统默认处理:

// 错误示例
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_PAINT:
            // 处理绘图
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        // 缺少default分支!
    }
    return 0;
}
6.5.3 正确做法
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_PAINT:
            // 处理绘图
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            // 未处理的消息交给系统默认处理(必须有!)
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
}

七、总结与进阶建议

7.1 核心知识点回顾

知识领域 核心内容 难度系数
程序入口 WinMain/wWinMain、调用约定、参数含义 ⭐⭐
消息机制 消息队列、消息循环、窗口过程、消息类型 ⭐⭐⭐⭐
GDI绘图 HDC管理、基本图形API、颜色设置、重绘机制 ⭐⭐⭐⭐⭐
资源管理 菜单/对话框资源、LoadMenu、WM_COMMAND处理 ⭐⭐⭐
安全攻防 消息钩子、EPP防护、恶意消息检测 ⭐⭐⭐⭐⭐

7.2 学习路径建议

  1. 入门阶段(1-2周)

    • 掌握WinMain入口函数
    • 理解消息机制基本概念
    • 实现简单的窗口创建和消息处理
  2. 进阶阶段(2-4周)

    • 深入学习GDI绘图
    • 掌握资源管理(菜单、对话框)
    • 实现完整的绘图程序
  3. 高级阶段(1-2个月)

    • 学习消息钩子技术
    • 了解安全攻防知识
    • 研究EPP产品的防护机制

7.3 实战项目建议

  1. 基础项目:简单的绘图板

    • 功能:绘制基本图形(直线、矩形、椭圆)
    • 技术点:GDI绘图、鼠标消息处理、双缓冲
  2. 进阶项目:记事本程序

    • 功能:文本编辑、菜单操作、文件保存
    • 技术点:编辑控件、菜单资源、文件I/O
  3. 高级项目:系统监控工具

    • 功能:监控鼠标键盘、记录消息
    • 技术点:全局钩子、消息拦截、行为分析

7.4 参考资源

官方文档

推荐书籍

  • 《Windows程序设计》(Charles Petzold)
  • 《Windows核心编程》
  • 《Windows高级编程指南》

开发工具

  • Visual Studio 2019/2022
  • Spy++(消息监控工具)
  • Resource Hacker(资源编辑工具)

结语

Windows编程是一个庞大而复杂的体系,本文涵盖了从入门到进阶的核心知识点。掌握这些内容后,您将能够:

  1. 独立开发Windows桌面应用程序
  2. 理解Windows消息机制的底层原理
  3. 实现复杂的GDI绘图功能
  4. 管理和使用Windows资源
  5. 了解安全攻防的基本概念

持续学习建议

  • 多动手实践,编写完整的程序
  • 阅读优秀的开源代码
  • 关注Windows API的更新和变化
  • 深入研究系统底层机制

祝您在Windows编程的道路上越走越远!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐