0


常用工具 | 如何使用GDIView工具排查GDI对象泄漏问题

   GDIView工具可以查看进程的对GDI对象的占用情况,主要用于GDI对象泄漏问题的排查,今天我们结合具体的问题来介绍一下如何使用这个工具。

1、概述

   GDI对象是Windows系统中执行绘图操作中要到的,常见的GDI对象有Bitmap位图对象、Brush画刷对象、Pen笔对象、Font字体对象、Region区域对象、DC对象,通过操作这些对象就可以完成绘图操作。

   在Win32编程中,对于Bitmap位图、Brush画刷、Pen笔、Font字体、Region区域等,在使用之前必须要调用对应的Create接口去创建这些对象,在使用完成后则需要去调用DeleteObject去释放这些对象;对于GetDC,在dc使用完成后,需要调用ReleaseDC函数去释放。如果没有在GDI对象使用完成后,没有调用DeleteObject或者ReleaseDC接口,则会产生GDI对象的泄漏。就像内存泄漏一样,申请的资源,在使用完成后没有去释放。

    在程序运行的过程中,我们去实时地查看系统任务管理器中目标进程的GDI对象总数,去判断目标进程是否有内存泄漏。默认情况下,资源管理器中是不显示GDI对象列的,可以右键列表标题栏,在右键菜单中点击“选择列”,勾选“GDI对象”,即可查看到,如下所示:

   当程序中有GDI对象泄露且发生泄漏的代码在频繁地执行,这样随着程序长时间运行,进程的GDI对象会持续的增长,可以在任务管理器中实时地观测目标进程的GDI总数的变化情况。

如果进程中有GDI对象泄漏,当GDI对象总数接近或达到1万个时,就会导致GDI绘图函数调用出现异常,出现窗口绘制不出来等问题,紧接着程序就可能会出现崩溃。很多Windows老程序员可能都遇到过类似的问题。

   当程序中有严重的GDI对象时,如何去排查定位呢?仅仅查看Windows系统的任务管理器中进程占用的GDI对象总数是远远不够的,通过总数的实时变化,我们可以推断出进程中可能有内存泄漏,但因为GDI对象类型比较多,无法确定是哪种对象泄漏,没法做针对性的排查。

   这个时候就需要使用GDIView工具了,该工具可以查看到Bitmap位图、Brush画刷、Pen笔、Font字体、Region区域、DC等多种对象的个数,如下所示:

这样就能看出到底是哪种对象有明显的泄漏了,这样我们就能有针对性地排查代码了。

2、问题案例

   据测试同事反馈,我们的软件在加入会议后发起桌面共享,桌面共享持续10多分钟后,软件的UI窗口显示异常了(窗口绘制不出来了),窗口点击也没反应。但会议中的其他参会方还能听到当前出问题的软件终端说话的声音。估计应该是UI线程出问题了,底层的音视频编解码模块的线程还在正常运行。

   难道是UI线程堵塞卡死了?或者是当前进程中有GDI对象泄漏,进程的GDI对象总数接近或达到1万个,导致GDI绘图函数都调用失败了,导致UI界面都绘制不出来了?于是到任务管理器中查看进程的GDI对象个数,果然达到了1万个,如下所示:

那基本可以确定程序中发生了GDI对象泄漏。

3、使用GDIView进行分析

   该轮到GDI对象泄漏检测利器GDIView上场了。打开GDIView,查看到我们软件进程的DC句柄异常的高:

居然达到了9000多个,那基本可以确定是DC对象发生了泄漏

   估计是代码中调用GetDC,但没有调用ReleaseDC将DC对象释放掉,即代码中调用GetDC去获取窗口DC对象,但在使用完成后没有调用ReleaseDC将DC句柄释放掉引起的。

4、进一步分析,找到相关的代码段,分析源码

   首先应该不是UI层代码的问题,UI层最近没有添加绘制的代码。然后又询问了底层的终端组件的同事,他们最近也没有添加操作DC的代码,他们在代码中搜索了一下,确实没有调用GetDC接口的。

   底层的webrtc开源库最近有修改过几个问题,有发布过库过来,怀疑是webrtc库修改的代码导致的。但是维护webrtc的同事说他们新增的代码并没有操作GDI对象去绘图,应该不是他们的问题。

这个是大家的思维定势,出问题了,最开始都喜欢说这肯定不是我们模块的问题。

   webrtc库是20220331发布过来的,于是安装了该日期之前的软件版本,入会发送桌面共享是没有GDI对象泄露的。然后又安装了03月31日发布后的版本,结果是有GDI对象泄露的,**所以确定肯定是0331发布过来的webrtc库的问题了。**此处使用到了历史版本比对法。

   但webrtc开源维护组的同事坚持认为他们添加的代码没问题,肯定是上层的问题。于是到同事那边查看svn上的修改记录,查看到了如下的一段修改记录:(新增了一段代码)
#if defined (WEBRTC_WIN)
    //修正程序开启DWM导致的鼠标位置问题
    int desktop_horzers = GetDeviceCaps( GetDC(nullptr) DESKTOPHORZRES);
    int horzers = GetDeviceCaps(GetDC(nullptr),HORZRES);
    float scale_rate=(float)desktop_horzers/(float)horzers;
    relative_position.set( relative_ position.x()*scale_rate, 
        relative_ position.y()*scale_rate );
#endif

果然是调用系统API函数GetDC,但在使用完DC对象后没有调用ReleaseDC去释放。这就对了,问题就是出在地方了。

   让同事加上了对ReleaseDC的调用,如下所示:
#if defined (WEBRTC_WIN)
    //修正程序开启DWM导致的鼠标位置问题
    HDC hDC = ::GetDC(nullptr);
    int desktop_horzers = GetDeviceCaps( hDC, DESKTOPHORZRES);
    int horzers = GetDeviceCaps(hDC,HORZRES);
    float scale_rate=(float)desktop_horzers/(float)horzers;
    relative_position.set( relative_ position.x()*scale_rate, 
        relative_ position.y()*scale_rate );
    ::ReleaseDC(nullptr, hDC);
#endif

重新编译,将库重新发布过来就解决问题了。

5、最后

   这段代码应该是同事从网上拷贝过来的,因为他们对UI编程不熟悉,在调用完GetDC之后,没有调用ReleaseDC接口去释放,所以导致了DC句柄的泄露。当时添加这段代码是为了解决发送桌面共享时,鼠标光标在系统非100%显示比例下位置有偏移的问题。

   在实际项目中我们多次使用GDIView工具定位过GDI对象泄漏问题。

本文转载自: https://blog.csdn.net/chenlycly/article/details/125399896
版权归原作者 dvlinker 所有, 如有侵权,请联系我们删除。

“常用工具 | 如何使用GDIView工具排查GDI对象泄漏问题”的评论:

还没有评论