VCpp检测和解决GDI泄漏问题

⌚Time: 2022-10-19 12:15:16

👨‍💻Author: Jack Ge

关于gdi泄漏问题注意的地方,询问了AI:


在 VC++ 中避免 GDI 泄漏需要注意以下几个关键方面:

1. 基本原则:成对使用

// 获取和释放必须配对
HDC hdc = GetDC(hWnd);
// 使用 hdc...
ReleaseDC(hWnd, hdc);

// 创建和删除必须配对
HBITMAP hBmp = CreateBitmap(...);
// 使用 hBmp...
DeleteObject(hBmp);

2. 常见 GDI 对象及其释放方法

// Pen, Brush, Font, Bitmap, Region, Palette 等
HPEN hPen = CreatePen(PS_SOLID, 1, RGB(255,0,0));
HBRUSH hBrush = CreateSolidBrush(RGB(0,255,0));
HFONT hFont = CreateFont(...);
HBITMAP hBitmap = CreateCompatibleBitmap(...);
HRGN hRgn = CreateRectRgn(...);
HPALETTE hPalette = CreatePalette(...);

// 使用后必须释放
DeleteObject(hPen);
DeleteObject(hBrush);
DeleteObject(hFont);
DeleteObject(hBitmap);
DeleteObject(hRgn);
DeleteObject(hPalette);

3. 特殊的 DC 获取方式

// 对于 GetDC()/ReleaseDC()
HDC hdc = GetDC(hWnd);        // 获取窗口 DC
// 使用...
ReleaseDC(hWnd, hdc);         // 必须用 ReleaseDC

HDC hdc = GetWindowDC(hWnd);  // 获取包含非客户区的 DC
// 使用...
ReleaseDC(hWnd, hdc);

// 对于 BeginPaint()/EndPaint() - 只在 WM_PAINT 中使用
case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    // 绘制操作...
    EndPaint(hWnd, &ps);
}
break;

// 创建和删除兼容 DC
HDC hMemDC = CreateCompatibleDC(hdc);
// 使用...
DeleteDC(hMemDC);             // 不是 DeleteObject!

4. 内存 DC 的正确使用

HBITMAP hOldBmp, hNewBmp;
HDC hMemDC = CreateCompatibleDC(hdc);

hNewBmp = CreateCompatibleBitmap(hdc, width, height);
hOldBmp = (HBITMAP)SelectObject(hMemDC, hNewBmp);

// 使用内存 DC 绘图...

// 恢复旧对象
SelectObject(hMemDC, hOldBmp);
// 先恢复再删除
DeleteObject(hNewBmp);
DeleteDC(hMemDC);

5. 使用 RAII 包装类(推荐)

class GDIDeviceContext {
    HDC m_hDC;
    HWND m_hWnd;
public:
    GDIDeviceContext(HWND hWnd) : m_hWnd(hWnd) {
        m_hDC = GetDC(hWnd);
    }
    ~GDIDeviceContext() {
        if(m_hDC) ReleaseDC(m_hWnd, m_hDC);
    }
    operator HDC() const { return m_hDC; }
};

class GDIObj {
    HGDIOBJ m_hObj;
public:
    GDIObj(HGDIOBJ hObj) : m_hObj(hObj) {}
    ~GDIObj() {
        if(m_hObj) DeleteObject(m_hObj);
    }
    operator HGDIOBJ() const { return m_hObj; }
};

// 使用示例
{
    GDIDeviceContext dc(hWnd);          // 自动释放 DC
    GDIObj pen(CreatePen(PS_SOLID, 1, RGB(255,0,0))); // 自动删除对象
    SelectObject(dc, pen);
    // 绘图操作...
} // 自动清理

6. 检测 GDI 泄漏的工具和方法

7. 常见陷阱

// 错误:SelectObject 后直接删除
HGDIOBJ hOld = SelectObject(hdc, hNewPen);
DeleteObject(hNewPen);  // 错误!应该先恢复旧对象

// 正确做法
HGDIOBJ hOld = SelectObject(hdc, hNewPen);
// ... 使用新 pen ...
SelectObject(hdc, hOld);  // 恢复旧对象
DeleteObject(hNewPen);    // 现在可以删除

// 错误:多次删除同一对象
DeleteObject(hObj);
DeleteObject(hObj);  // 错误!可能导致崩溃

// 错误:用错删除函数
DeleteObject(hDC);   // 错误!DC 应该用 DeleteDC
DeleteDC(hPen);      // 错误!GDI 对象用 DeleteObject

8. 最佳实践

  1. 使用智能指针:如 std::unique_ptr 配合自定义删除器
  2. 封装资源管理:创建资源管理类
  3. 在构造函数中获取,在析构函数中释放
  4. 检查返回值:确保 GDI 函数调用成功
  5. 避免在栈上保存 GDI 对象句柄

通过遵循这些规则,可以有效避免 VC++ 中的 GDI 泄漏问题。


使用GDIView检测的GDI泄漏情况,DC数量一直增加而没有得到释放