推荐用getdesktopwindow()+getwindowdc()获取屏幕dc,创建兼容dc再生成位图,bitblt后用getdibits导出像素,构造bmp头时bfoffbits需按调色板大小动态计算。

GetDC(NULL) 获取全屏设备上下文是否安全
直接传 NULL 给 GetDC 确实能拿到整个屏幕的 DC,但这是“不推荐的惯用法”——它返回的是全局共享的屏幕 DC,若其他线程或程序同时调用 GetDC(NULL) 并修改了绘图状态(比如改变了 SetBkMode 或 SelectObject),你的截图可能被意外污染。
更稳妥的做法是用 GetDesktopWindow() + GetWindowDC():
-
GetDesktopWindow()返回桌面窗口句柄,是唯一且稳定的 -
GetWindowDC()获取该窗口的完整 DC(含任务栏区域),且每个调用相互隔离 - 用完必须配对调用
ReleaseDC(hwnd, hdc),否则资源泄漏
CreateCompatibleBitmap 需要先创建兼容 DC
不能直接用屏幕 DC 创建位图。Windows 要求位图与 DC 的像素格式、色彩深度严格匹配,而屏幕 DC 通常是“显示驱动相关”的,CreateCompatibleBitmap(hdcScreen, ...) 在某些高 DPI 或多显卡环境下会失败(返回 NULL)。
正确链路是:
立即学习“C++免费学习笔记(深入)”;
- 用
CreateCompatibleDC(hdcScreen)创建一个内存 DC - 再用这个内存 DC 作为参数调用
CreateCompatibleBitmap(hdcMem, width, height) - 最后用
SelectObject(hdcMem, hBitmap)把位图选入内存 DC
漏掉 CreateCompatibleDC 这一步,CreateCompatibleBitmap 极大概率返回空指针,且不报错,容易误判为“没截到图”。
BitBlt 复制后必须用 GetDIBits 导出像素数据
BitBlt 只是把屏幕内容“画进”内存位图,但该位图仍是 GDI 对象,内部存储格式不透明(可能是压缩的、带调色板的、甚至延迟分配的)。想保存为标准 BMP 文件,必须用 GetDIBits 把像素逐字节拷贝出来。
关键注意点:
- 第一次调用
GetDIBits传lpvBits = NULL,仅获取BITMAPINFO中的biSizeImage和补零信息 - 第二次才传真实缓冲区,且缓冲区大小必须按
biSizeImage分配(不是width * height * 4) -
BITMAPINFOHEADER的biHeight设为负值,表示自上而下扫描(BMP 文件标准),否则图像会上下翻转
保存 BMP 文件时 BITMAPFILEHEADER 容易写错偏移
手动构造 BMP 文件头时,bfOffBits(像素数据起始偏移)不是固定值。它等于 sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + palette_size,而 palette_size 在 24 位图中为 0,但在 8 位图中是 1024(256 项 × 4 字节)。硬编码 54 只适用于无调色板的 24 位图,一旦支持真彩色以外的格式就会损坏文件。
更可靠的方式:
- 用
GetDIBits第一次调用填充完整的BITMAPINFO(含biClrUsed) - 计算 palette_size =
bi.biClrUsed ? bi.biClrUsed * sizeof(RGBQUAD) : 0 bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + palette_size
实际项目里建议直接用 StretchDIBits + 内存映射写入,或者改用 Gdiplus::Bitmap::Save 避开这些细节——但如果你正在调试 GDI 截图流程,这些字节对齐和偏移量就是最常卡住的地方。










