当前位置: 首页 > news >正文

Windows GDI 对象泄漏排查实战

Windows GDI 对象泄漏排查实战


前言:一次“画布闪烁”引发的 GDI 泄漏排查

几年前维护一个老 MFC 项目时,曾遇到界面频繁“一闪一闪”、卡死的问题。最初怀疑是绘图逻辑,排查多次无果。直到在任务管理器添加了“GDI 对象”列,才发现进程的 GDI 对象数量不断飙升,最终定位到 GDI 对象泄漏(画布绘图相关的 GDI 资源未正确释放)。

现在使用 GDIView 等工具,GDI 泄漏的排查效率已提升数倍。本文结合实战经验,系统介绍 GDI 对象类型、典型泄漏场景、GDIView 工具高效用法及进阶分析技巧。


一、GDI 对象基础与常见泄漏场景

常见 GDI 对象及其生命周期

类型创建API销毁API说明
HDCGetDC, CreateDC, CreateCompatibleDCReleaseDC, DeleteDC设备上下文,画布本体
HBITMAPCreateBitmap, CreateDIBitmap, CreateCompatibleBitmap, LoadBitmapDeleteObject位图对象(像素存储)
HPENCreatePen, CreatePenIndirectDeleteObject画笔,用于线条边框绘制
HBRUSHCreateSolidBrush, CreateHatchBrush, CreateBrushIndirectDeleteObject画刷,用于填充区域色彩
HFONTCreateFont, CreateFontIndirectDeleteObject字体对象
HRGNCreateRectRgn, CreateRoundRectRgn, CreateEllipticRgn, CreatePolygonRgnDeleteObject区域对象,裁剪或绘制区域
HPALETTECreatePaletteDeleteObject调色板(低色深场景)
HMETAFILECreateMetaFile, CopyMetaFileDeleteMetaFile元文件(矢量图记录)
HENHMETAFILECreateEnhMetaFile, CopyEnhMetaFileDeleteEnhMetaFile增强型元文件
HICONCreateIcon, LoadIconDestroyIcon图标对象
HCURSORCreateCursor, LoadCursorDestroyCursor鼠标光标对象
HBITMAP (DIB)CreateDIBSectionDeleteObject设备无关位图

注意: 所有 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/gdiview.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 对象异常增长,优先排查高频重绘函数。

四、进阶:更强大分析工具推荐

  • 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 官方文档
http://www.xdnf.cn/news/868753.html

相关文章:

  • Bootstrap 5学习教程,从入门到精通,Bootstrap 5 容器(Container)语法知识点及案例代码详解(4)
  • RAG-Gym:一个用于优化带过程监督的代理型RAG的统一框架
  • macOS 连接 Docker 运行 postgres
  • HarmonyOS 实战:给笔记应用加防截图水印
  • 【Kdump专题】kexec加载捕获内核和 makedumpfile保存Vmcore
  • GPUCUDA 发展编年史:从 3D 渲染到 AI 大模型时代(上)
  • LeetCode刷题---贪心算法---944
  • 《PyTorch:开启深度学习新世界的魔法之门》
  • 什么是分布式锁?几种分布式锁分别是怎么实现的?
  • Vue3+Vite中lodash-es安装与使用指南
  • 定制化5G专网服务,助力企业数字化转型
  • 华为OD最新机试真题-流水线-OD统一考试(B卷)
  • 在Mathematica中实现Newton-Raphson迭代的收敛时间算法
  • 【LLM大模型技术专题】「入门到精通系列教程」LangChain4j与Spring Boot集成开发实战指南
  • 学习笔记085——Spring Data JPA笔记
  • DeepSeek-R1-0528:学术写作的新纪元
  • CppCon 2015 学习:A C++14 Approach to Dates and Times
  • Vue 3 弹出式计算器组件(源码 + 教程)
  • SOC-ESP32S3部分:30-I2S音频-麦克风扬声器驱动
  • Go语言学习-->go的跨平台编译
  • 基于C++实现(WinForm) LAN 的即时通信软件
  • 【笔记】PyCharm 使用问题反馈与官方进展速览
  • 开源模型应用落地-OpenAI Agents SDK-集成Qwen3-8B-function_tool(二)
  • IDEA中微服务指定端口启动
  • java31
  • Spring Boot 从Socket 到Netty网络编程(下):Netty基本开发与改进【心跳、粘包与拆包、闲置连接】
  • React组件基础
  • Python 2.7 退役始末:代码架构缺陷与社区演进路线图
  • Linux-linux和windows创建新进程的区别以及posix_spawn
  • 爬虫学习记录day1