使用GDIView排查GDI对象泄漏导致的程序UI界面绘制异常问题

目录

1、问题说明

2、初步分析

3、查看任务管理器,并使用GDIView工具分析

4、GDIView可能对Win10兼容性不好,显示的GDI对象个数不太准确

5、采用历史版本比对法,确定初次出现问题的时间点,并查看前一天的代码修改记录

6、将修改的代码与测试现象结合起来,最终定位问题

7、事后的思考

8、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html       最近在项目中遇到了一个GDI对象泄漏问题,排查该问题也用了不少时间,其问题场景和排查方法很有代表性,今天将当时排查的过程做个详细的记录和总结,以供大家借鉴或参考。

1、问题说明

        某天早上来公司上班,有同事反馈我们开发的PC客户端软件的UI界面显示出问题了,窗口中部分区域与按钮等显示不全,应该是部分界面绘制失败了,但软件并没有崩溃。同事说前一天晚上他们为了测试移动APP,使用我们的PC端软件和移动app进行了联动,进行了一夜的拷机测试,早上来就发现PC客户端出了上述问题。他们没有关闭软件,保留着现象及现场,喊我们过去看看是怎么回事。

2、初步分析

         于是我到同事那边去查看现象,根据以往经验,感觉很有可能是GDI句柄泄漏引起界面绘制异常,当程序的GDI对象接近1万个时,界面就会出现绘制失败,显示不全的问题。

在Windows系统中,每个进程的GDI对象总数是有上限的,上限就是10000个,当接近或达到这个上限时,界面就会绘制失败,甚至程序会出现闪退崩溃。

        类似的问题,前几天有同事反馈过,但其没保留现象,直接将程序重启了,所以当时没有进行分析,这次这个同事反馈的这个问题,从现象上看和之前同事反馈的问题是类似,正好借此机会详细排查一下。

3、查看任务管理器,并使用GDIView工具分析

        为了验证GDI对象泄漏的猜想,先是打开系统的任务管理器去查看软件进程的GDI对象总数。打开任务管理器,点击详细信息标签页,找到目标软件进程,看到进程的GDI总数确实有异常,已经达到了9999个,差1个就到1万的上限了,如下所示:

​       正常情况下,进程在某个时刻使用的GDI对象总数也就几百个左右,最多也就1000多个,此处居然达到了9999个,肯定是有GDI对象泄漏了。使用完的GDI对象,没有调用DeleteObject等接口将之释放掉,这样会导致程序中占用的GDI对象越来越多,如果有GDI对象泄漏的代码在频繁的执行,那么泄漏会很明显,到任务管理器中可以看到目标进程的GDI对象总数在持续不断的上升,可能很快就要达到10000个上限了。

        默认情况下,任务管理器的进程列表中不会显示进程的GDI总数,需要右键点击列表头,在弹出的右键菜单中点击“选择列”:

​然后在弹出的窗口中找到GDI对象选项:

​勾选上即可看到进程的GDI对象总数。

正常情况下,进程的GDI对象最多只会有上千个,如果有好几千,一般可能是有GDI对象泄漏。

4、GDIView可能对Win10兼容性不好,显示的GDI对象个数不太准确

        仅仅通过任务管理器中的GDI对象总数判断出有GDI对象是不够的,因为GDI对象有多种,比如常见的GDI对象有Pen(用来划线的画笔)、Brush(用来填充区域颜色的画刷)、Bitmap(用来绘制图片的位图)、Font(用来控制文字显示大小及字形的字体)、Region(区域)、DC(用来绘制窗口的设备上下文)等,如下:

       为了有针对性的排查,我们还需要知道具体是哪种类型的GDI对象有泄漏,这就需要使用GDI对象查看工具GDIView。于是在同事的电脑上到GDIView官网上下载了GDIView工具,因为当前Windows系统是64位的,所以要下载64位的GDIView,64位系统上不能运行32位的GDIView,直接运行会报错(按讲64位系统是支持32位程序的,可能是GDIview工具自己的限制),如下:

        启动64位GDIView工具后,看到All GDI数目为9999:

​查看其他具体类型的项,只有Bitmap位图对象数目比较多,但只有1000多个(1436个),其他类型的GDI对象都比较少,那这个9999总数主要是由哪个对象泄漏引发的呢?从最后的分析结果看,是Bitmap对象泄漏引起的,那Bitmap对象数应该有好几千个,为啥GDIView中只显示1000多个?应该是GDIView工具对Win10系统兼容性不好,显示的各个GDI对象的数目有问题,之前我们在Win7和XP系统上用GDIView工具排查过GDI泄漏问题,GDIView中显示的各个类型的GDI对象都是比较准的。

5、采用历史版本比对法,确定初次出现问题的时间点,并查看前一天的代码修改记录

        GDIView中看不到具体是哪个类型的GDI泄漏,这样我们就没法进行有针对性的排查。好在我们有个脚本控制的自动化代码编译系统,只要有修改代码,每天都会自动编译版本,生成程序的安装包,如下所示:

于是我们只能使用历史版本比对法,取几个时间点的版本(安装包),多次安装并执行程序,然后再采取二分法取版本,看看是从哪天开始有这个问题的,然后我们查看前一天提交的代码,可能就能找到排查问题的线索了。

历史版本比对法,比较适用相对独立的客户端程序,虽然是个比较笨重、原始的办法,但很多时候都比较有用,我们在项目中已多次使用。

        最终通过对比发现,从2022年12月15日开始编译的版本都有内存泄漏的问题,12月14日的版本是没问题的。于是在SVN上查看前一天(12月14日)的代码提交记录,看看可能是修改哪一处的代码引发的。但12月14日当天修改的代码是处理业务服务器重连问题,修改了相关的逻辑,但这些修改的代码都和GDI绘制没关系,为啥会触发GDI对象泄漏呢?很是奇怪!到此,排查问题的线索似乎又断了。

6、将修改的代码与测试现象结合起来,最终定位问题

        12月14日之前业务服务器的重连功能都是有问题的,12月14日修改代码后,重连功能就没问题了。这时,测试同事又提供了一个关键的线索,用当前最新的版本的客户端软件,登录公网上的通用平台是没有GDI对象泄漏的,但登录公司内部的内网测试平台就有GDI对象泄漏。这两个平台有啥差别,导致同一个版本的客户端软件登录后有不同的表现呢?

        于是结合修改的代码,12月14日修改的代码是关于某类业务服务器断链后的重连代码,12月14日之前的重连代码都是有问题的。难道是两个平台上某个业务服务器的连通状态是不一样的?于是用客户端分别登录这两个平台,查看日志,看看两个平台的所有业务服务器的连接状态。果然是有差异的,公网平台上的所有业务服务器都是能正常连接的,但内网测试平台上某个业务服务器一直是连不上的,一直在不断重连(业务服务器连不上时会自动去重连)。

        于是查看触发重连时的整个流程的所有代码,然后果然找到了问题,重连的流程中会去调用一个接口去自动生成一张图片,调用CreateCompatibleBitmap API函数去创建一个Bitmap位图对象,但这个Bitmap位图对象在使用完后,没有调用DeleteObject将Bitmap对象释放掉,所以导致了GDI对象泄漏。问题代码片如下:

HDC hdc = ::GetDC( NULL );
HDC memDC = ::CreateCompatibleDC( hdc );HBITMAP hBitmap, hOldBitmap; 
// 调用CreateCompatibleBitmap创建一个与设备描述表兼容的位图(问题就出在这个)
hBitmap = ::CreateCompatibleBitmap( hdc, nWidth, nHeight ); 
hOldBitmap = (HBITMAP)SelectObject( memDC, hBitmap ); ::SetBkColor( memDC, WHITE_BRUSH ); // 区域刷白// 设置字体
// 字体创建
LOGFONT lf;
u32 dwXDpi = GetDeviceCaps( hdc, LOGPIXELSX ); // 得到当前显示设备的水平单位英寸像素数;
if ( dwXDpi != 0)
{nPointSize = static_cast<u32>( nPointSize * 96.0 / dwXDpi );
}
memset( &lf, 0, sizeof(LOGFONT) );
lf.lfHeight = -MulDiv ( nPointSize, GetDeviceCaps ( hdc, LOGPIXELSY ), 72 );
lf.lfWidth = lf.lfHeight/2;
lf.lfOutPrecision = OUT_STRING_PRECIS;
lf.lfQuality = CLEARTYPE_QUALITY;
lf.lfWeight = FW_NORMAL;
_tcscpy( lf.lfFaceName, _T("微软雅黑") );
HFONT hFont = ::CreateFontIndirect( &lf );::SetBkMode(memDC, TRANSPARENT);
::SetTextColor( memDC, RGB( 213, 242, 253 ) );
HFONT hOldFont = (HFONT)::SelectObject( memDC, hFont );RECT rcDest;
rcDest.left = 0;
rcDest.top = 0;
rcDest.right = nWidth;
rcDest.bottom = nHeight;if ( emLogoPos == emTopLeft_Api || emLogoPos == emBottomLeft_Api )
{::DrawText( memDC, strName, strName.GetLength(), &rcDest, DT_LEFT | DT_SINGLELINE );
}
else
{::DrawText( memDC, strName, strName.GetLength(), &rcDest, DT_RIGHT | DT_SINGLELINE );
}//::BitBlt( hdc, 0,0,nWidth,nHeight, memDC,0,0, SRCCOPY );CUIString strFile = GetSelfFilePath()+ LOGO_BMP_FILE;
SaveBitmapToBmpFile( hBitmap, strFile, LOGO_DPI_32 );::SelectObject( memDC, hOldFont );
::SelectObject( memDC, hOldBitmap );if ( hFont != NULL )
{::DeleteObject( hFont );
}if ( NULL != memDC )
{::DeleteDC( memDC );
}
::ReleaseDC( NULL, hdc );

代码结尾处释放了Font字体对象和DC对象,但忘记释放Bitmap对象。在代码片的结尾处应该调用DeleteObject将之前创建的Bitmap对象释放掉,即:

if ( hBitmap != NULL )
{::DeleteObject( hBitmap );
}

修改后的代码块如下:

        因为测试平台上某个业务服务器始终有问题,客户端连接不上,一直在不断的重连,所以这段包含GDI对象泄漏的代码在持续不断的执行,这样在长时间的拷机运行之后,导致程序的GDI对象总数达到了9999个。至此终于找到产生GDI对象泄漏的源头,修改代码后编译版本再安装运行,就不再有内存泄漏了。

        这个地方也说明一个问题,GDIView在Win10系统中运行,显示的各类型的GDI对象的数目是不准确的。本问题中,是Bitmap对象有泄漏,Bitmap对象应该有好几千个才对,结果GDIView中显示的Bitmap对象只有1000多个,这个显示不准确的问题下次要注意了。其实,一开始看到Bitmap对象有1000多个,就应该觉察到Bitmap对象有问题了,一般情况下不可能有这么多的!

      此外,之前也写过一篇使用GDIView排查GDI对象泄漏的案例,感兴趣的话可以查看对应的文章:

使用GDIView工具排查GDI对象泄漏问题icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125399896

7、事后的思考

        这段生成图片的代码是十多年前写的,出自于一个刚毕业的应届生之手,应该是因为经验不足,写出的代码不规范,在使用完创建的GDI对象之后应及时地将对象释放掉。我们平时一再地强调,写代码一定要规范,要尽量考虑的全面一些,否则可能会埋下一些或大或小的隐患。

        此外,这段GDI对象泄漏的代码掩藏的比较深,在业务服务器都能正常连接的平台上(比如给客户使用的商用平台)不会触发GDI泄漏。公司内部的测试平台正好这段时间业务服务器有问题,触发了客户端软件的重连流程,才将这个内存泄漏的问题暴露出来。

在公司内部测试基本没问题的软件,拿到客户的机器上,拿到各式各样的运行环境中,可能会出现这样那样的问题,比如复杂组网环境中的网络连通问题、软件运行异常等。公司内部的测试及运行环境毕竟是有限的,很多潜在的问题可能很难暴露出来。

8、最后

       该问题实例中的问题可能并不是很难,但整个问题的排查方法和思路,以及不同场景下的不同表现现象的启示,都很有参考价值。所以本文详细记录了整个问题的排查过程,以供大家借鉴或参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/91165.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL架构 InnoDB存储引擎

1. 什么是Mysql&#xff1f; 我们在开发的时候&#xff0c;我们都需要对业务数据进行存储&#xff0c;这个时候&#xff0c;你们就会用到MySQL、Oracal等数据库。 MySQL它是一个关系型数据库&#xff0c;这种关系型数据库就有Oracal、 MySQL&#xff0c;以及最近很火的PgSQL等。…

Docker-Windows安装使用

1.下载docker https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors 2.配置虚拟化环境 通过控制面板“设置”启用 Hyper-V 角色 右键单击 Windows 按钮并选择“应用和功能”。选择相关设置下右侧的“程序和功能”。选择“打开或关闭 Windows 功能”。选择“Hyper-…

ElementUI -- Mock.js介绍和使用与首页导航栏左侧菜单搭建

1.1 mockjs介绍 Mock.js是一个用于生成随机数据和模拟接口请求的JavaScript库。它可以帮助开发人员在前端开发过程中模拟后端接口的返回数据&#xff0c;以便进行前端页面的开发和测试。 Mock.js有两个重要的特性风靡前端: 数据类型丰富 Mock.js提供了一套简单易用的API&#x…

双指针算法——移动零

双指针算法——移动零&#x1f60e; 前言&#x1f64c;题目详情&#xff1a;图解分析&#xff1a;代码分享&#xff1a;B站讲解视频链接&#xff1a; 总结撒花&#x1f49e; &#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭&#xff1a;全神贯注的上吧…

红队打靶:THE PLANETS: MERCURY打靶思路详解(vulnhub)

目录 写在开头 第一步&#xff1a;主机发现和端口扫描 第二步&#xff1a;Web渗透 第三步&#xff1a;获取初步立足点并搜集信息 第四步&#xff1a;软连接劫持sudo提权 总结与思考 写在开头 本篇博客在自己的理解之上根据大佬红队笔记的视频进行打靶&#xff0c;详述了…

基于SSM的教师办公管理的设计与实现(有报告)。Javaee项目。

演示视频&#xff1a; 基于SSM的教师办公管理的设计与实现&#xff08;有报告&#xff09;。Javaee项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring S…

你的游戏项目有这些问题吗?

在移动游戏对高品质画面的要求不断增加的背景下&#xff0c;我们一直专注于移动设备GPU性能的优化&#xff0c;以确保您的游戏体验得以最佳展现。然而&#xff0c;不同GPU芯片之间的性能差异以及由此可能引发的GPU瓶颈问题使得优化工作更加具有挑战性。 因此&#xff0c;在不久…

git报错:Failed to connect to 127.0.0.1 port 1080

Bug描述 由于在试了网上的这条命令 git config --global http.proxy socks5 127.0.0.1:1080 git config --global https.proxy socks5 127.0.0.1:1080git config --global http.proxy 127.0.0.1:1080 git config --global https.proxy 127.0.0.1:1080Bug描述&#xff1a;Faile…

Three.js加载360全景图片/视频

Three.js加载360全景图片/视频 效果 原理 将全景图片/视频作为texture引入到three.js场景中将贴图与球形网格模型融合&#xff0c;将球模型当做成环境容器使用处理视频时需要以dom为载体&#xff0c;加载与控制视频动作每次渲染时更新当前texture&#xff0c;以达到视频播放效…

基于arduino的土壤湿度检测

1.总体设计框图 本浇花系统总体上分为硬件和软件两大组成部分。硬件部分包括Arduino UNO开发板、温湿度传感器、通信模块、浇水执行系统和液晶显示等。软件部分包括Android客户端。系统结构如图1所示 本浇花系统总体上分为硬件和软件两大组成部分。硬件部分包括Arduino UN…

Node.js安装教程【附安装包资源】

文章目录 安装包下载安装流程配置环境变量检查Node.js是否安装成功指定全局模块和模块缓存的路径设置淘宝镜像全局安装cnpm&#xff08;这一步可以选择是否执行&#xff09; 安装包下载 安装包下载 安装流程 修改安装路径 配置环境变量 看看环境变量里面是否有Node.js的…

手把手带你体验一场属于Linux的学习之旅

手把手带你体验一场属于Linux的学习之旅 Linux是一个开源的操作系统&#xff0c;以性能高和稳定著称因为继承unix&#xff0c;在权限和网络上的设计与表现也非常优异。同时其系统开源/免费/优秀/稳定&#xff0c;自由高可定制&#xff0c;深受程序员们等代表的极客们的喜爱&…

Python入门教程48:Pycharm永久镜像源的pip配置方法

国内几个好用的Python镜像服务器地址&#xff1a; 清华大学镜像站&#xff1a;https://pypi.tuna.tsinghua.edu.cn/simple/阿里云镜像站&#xff1a;https://mirrors.aliyun.com/pypi/simple/中科大镜像站&#xff1a;https://pypi.mirrors.ustc.edu.cn/simple/中国科技大学镜…

Ros2 学习02- ubuntu22.04 安装ros2

设置镜像源 sudo vim /etc/apt/sources.list#阿里源 deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ jamm…

华为云智能化组装式交付方案 ——金融级PaaS业务洞察及Web3实践的卓越贡献

伴随信息技术与金融业务加速的融合&#xff0c;企业应用服务平台&#xff08;PaaS&#xff09;已从幕后走向台前&#xff0c;成为推动行业数字化转型的关键力量。此背景下&#xff0c;华为云PaaS智能化组装式交付方案闪耀全场&#xff0c;在近日结束的华为全联接大会 2023上倍受…

【Vue】数据监视输入绑定

hello&#xff0c;我是小索奇&#xff0c;精心制作的Vue系列持续发放&#xff0c;涵盖大量的经验和示例&#xff0c;如有需要&#xff0c;可以收藏哈 本章给大家讲解的是数据监视&#xff0c;前面的章节已经更新完毕&#xff0c;后面的章节持续输出&#xff0c;有任何问题都可以…

zemax埃尔弗目镜

可以认为是一种对称设计&#xff0c;在两个双胶合透镜之间增加一个双凹单透镜 将半视场增大到30&#xff0c;所有的轴外像差维持在可以接受的水平。 入瞳直径4mm波长0.51、0.56、0.61半视场30焦距27.9mm 镜头参数&#xff1a; 成像效果&#xff1a;

用ChatGPT编写一个词卡显示网页

一、问题缘起 之前&#xff0c;我就发觉很多老师喜欢通过播放单词音频&#xff0c;显示单词拼写&#xff0c;这种词卡的形式来帮助学生记忆单词。于是&#xff0c;我就用Python制作了一个记单词软件&#xff0c;可以实现对words.txt中的单词滚动显示&#xff0c;播放发音&…

正则表达式贪婪模式和非贪婪模式

一、贪婪模式 贪婪模式表示尽可能多的匹配字符串&#xff0c;正则表达式六个量词元字符?、、*、{n}、{n,m}、{n,}默认是贪婪模式 接下来引入一个场景来分析说明 获取html a标签href属性的值 <a href"https://www.baidu.com/" attr"abc"></a>…

【Linux】详解线程第三篇——线程同步和生产消费者模型

线程同步和生消模型 前言正式开始再次用黄牛抢票来讲解线程同步的思想通过条件变量来实现线程同步条件变量接口介绍初始化和销毁pthread_cond_waitsignal和broadcast 生产消费者模型三种关系用基本工程师思维再次理解基于生产消费者模型的阻塞队列版本一版本二多生多消 利用RAI…