windows平台用bitblt抓取主屏最简实现需依次调用getdesktopwindow()、getdc()、createcompatibledc()、createcompatiblebitmap()、selectobject()、bitblt(),最后释放资源;关键点包括匹配位深、正确填充bmp文件头(bfoffbits和行对齐)、避免资源泄漏。

Windows平台用BitBlt抓取主屏最简实现
直接调用GDI API是最轻量、无需第三方库的方式,适用于Win32桌面程序。核心是获取屏幕设备上下文(DC),再用BitBlt把像素拷贝到内存DC中。
常见错误:忘记释放HDC或HBITMAP导致资源泄漏;用GetDC(NULL)后没配对调用ReleaseDC;位图创建时未指定BI_RGB和正确位深。
- 必须用
CreateCompatibleDC创建内存DC,不能直接在屏幕DC上操作位图 - 位图创建需匹配屏幕颜色格式:
GetDeviceCaps(hdcScreen, BITSPIXEL)决定biBitCount -
BitBlt返回0表示失败,应检查GetLastError(),常见原因是目标DC未选入位图 - 保存为BMP文件需手动填充
BITMAPFILEHEADER和BITMAPINFOHEADER,跳过调色板(24位真彩色时)
保存为BMP时BITMAPFILEHEADER怎么填才不黑屏
截图数据本身没问题,但BMP头写错会导致图片全黑或打不开。关键不是像素数据,而是文件头字段的字节序和偏移计算。
容易踩的坑:bfOffBits算错(漏掉信息头+可选调色板长度)、bfSize用sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+图像数据大小但忘了对齐(BMP行字节必须4字节对齐)。
-
bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)(24位图无调色板) - 每行字节数按
((width * 3) + 3) & ~3向上对齐到4字节边界 -
bfSize = bfOffBits + 行字节数 × height,不是原始宽×高×3 - 写文件前用
SetFilePointer定位到bfOffBits处再写像素数据,避免头信息被覆盖
为什么GetDIBits比BitBlt更适合后续处理
如果截图后要转成OpenCV cv::Mat、做滤镜或上传网络,GetDIBits直接拿到RGB像素指针,省去从位图对象提取数据的步骤。
BitBlt只是复制,像素还在GDI位图里;GetDIBits能一次性把解压缩后的RGB数据扔进你给的缓冲区,更可控。
- 调用前先用
GetDIBits(hdc, hBitmap, 0, height, NULL, &bmi, DIB_RGB_COLORS)查所需缓冲区大小(bmi.bmih.biSizeImage) - 第二次调用传真实缓冲区指针,
GetDIBits自动按BMP格式填充——注意它默认是Bottom-Up顺序(首行是图像底部) - 若需Top-Down,设
bmi.bmih.biHeight = -height,此时GetDIBits会反转行序,但biSizeImage不变 - 务必检查返回值,失败时
bmi.bmih.biSizeImage可能为0
多显示器下只截指定屏幕用EnumDisplayMonitors而非硬编码坐标
硬写(0,0,width,height)只能抓主屏。要精确控制哪个屏,得遍历显示器并匹配HMONITOR。
误区:以为GetWindowDC(GetDesktopWindow())能跨屏,其实它仍只返回主屏DC;GetDC(NULL)也是同理。
- 用
EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&monitorInfo)回调每个显示器 - 回调函数里调用
GetMonitorInfo(hMonitor, &mi),mi.rcMonitor给出该屏绝对坐标 - 创建兼容DC后,
BitBlt的源坐标用mi.rcMonitor.left/top,宽高用right-left/bottom-top - 别依赖
GetSystemMetrics(SM_CXSCREEN),那是主屏宽度,不是所有屏总宽
跨屏截图真正的难点不在API调用,而在理解Windows坐标系是全局的、原点在主屏左上角,所有显示器矩形都相对于这个原点定义。漏掉这点,多屏时截图位置永远偏移。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











