Windows平台用BitBlt+Gdiplus截全屏最稳方式是调用GetDC(NULL)获取屏幕DC,绑定CreateCompatibleBitmap位图后BitBlt拷贝,需GdiplusStartup初始化、动态获取PNG编码器CLSID,并配对释放GDI资源。

Windows 平台用 BitBlt + Gdiplus 截全屏最稳
直接调 Windows GDI 是目前 C++ 全屏截图最轻量、兼容性最好的方式,不依赖第三方库,Win7 及以上原生支持。关键不是“能不能”,而是BitBlt 的源设备上下文(HDC)必须来自屏幕,且目标 HDC 要绑定到兼容位图(CreateCompatibleBitmap),否则截出来是黑图或乱码。
常见错误:GetDC(NULL) 拿到的是整个屏幕 DC,但若没调 SelectObject 把位图选入目标 DC,BitBlt 实际没写入任何像素;还有人漏掉 GdiplusStartup,导致 Gdiplus::Bitmap::Save 直接崩溃。
- 必须先调
Gdiplus::GdiplusStartup,否则 PNG/BMP 保存失败,报错GenericError -
BitBlt的dwRop参数固定用SRCCOPY,别用NOTSRCCOPY等非常规值 - 全屏宽高要用
GetSystemMetrics(SM_CXSCREEN)和GetSystemMetrics(SM_CYSCREEN),别硬写 1920×1080
抓指定窗口画面得用 GetWindowDC 而非 GetDC
GetDC(hwnd) 只能取客户区(Client Area),标题栏、边框、阴影全丢;要完整窗口画面(含非客户区),必须用 GetWindowDC(hwnd),并确保窗口未最小化(IsIconic(hwnd) 返回 false)。
容易踩的坑:GetWindowRect 返回的是屏幕坐标,但 BitBlt 的目标矩形是相对于目标位图左上角的,所以宽高直接用 rect.right - rect.left 即可,无需平移坐标。
立即学习“C++免费学习笔记(深入)”;
- 调
GetWindowRect(hwnd, &rect)后,位图尺寸就是rect.right - rect.left×rect.bottom - rect.top - 如果窗口被其他窗口遮挡,
BitBlt仍能捕获其原始画面(GDI 层面未裁剪),这是预期行为 - 对无窗口句柄的 UWP 应用(如 Microsoft Store 版 Edge),此法无效,需换用 Desktop Duplication API
保存为 PNG 时注意 CLSID 初始化和压缩质量
C++ 里 Gdiplus::Bitmap::Save 不支持直接设 PNG 压缩级别——PNG 本身无损,所谓“压缩”实为滤波器选择和 zlib 级别,GDI+ 封装里不暴露该控制权。你只能靠 CLSID 区分格式,而 PNG 的 CLSID 必须从 Gdiplus::GetEncoderClsid(L"image/png", &clsid) 动态获取,硬编码会失败。
BMP 则简单得多:L"image/bmp" 对应的 CLSID 可静态定义,且无压缩参数,文件体积大但 100% 兼容。
- PNG 保存失败常见报错:
InvalidParameter→ 多半是GetEncoderClsid返回 -1,检查传入的 MIME 字符串是否带空格或大小写错误 - 想减小 PNG 体积?只能靠后期用 libpng 或 ImageMagick 二次压缩,GDI+ 不提供接口
- 路径含中文时,务必用
wchar_t*和Save(L"out.png", &clsid),传char*会乱码或静默失败
跨 DPI 缩放场景下截图内容会被拉伸或裁剪
在高 DPI 显示器(如 150% 缩放)上,GetSystemMetrics(SM_CXSCREEN) 返回的是逻辑像素,而 BitBlt 操作的是物理像素。若程序未声明 DPI 感知(manifest 中缺 dpiAware=true),系统会自动缩放 DC,导致截图模糊或只有一半画面。
解决方案只有两个:要么在 manifest 加 <dpiAware>true</dpiAware>,要么改用 GetDpiForSystem() + GetSystemMetricsForDpi(Win10 1703+)动态算物理尺寸。
- 没加 DPI 感知时,
GetSystemMetrics(SM_CXSCREEN)可能返回 1280(逻辑宽),但实际屏幕物理宽是 1920 —— 这就是截图缺右边的原因 - 使用
GetDC(NULL)前,先调SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE)(需 Win8.1+)比改 manifest 更灵活 - Qt 或 wxWidgets 等框架封装了 DPI 适配,纯 Win32 API 必须自己扛
实际代码里最易出问题的是 GDI 对象泄漏:ReleaseDC、DeleteDC、DeleteObject 必须严格配对,少一次就内存缓慢增长。这事没法靠 RAII 自动兜底,得手写 wrapper 或用 ATL 的 CDCHandle。


















