您的位置:首页 > 其它

程序性能和稳定性优化--GDI

2017-04-26 21:41 127 查看
写这边初衷是记录性能优化开发过程的点滴,以及期间的各种问题排查,修正等, 也为后续同仁减少不必的期间痛苦, 鉴于经验和水平,以及写作能力,如果有问题,望及时纠正:

下面开始记录点滴:

最近测试反馈,程序用着用着就莫名的异常,没有具体点规律, 做为开发,就讨厌这种莫名的问题,后面排查,通过任务管理器查看相关信息,其实这个管理器是很好的一把利器, 通过设置“查看-选择列”, 可以查看很多信息,如内存,GDI,句柄,线程数等, 为我们前期排查提供依据。

排查之后,发现程序GDI会快速的增加,其正常,达到9999之后就不正常或者直接退出, 以此排查是GDI泄露导致程序问题,模块比较多,所以排查也是耗时耗力,通过测试协助,将问题限定在其中某一个模块。但是其中的代码也是10多个类, 且怎么可以定位呢?

这个里有个很放方便的工具,GDIndicator.exe, 详细信息参考如下连接
https://www.microsoft.com/china/MSDN/library/Windev/WindowsXP/DetPluGDILeakCodTooWinXP.mspx?mfr=true;
根据该工具可以直观的查看到那个资源泄露,如DC一致增长,当总GDI数达到10000就异常,进而在模块代码中排查问题所以,如果一切顺利则万事大吉,否则就要针对相关自绘代码逐步排查,已我最近排查为例:

定位到具体模块, 逐步排查自绘等相关逻辑,发现没有问题,进而怀疑调用底层封装的控件有问题,这就坑了,底层是CVirtualGridCtrl基础上做一层封装,其中有个接口会根据当前文字获取高度,具体代码片段如下:

案例一:比较直观的泄露

int CalCaptionHeight()

{

int nHeight = 0;

CVirtualGridCtrl* pGrid = pHead->GetGrid()

if(pGrid )

{

CDC* pDC = pGrid->GetDC();

if(pDC != NULL)

{

CRect rc(0,0,0,0);

pDC ->DrawText(strCaption, strCaption.GetLength(), rc, DT_CALCRECT);

nHeight = rc.Height();

pGrid->ReleaseDC(pDC ); // 之前没有改行代码

}

}

return nHeight

}

该接口为在绘制表头时使用,且为底层控件接口, 客户端程序使用好多年,均没有出现异常想象, 为什么现在会有呢? 主要原因如下:

首先,该接口存在GDI泄露,但是由于调用的次数不多, 所以一般情况没有什么问题;

其次, 因为不正常调用接口,导致大量的刷新操作,进而不断调用CalCaptionHeight(), 进而导出程序异常。

案例一:比较隐晦的:

我们程序中经常将图片做成一个list,然后用CImageListObj等进行加载, 主要是为减少资源数量,以及程序方便,然后使用时会利用
如下代码:
HICON hIcon = CImageListObj.ExtractIcon(nInd)
ICONINFOinfor
::GetIconInfor(hIcon,&infor)
BITMAPbmp
::GetObject(infor.hbmColor,sizeof(BITMAP), (LPVOID)& bmp);
intnWidth = bmp.bmWidth
intnHight = bmp.bmHeigt
::DrawIconEX(Pdc->GetSafeHdc(),cx,cy,hIcon,nWidth, nHieht,0,NULL, DI_NORMAL);
以上通过传递图标索引即可画对应的图片, 以上代码存在严重泄露
主要如下
1, 获取的ICONINFO infor 一定要释放其中的HBITMAP成员,调用如下:
::DeleteObject(infor.hbmColor)
::DeleteObject(infor.hbmMask)
2, 获取的BITMAP 也要进行释放, 调用如下:
::DeleteObject((HGDJOBJ)&bmp)
3,获取的HICON hIcon,一定要记得是否 调用如下:
::DesstroyIcon(hIcon)
发先该问题之后,根据GDIndicator.exe工具,是无法显示HICON的资源, 然后就总数一致增加,最后还好该出自绘代码不多,然后一段一段注释,排查到上面带颜色的泄露,然后查资料等, 合适问题。
期间还有个小插曲, 排查到问题之后, 很愉快的解决,但是第二天测试反馈还存在,就郁闷,以为版本没有发布好, 后来排查, 居然少了::DeleteObject((HGDJOBJ)&bmp)
这行代码,进而还是异常, 看来针对问题还是要保持一定的警惕性

通过以上排查,足以可以,类似这样的问题,有多么坑, 所以平时写代码时,要每一行一行审阅, 常见的问题,一定要铭记于心,为此整理如下想资料,便于大家了解,详见附件文档描述, 如下简要描述GDI

1, 此处所知的系统资源主要知window GDI资源, 如bitmap, dc,pen,icon等,针对其做一份整理,详见附件。

作为还在继续奋战在window UI 做客户端下同仁们,应该对其深有认识:

如上资源为系统资源,其创建是受限的,主要受限如下: 系统本身对限制和注册表最大值的设置,具体如下:

1,系统中每个进程创建GDI是有限制的,理论上为64K,为什么64k呢? 因为GDI实为window维护的一些数据结构,基于稳定和安全将所以GDI对象交系统管理,用户通过获取句柄进行操作。

window2000中,句柄实际为DWORD类型, 占32为字节,因此理论上为64。

2, 每个进程创建最大数还受操作系统中GDI最大数的限制,当达到一定数量时,则不进程创建, 不同的操作系统可能不同,没有做考证。

3,进程创建最大数,还受限于注册表设置最大数影响,主要位于KEY_LOCAL_MACHINE/ SOFTWARE/Microsoft/Windows NT/CurrentVersion/Windows下的"GDIProcessHandleQuota" 项所设置的值, 默认值为10000, 所以当达此致获取就失败, 如果程序为做保护,进而就异常。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: