GDI泄漏排查经验零散总结
1.GDI对象以及释放方法:
GDI对象 |
产生方法 |
销毁方法 |
位图(HBITMAP) |
CreateBitmap,CreateBitmapIndirect, CreateCompatibleBitmap,CreateDIBitmap, CreateDIBSection,CreateDiscardableBitmap |
DeleteObject |
画刷(HBRUSH) |
CreateBrushIndirect,CreateDIBPatternBrush, CreateDIBPatternBrushPt,CreateHatchBrush, CreatePatternBrush,CreateSolidBrush |
DeleteObject |
设备上下文(HDC) |
CreateDC |
DeleteDC,ReleaseDC |
字体(HFONT) |
CreateFont,CreateFontIndirect |
DeleteObject |
内存DC(HDC) |
CreateCompatibleDC |
DeleteDC |
调色板(HPALETTE) |
CreatePalette |
DeleteObject |
画笔(HPEN) |
CreatePen,CreatePenIndirect |
DeleteObject |
区域(HRGN) |
CombineRgn,CreateEllipticRgn, CreateEllipticRgnIndirect,CreatePolygonRgn, CreatePolyPolygonRgn,CreateRectRgn, CreateRectRgnIndirect,CreateRoundRectRgn, |
DeleteObject |
2.资源切换时容易出现的GDI泄漏:
1)SelectObject、SetBitmap、SetIcon、SendMessage(消息为BM_Bitmap时),会返回之前使用的GDI资源,不再使用的GDI资源需要及时释放(记录好之前使用的系统GDI资源,在结束时还原设置并释放掉申请的GDI资源);
2)SelectObject 选入的用户创建的GDI资源,需要在不再使用时选出并释放。
3)使用的控件有时候会因为一些原因在Res资源中添加过图标等GDI资源,导致在代码中做控件初始化时设置新图标产生了GDI泄漏(从代码上来看是第一次设置图标就引起了泄漏)。
4) ::GetDC使用::ReleaseDC来释放,CreateDC \ CreateCompatibleDC使用DeleteDC来释放。
5)窗体Hwnd没有释放也会引起GDI泄漏,因为窗体中使用的GDI资源没有销毁时机,自然也就调用不到内部的销毁函数。
3.LoadImage函数:
LoadImage函数可以加载Bitmap、Icon、Cursor三种GDI资源,需要分别使用DeleteObject、DestroyIcon、DestroyCursor来释放,不可以混用。
LoadImage函数生成的GDI资源使用后就可以释放,不会因为立即释放后导致前面设置的资源不起作用。
4. CImageList存储的GDI资源需要调用DeleteImageList来释放。
5.CDC、CPEN、CBrush等MFC包装的GDI类,在其析构函数中会调用DeleteObject函数取释放资源。
6.创建GDI资源的函数和释放GDI的函数使用次数要匹配,比如:窗口Create、OnInitDialog、以及消息响应等函数会因为一些原因多次调用(比如DoModal如果被循环调用是会引起窗口的Create和OnInitDialog反复触发),如果在这类函数中申请GDI资源需要特别注意,因为一般作为成员变量的GDI资源的释放在析构函数中的话就只会被调用一次。
7.给外部模块调用的函数中如果包含了GDI资源的申请需要在函数头注释,提醒调用者需要手动释放(往往函数被包装几层后外层函数调用者很容易忽略释放)。
8.少量代码是可以根据代码静态检视或者分模块调试来找出GDI泄漏位置,但是大量代码排查需要借助工具才比较有效率,这里推荐Deleaker这款工具(GDI泄漏和内存泄漏都可以准确的找出代码行)。