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
相关产品推荐
相关产品推荐

