现在的位置: 首页 > 综合 > 正文

关于应用程序出现窗口不完整,GDI对象猛增,GDI资源泄漏的问题的探讨

2012年07月29日 ⁄ 综合 ⁄ 共 4558字 ⁄ 字号 评论关闭

http://hi.baidu.com/qi_xian/blog/item/08011716e096751e962b4345.html

本文转载于3SDN: http://www.3sdn.net

有时候,一个应用程序运行到一定的时间,会出现窗口不完整(花屏),出现“必需的资源无法得到”的报错,这是个令人烦恼的问题。此时,你如果打开资源管理器,在“查看”中“选择列”,添加“GDI对象”,可以很清晰得看到,随着程序的运行,GDI对象,快速地增加,当数量达到9999时(为什么是这个数,下文会提到)时,程序窗口界面就会出现不完整现象,此时,你若拖动程序里的滚动条之类的,将会出现严重的花屏,甚至还会弹出一个不完整的警告框,警告:“必需的资源无法得到”。

这是典型的GDI资源泄漏的问题。

之所以会出现这样的问题,主要是你在程序中创建了GDI对象,之后并没有释放或消毁等等。这种问题一般出现在OnPaint(),Draw()还有一些涉及到绘图的函数中。下面举例说明:

1、一个新的GDI对象选择到了DC,但使用完后没有恢复DC中的原始GDI对象。
CGDIObject *pOldDC=pDC->SelectObject(&yourobject);
//......  
pDC->SelectObject(pOldDC); //在绘图结束时,这句不能少

2、通过GetDC()等获得的上下文CDC,使用后,必须Realease。

3、创建 BITMAP 对象,最后要DeleteObject()。

4、这是我遇到的问题:在做文件搜索时,用到CListCtrl显示搜索结果,我想在文件名前显示文件图标,因此用函数:

::SHGetFileInfo (pathname, FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(SHFILEINFO),SHGFI_USEFILEATTRIBUTES | SHGFI_DISPLAYNAME | SHGFI_TYPENAME|SHGFI_ICON|SHGFI_SMALLICON))

得到文件的图标,但后来我并没有用到,因此就放那不管了,由此“铸成大错”,后果不堪设想,如上文所描述的那样。解决方法是去掉红色部分就可以了。这时,运行程序,GDI对象的数量一般稳定在32-35之间,不会出现一瞬间就飙升到9999的可怕景象了。

下面说说GDI对象问题。

为解决我遇到的问题,我上网寻觅许久,最后看到henan_lujun(网名,引自:http://www.programfan.com/club/showpost.asp?id=7995)的分析,明白些许,下面转载片段:

     GDI对象,实际上是Windows系统维护的一些数据结构。微软基于稳定性和健壮性考虑,将所有GDI对象的管理权都交给Windows系统的对象管理器管理,用户只能通过系统返回的“句柄”来操作这些对象。
     在Windows 2000中,句柄实际上是一个DWORD类型的值。该DWORD值是一个32比特位的数据,它又分为两个部分:Table Index及Uniqueness Identifier,他们各占16位,因此,在理论上来说,Windows中的每个进程,所能访问的GDI对象的最大值是64K。然而,在Windows 2000中,客户句柄的最大数目被硬设置为16384 (16K);
     然而,在Windows 2000中,既便客户句柄的最大数目被硬设置为16384,那么为什么在实际中GDI对象增加到9999后,程序界面就开始混乱了呢?原来,在Windows2000中,每进程的GDI对象的最大值又被默认为10000——据微软资料显示,之所以设置为10000,是为了阻止“Bad”程序分配过多的资源,因此,当GDI对象达到9999之后,程序就不能再创建新的GDI资源,这样,每次都使用新建资源来绘制界面的程序就产生混乱了。
      不过,在Windows 2000及以后的操作系统中,每个进程可以创建的GDI对象的最大值,是可以通过修改注册表来重新设置的,在Windows 2000中,该注册表项所为:HKEY_LOCAL_MACHINE/ SOFTWARE/Microsoft/Windows NT/CurrentVersion/Windows下的"GDIProcessHandleQuota"。设置完新的值后,重启计算机后,系统中每进程可以使用的GDI对象数就会变成你新设置的数。
然而,上述说法并不完全正确!
     据SYBASE的一份资料显示(http://www.sybase.com/detail?id=1019174),在Windwos2000中,只可以对”GDIProcessHandleQuota”值进行微调,如果设置的值超过15000,系统就变得不稳定。事实上,我在Windows 2000中进行了测试,当GetGuiResources函数的返回值为12288(12K)时,就已经不能创建新的GDI对象了,也就是说,在Windows 2000中当一个进程总的GDI对象数达到12288以后,就不能再创建新的GDI对象了。
     那么,在Windows 2000中,进程所能创建的GDI对象数,是每个进程独立的,还是要受限于Windows操作系统呢?为此,我写了一个check程序,该程序批量创建指定数目的BRUSH对象,并统计本进程的GDI对象数及系统中总的GDI对象数,其运行界面如下(这里图显示不出来,不过无关紧要)

     其中统计系统中总的GDI对象的代码如下:
int   GetGDINumInSystem(void)
{
     int nGDINums = 0;    /*所有进程的GDI对象之和*/
     int nProcess    = 0;    /*系统中的进程数*/

     DWORD aProID[1024];
     DWORD cbNeeded;
     ::EnumProcesses ( aProID, sizeof(aProID), &cbNeeded );

     /*系统中进程总数*/
     nProcess = cbNeeded / sizeof ( DWORD );

     /*统计每个进程的GDI对象数*/
     for ( INT i=0; i < nProcess; i ++ )
     {
         HANDLE hPro = ::OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,   FALSE,   aProID[i]   );

         nGDINums += ::GetGuiResources ( hPro, GR_GDIOBJECTS );

         CloseHandle ( hPro );
     }

     return nGDINums;
}

     使用此check程序,做如下试验:
     ①修改Windows 2000的注册表项” GDIProcessHandleQuota”值为12000;
     ②开启一个check进程,在其中创建11000个GDI对象!
     ③开启第二个check进程(第一个进程不关闭),在其中创建11000个GDI对象。
     试验结果表明,第一个进程的对象可以顺利创建,而第二个进程的对象则不能顺利创建,这就说明,在Windows 2000中,每个进程可以创建的GDI对象数,不仅在进程内部有一定限制,而且还受限于整个操作系统。经多次试验表明,当Windows 2000系统中总的GDI对象数达到15900以后的某个值后,进程就不能再创建GDI对象了,系统就变得不稳定了,至于该临界值到底是多少,各次试验结果并不一致,但是都在15900以后。

     上面的测试是在Windows 2000中进行的,那么,在Windows 2003中,情况又是什么样呢?立即动手,到2003系统中,修改注册表项”GDIProcessHandleQuota”为20000,然后运行测试程序并在其中创建20000个GDI对象,一切正常! 再改,30000,运行,仍然正常;……直到改到了70000的时候,系统运行才会出现界面绘制问题,在这样的事实下,不得不做假设:难道Windows 2003中取消了客户GDI句柄最多16K的限制,而将限制设到了64K?
     为了验证这个推测,我使用check程序直接创建64K的GDI资源,运行结果表明,当进程创建了一定的GDI对象之后,就不能创建新的GDI对象了,这表明上面的推测不完全正确,不过,基于在Windows 2000中的试验经验,很快就想到了在Windows 2003中系统可以创建的GDI对象应该还要受限于操作系统,也就是说:Windows 2003中放宽了每个进程可以创建的GDI对象数目,但是整个系统中GDI对象数不能超过某个值。为验证此结果,做如下试验:
    ①修改Windows 2003的注册表项” GDIProcessHandleQuota”值为50000;
    ②开启一个check进程,在其中创建40000个GDI对象!
    ③开启第二个check进程(第一个进程不关闭),在其中创建40000个GDI对象。
    试验结果基本上和Windows 2000中的结果类似,唯一不同的是,在第二个进程创建GDI对象的过程中,当系统中总GDI对象达到63700以后的某个值后才会创建失败。同样,该临界值也不固定,不过多次试验表明,当总的GDI对象数达到63700以后,系统就变得不稳定了。

结论
     经过上面的分析,我们可以知道,在Windows 2000/2003 操作系统中,每个进程可以创建的GDI对象,受限于三个方面因素:系统本身的两个方面的限制和Windows注册表中设置的最大值限制。
     首先,每个进程可以创建的GDI对象数,在理论上为64K,但是在Windows 2000中,系统将客户可以创建的GDI句柄数硬设置为不能超过16K,而事实上当GDI对象数达到12K之后,系统即不正常;在Windows 2003中,系统放宽了对GDI对象数的限制,使得每个进程可以使用的GDI对象数接近64K;
    
     其次,每个进程可以创建的GDI对象数,还受限于操作系统中GDI对象总数;在Windows 2000中,当系统中总的GDI对象达到15900以后的某个值后,进程就不能再创建新的GDI对象了;在20003中,这个值增加到63700以后的某个值。但是,到底是哪个值,则不固定。
    
     最后,进程可创建的最大GDI对象数目还受限于注册表中HKEY_LOCAL_MACHINE/ SOFTWARE/Microsoft/Windows NT/CurrentVersion/Windows下的"GDIProcessHandleQuota" 项所设置的值,该值设置了Windows中每个进程可以创建的最大GDI对象数,在Windows 2000及2003系统中,其默认均为10000,这也就说明了当程序创建的GDI对象数达到9999之后界面为什么会混乱。

相信,你看了之后,知道是怎么回事了吧。

总之一句话,创建了GDI/GDI+对象,用完之后,要释放!

抱歉!评论已关闭.