You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GDI+截图内存泄漏问题排查求助

问题分析与修复方案

你的内存泄漏明确来自GDI资源未正确释放,以下是代码中的核心问题点和对应修复步骤:

1. 致命泄漏点:GDI资源释放顺序与函数调用错误

代码里存在两个关键的GDI资源管理错误:

  • 注释掉了SelectObject(hDC, old_obj);:将hBitmap选入DC后,必须先把原始GDI对象(old_obj)选回DC,否则hBitmap会被DC占用,无法通过DeleteObject正确销毁。
  • 用DeleteObject(hDC);代替DeleteDC(hDC);:DC属于设备上下文,必须用DeleteDC销毁;DeleteObject仅用于销毁位图、画笔这类GDI对象,函数调用错误会直接导致DC资源泄漏。

2. 修复后的核心资源释放代码

修改ss函数中的资源释放段,确保按正确顺序释放GDI资源:

// 恢复被注释的代码,先将DC的原始对象选回
SelectObject(hDC, old_obj);
// 正确销毁DC
DeleteDC(hDC);
// 此时可以安全销毁位图
DeleteObject(hBitmap);
::ReleaseDC(NULL, hWindow);

3. 其他优化与风险规避点

  • GDI+初始化优化:每次点击都调用GdiplusStartup和GdiplusShutdown效率极低,建议将这两个函数移到程序初始化和退出阶段,只执行一次。
  • 冗余流操作移除:代码中istream->Read后又调用istream->Write是多余的,bitmap.Save已经将数据写入流,直接读取即可。
  • 缓冲区越界防护:全局images缓冲区大小固定为409600,多次截图后imageSz可能超出上限,建议添加边界检查,或改用std::vector<char>这类动态容器避免崩溃。

修复后的完整ss函数示例

void ss(HWND hwnd)
{
    using namespace Gdiplus;
    IStream* istream;
    HRESULT res = CreateStreamOnHGlobal(NULL, true, &istream);
    if (FAILED(res)) {
        return; // 处理流创建失败的情况
    }

    RECT rWindowRect;
    GetWindowRect(hwnd, &rWindowRect);
    
    // 建议将GdiplusStartup/Shutdown移到程序初始化/退出阶段
    ULONG_PTR gdiplusToken;
    GdiplusStartupInput gdiplusStartupInput;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    HDC hWindow = GetDC(NULL);
    if (!hWindow) {
        GdiplusShutdown(gdiplusToken);
        istream->Release();
        return;
    }

    HDC hDC = CreateCompatibleDC(hWindow);
    if (!hDC) {
        ::ReleaseDC(NULL, hWindow);
        GdiplusShutdown(gdiplusToken);
        istream->Release();
        return;
    }

    int width = abs(rWindowRect.right - rWindowRect.left);
    int height = abs(rWindowRect.bottom - rWindowRect.top);
    HBITMAP hBitmap = CreateCompatibleBitmap(hWindow, width, height);
    if (!hBitmap) {
        DeleteDC(hDC);
        ::ReleaseDC(NULL, hWindow);
        GdiplusShutdown(gdiplusToken);
        istream->Release();
        return;
    }

    HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
    BitBlt(hDC, 0, 0, width, height, hWindow, rWindowRect.left, rWindowRect.top, SRCCOPY);

    // GDI+保存位图到流
    Bitmap bitmap(hBitmap, NULL);
    CLSID clsid;
    GetEncoderClsid(L"image/jpeg", &clsid);
    bitmap.Save(istream, &clsid, NULL);

    // 读取流中图片数据
    STATSTG pstatstg = {0};
    istream->Stat(&pstatstg, 0);
    LARGE_INTEGER li = {0};
    istream->Seek(li, STREAM_SEEK_SET, NULL);
    
    ULONG BytesRead = 0;
    int bufSz = pstatstg.cbSize.QuadPart;
    char *buffer = new char[bufSz];
    istream->Read(buffer, bufSz, &BytesRead);

    // 写入全局缓冲区(添加边界检查避免越界)
    if (imageSz + 2 + bufSz < 409600) {
        memcpy(images + imageSz, "!!", 2);
        memcpy(images + imageSz + 2, buffer, bufSz);
        imageSz += 2 + bufSz;
    }

    // 按顺序释放所有资源
    delete[] buffer;
    SelectObject(hDC, old_obj);
    DeleteObject(hBitmap);
    DeleteDC(hDC);
    ::ReleaseDC(NULL, hWindow);

    GdiplusShutdown(gdiplusToken);
    istream->Release();
}

额外验证工具

可以用Windows自带的GDIView工具实时查看进程的GDI对象数量变化,验证修复后的泄漏问题是否解决。

内容的提问来源于stack exchange,提问作者unixpipe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 00:05:27