Windows GDI 对象泄漏排查实战
Windows GDI 对象泄漏排查实战
前言:一次“画布闪烁”引发的 GDI 泄漏排查
几年前维护一个老 MFC 项目时,曾遇到界面频繁“一闪一闪”、卡死的问题。最初怀疑是绘图逻辑,排查多次无果。直到在任务管理器添加了“GDI 对象”列,才发现进程的 GDI 对象数量不断飙升,最终定位到 GDI 对象泄漏(画布绘图相关的 GDI 资源未正确释放)。
现在使用 GDIView 等工具,GDI 泄漏的排查效率已提升数倍。本文结合实战经验,系统介绍 GDI 对象类型、典型泄漏场景、GDIView 工具高效用法及进阶分析技巧。
一、GDI 对象基础与常见泄漏场景
常见 GDI 对象及其生命周期
类型 | 创建API | 销毁API | 说明 |
---|---|---|---|
HDC | GetDC, CreateDC, CreateCompatibleDC | ReleaseDC, DeleteDC | 设备上下文,画布本体 |
HBITMAP | CreateBitmap, CreateDIBitmap, CreateCompatibleBitmap, LoadBitmap | DeleteObject | 位图对象(像素存储) |
HPEN | CreatePen, CreatePenIndirect | DeleteObject | 画笔,用于线条边框绘制 |
HBRUSH | CreateSolidBrush, CreateHatchBrush, CreateBrushIndirect | DeleteObject | 画刷,用于填充区域色彩 |
HFONT | CreateFont, CreateFontIndirect | DeleteObject | 字体对象 |
HRGN | CreateRectRgn, CreateRoundRectRgn, CreateEllipticRgn, CreatePolygonRgn | DeleteObject | 区域对象,裁剪或绘制区域 |
HPALETTE | CreatePalette | DeleteObject | 调色板(低色深场景) |
HMETAFILE | CreateMetaFile, CopyMetaFile | DeleteMetaFile | 元文件(矢量图记录) |
HENHMETAFILE | CreateEnhMetaFile, CopyEnhMetaFile | DeleteEnhMetaFile | 增强型元文件 |
HICON | CreateIcon, LoadIcon | DestroyIcon | 图标对象 |
HCURSOR | CreateCursor, LoadCursor | DestroyCursor | 鼠标光标对象 |
HBITMAP (DIB) | CreateDIBSection | DeleteObject | 设备无关位图 |
注意: 所有 GDI 对象均需显式销毁,未释放会造成资源泄漏。绝大部分 GDI 对象的销毁函数为 DeleteObject,但 HDC、HICON、HCURSOR、HMETAFILE、HENHMETAFILE 等有专用销毁函数,使用时要注意区分。
典型画布输出泄漏
最易泄漏场景:
- 在 OnDraw/OnPaint/定时器等高频重绘函数中频繁创建画笔、画刷、字体等对象但未销毁。
- 使用 new CPen/new CBrush 后未调用 delete 或 DeleteObject。
- SelectObject 后未恢复原 GDI 对象。
错误示例:
void CMyView::OnDraw(CDC* pDC)
{CPen* pPen = new CPen(PS_SOLID, 2, RGB(255,0,0));pDC->SelectObject(pPen);// ... 绘制 ...// 漏掉 delete pPen,造成泄漏!
}
正确示例:
void CMyView::OnDraw(CDC* pDC)
{CPen pen(PS_SOLID, 2, RGB(255,0,0));CPen* pOldPen = pDC->SelectObject(&pen);// ... 绘制 ...pDC->SelectObject(pOldPen); // 恢复原画笔// pen 析构自动释放,无泄漏
}
二、GDIView 实战——高效定位 GDI 泄漏
1. GDIView 简介与下载
- GDIView 是一款专门用于监控 GDI 对象分布与趋势的绿色工具。
- 下载地址:https://www.nirsoft.net/utils/gdi_handles.html
2. 汉化
GDIView 还提供其他语言版本。为了更改 GDIView,下载相应语言的 zip 文件,解压 ‘gdiview_lng.ini’, 并将其放在您安装了 GDIView 实用程序的同一文件夹中。
3. 启用自动刷新与增量显示
- 自动刷新:
在菜单栏选择“刷新”→“自动刷新时更新句柄列表”→选择“每1秒”。这样可以让 GDI 对象数量实时自动更新,方便观察变化趋势。 - 只显示变化量:
GDIView 会在各 GDI 对象类型后显示与上一次刷新相比的增减值。例如,某一秒 Pen 对象数从100变成110,Diff 会显示 +10,便于直观看到对象激增或回落的时刻。
4. 实时监控和定位对象类型
- 在进程列表中选择你的目标进程。
- 操作程序界面(如反复点击、刷新、拖动窗口等),同时观察画笔、画刷等列的数量和 Diff 增量,判断是否存在持续增加的趋势。
- 一旦发现某类型对象不断上涨,说明可能有泄漏。
5. 启用扩展信息窗口
- 选中目标进程后,点击菜单“查看”→“显示句柄附加信息”。
- 下方窗口会显示当前所有 GDI 对象句柄、类型、检测编号、首次分配时间等详细信息。
6. 字段解释与实战用法
- Detect Counter(检测计数):
表示此 GDI 对象自被发现以来,每次刷新计数都会 +1。计数越高,说明对象存在的时间越长。某些对象长期不释放、计数飙高,即为泄漏高风险。 - Detected On(检测到时间):
记录该对象首次被检测到的具体时间,可结合你实际操作对照分配时机。比如你刚点击某按钮,某些对象的 Detected On 时间就是当前时刻,说明与此操作相关。
7. 实践建议
- 利用自动刷新和增量,结合界面操作,快速发现 GDI 对象异常激增的功能区域。
- 配合扩展信息窗口的 Detect Counter 和 Detected On,重点关注长期未释放的对象,从而回溯到相关操作和源码,逐步锁定泄漏点。
三、GDI 管理与开发实践建议
- 任何 new/CreateXxx 生成的 GDI 对象,都必须有对应的 DeleteObject/delete。
- 每次 SelectObject 后保存旧对象并在绘制后恢复。
- 用 C++ 栈对象(如 MFC 的 CPen/CBrush)代替裸 new。
- 多线程、定时器、插件等高频绘制场合尤须重视。
- 发现 GDI 对象异常增长,优先排查高频重绘函数。
GDI 对象与设备上下文(DC)释放铁律
在 Windows GDI 编程中,资源管理有如下通用铁律,务必遵守:
对象来源 | 释放方法 | 说明 |
---|---|---|
CreateXxx 产生的 GDI 对象(如 Pen、Brush、Font、Bitmap、Region 等) | DeleteObject | 绝大多数 GDI 对象均用DeleteObject销毁 |
CreateDC、CreateCompatibleDC 产生的 HDC | DeleteDC | 自己创建的内存/兼容 DC,必须 DeleteDC |
GetDC、GetWindowDC 等获得的 HDC | ReleaseDC | 获取系统 DC 后必须 ReleaseDC 归还系统 |
切记:不能混用 DeleteDC 和 ReleaseDC,也不能用 DeleteObject 释放 DC!
正确示例代码
// 1. Create出来的GDI对象用DeleteObject
HPEN hPen = CreatePen(...);
DeleteObject(hPen);// 2. Create出来的DC用DeleteDC
HDC hMemDC = CreateCompatibleDC(hWndDC);
DeleteDC(hMemDC);// 3. GetDC得出的DC用ReleaseDC
HDC hWndDC = GetDC(hWnd);
// ... 绘图 ...
ReleaseDC(hWnd, hWndDC);
错误示例(不要这样做)
// 不要用ReleaseDC释放Create出来的DC
HDC hMemDC = CreateCompatibleDC(hWndDC);
ReleaseDC(hWnd, hMemDC); // 错误!// 不要用DeleteDC释放GetDC获得的DC
HDC hWndDC = GetDC(hWnd);
DeleteDC(hWndDC); // 错误!
遵守以上资源释放规则,是避免GDI泄漏和系统稳定的基础!
四、进阶:更强大分析工具推荐
- VMMap:内存分布分析,统计 GDI/USER 对象。
- Process Explorer:实时查看进程 GDI/USER 对象、资源泄漏趋势。
- WinDbg: 配合符号表和 !gdi 命令,可查看 GDI 对象分配堆栈。
五、结语与建议
- GDI 对象泄漏极易导致界面异常、崩溃等顽疾,桌面开发者务必警惕。
- 善用 GDIView 等工具,能高效定位泄漏类型和增减趋势。
- 平时多做专项检查,保持良好资源管理习惯。
- 复杂场景可用 VMMap、Process Explorer、WinDbg 做深入诊断。
- Visual Studio 2022 的内存快照/诊断工具可以精准地定位到 C++ GDI 对象泄漏的具体位置和类型。
- GDIView 等工具则更适合系统级体检、无源码排查或 Release 环境监控补充使用。
如需更多画布绘制与 GDI 资源管理代码示例,或有 GDI 相关疑难,欢迎留言交流!
实用工具一览:
- GDIView 官网
- VMMap 官网
- Process Explorer 官网
- WinDbg 官方文档