BitBlt 截图需按顺序获取屏幕DC、创建兼容DC和DIB位图、用BitBlt拷贝、构造BMP头并写入像素,最后正确释放资源;关键在位图方向(biHeight为负)、行对齐(每行字节数4倍数)、句柄生命周期管理。

用 BitBlt 从屏幕 DC 拷贝图像到兼容 DC
核心是把屏幕内容“复制”进内存位图,不是直接读显存。必须先获取屏幕设备上下文(GetDC(NULL)),再创建兼容 DC 和兼容位图,最后用 BitBlt 把屏幕像素搬进去。漏掉任一环节都会黑屏或返回空数据。
-
GetDC(NULL)获取全屏 DC,不能用GetDesktopWindow()配GetDC(),否则可能截不到任务栏或某些 UWP 窗口 - 兼容 DC 必须用
CreateCompatibleDC(hdcScreen)创建,不能用GetDC得到的 DC 直接操作位图 -
BitBlt的源坐标是(0, 0),目标坐标也是(0, 0),宽高要和屏幕一致(GetSystemMetrics(SM_CXSCREEN)等) - 拷贝完必须调用
SelectObject把旧位图选回去,否则DeleteObject会失败
用 CreateDIBSection 替代 CreateCompatibleBitmap 才能安全写入 BMP 文件
CreateCompatibleBitmap 创建的位图没有直接内存指针,无法用 fwrite 写出原始像素;而 CreateDIBSection 能返回可读写的内存地址,是保存为 BMP 的前提。GDI 截图保存 BMP 必须走这条路,否则只能靠 GetDIBits 多拷贝一次,效率低且易出错。
- 调用
CreateDIBSection时pbmi参数要填完整的BITMAPINFO,包括biSize、biWidth、biHeight、biPlanes、biBitCount(设为 24)、biCompression(BI_RGB) -
biHeight设为负值(如-height)可让位图内存布局与 BMP 文件一致(顶行在前),避免后续翻转 - 返回的
pvBits就是像素起始地址,可直接传给fwrite
手动构造 BMP 文件头和信息头再写入像素数据
BMP 是裸格式,没有封装库也得自己拼头。文件头(BITMAPFILEHEADER)和信息头(BITMAPINFOHEADER)必须严格对齐、字节序正确,否则 Windows 看不了。尤其注意 bfOffBits 要算准:文件头 14 字节 + 信息头 40 字节 + 可选调色板(24 位图无调色板,所以就是 54)
-
bfSize=54 + width * height * 3(24 位图每像素 3 字节),但要注意 BMP 行字节必须是 4 的倍数,所以每行实际宽度要补齐:rowSize = ((width * 3) + 3) & ~3 -
bfOffBits=54,biSizeImage=rowSize * height,这两个值不一致是常见错误来源 - 写文件必须用二进制模式
fopen(..., "wb"),文本模式会破坏像素数据
释放资源顺序不能错:先释放位图内存,再删 GDI 对象,最后释放 DC
顺序反了会导致句柄泄漏甚至程序崩溃。GDI 对象引用计数很敏感,DeleteObject 前必须确保没被任何 DC 选中;ReleaseDC 前必须确保没被用于绘图。
立即学习“C++免费学习笔记(深入)”;
- 用
SelectObject(hdcMem, hOldBitmap)恢复原位图后,再调用DeleteObject(hBitmap) -
pvBits是CreateDIBSection分配的,不用free,但对应位图对象仍需DeleteObject -
DeleteDC(hdcMem)必须在DeleteObject之后,ReleaseDC(hdcScreen)必须在最后 - 建议所有句柄初始化为
NULL,释放前加if (h != NULL)判断,避免重复释放
GDI 截图看着简单,但每个 API 调用背后都有隐含约束,尤其是位图方向、内存对齐、句柄生命周期这三点,几乎每个新手都会卡住一两次。










