C++ WinApi窗口截图重复问题:如何每次获取最新画面?
问题描述
通过C++ WinApi获取指定窗口截图时,首次截图正常,但后续多次截图均得到相同画面;重启系统后首次截图可获取新画面,但后续再次截图仍重复。已尝试调用GdiFlush()无效,怀疑存在缓存机制但不知如何清除。
原代码:
#include <cstdio> #include <windows.h> #include <tlhelp32.h> #include <iostream> #include <chrono> #include <thread> #include "opencv2/core/core.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <opencv2/highgui/highgui.hpp> cv::Mat hwnd2mat(HWND handle_window); int main( int, char *[] ) { using namespace std::chrono_literals; PROCESSENTRY32 entry; entry.dwSize = sizeof(PROCESSENTRY32); HWND handle; handle=FindWindowA(NULL, "test.exe"); cv::Mat pict = hwnd2mat(handle); cv::imwrite("1.jpg", pict); return 0; } cv::Mat hwnd2mat(HWND handle_window){ GdiFlush(); HDC hwindowDC=GetDC(handle_window); HDC hwindowCompatibleDC=CreateCompatibleDC(hwindowDC); RECT window_size; GetClientRect(handle_window, &window_size); int src_height = window_size.bottom; int src_width = window_size.right; HBITMAP bitmap = CreateCompatibleBitmap( hwindowDC, src_width,src_height); BITMAPINFOHEADER bi; bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = src_width; bi.biHeight = -src_height; bi.biPlanes = 1; bi.biBitCount = 32; bi.biCompression = BI_RGB; bi.biSizeImage = 0; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrUsed = 0; bi.biClrImportant = 0; SelectObject(hwindowCompatibleDC, bitmap); StretchBlt( hwindowCompatibleDC, 0,0, src_width, src_height, hwindowDC, 0, 0,src_width,src_height, SRCCOPY); cv::Mat src; src.create(src_height, src_width,CV_8UC4); GetDIBits(hwindowCompatibleDC,bitmap,0,src_height,src.data,(BITMAPINFO *)&bi,DIB_RGB_COLORS); DeleteObject (bitmap); DeleteDC(hwindowCompatibleDC); ReleaseDC(handle_window, hwindowDC); return src; }
解决方案
问题根源在于系统对窗口DC的缓存机制,默认StretchBlt/BitBlt可能读取缓存画面而非实时内容,解决核心是强制窗口将最新内容主动渲染到目标DC:
- 发送
WM_PRINTCLIENT/WM_PRINT消息:迫使窗口主动将客户区(或整个窗口)的最新内容绘制到我们创建的兼容DC,绕过系统缓存。 - 替换拷贝式截图为消息驱动绘制:相比直接拷贝DC,消息驱动方式能确保获取实时画面。
修正后的代码
#include <cstdio> #include <windows.h> #include <tlhelp32.h> #include <iostream> #include <chrono> #include <thread> #include "opencv2/core/core.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <opencv2/highgui/highgui.hpp> cv::Mat hwnd2mat(HWND handle_window); int main( int, char *[] ) { using namespace std::chrono_literals; HWND handle = FindWindowA(NULL, "test.exe"); if (!handle) { std::cerr << "Failed to find window" << std::endl; return 1; } // 测试多次截图,验证实时性 for (int i = 0; i < 3; ++i) { cv::Mat pict = hwnd2mat(handle); std::string filename = std::to_string(i+1) + ".jpg"; cv::imwrite(filename, pict); std::this_thread::sleep_for(2s); // 等待窗口内容变化 } return 0; } cv::Mat hwnd2mat(HWND handle_window){ HDC hwindowDC = GetDC(handle_window); HDC hwindowCompatibleDC = CreateCompatibleDC(hwindowDC); RECT window_size; GetClientRect(handle_window, &window_size); // 正确计算窗口尺寸(兼容窗口位置非原点的场景) int src_height = window_size.bottom - window_size.top; int src_width = window_size.right - window_size.left; HBITMAP bitmap = CreateCompatibleBitmap(hwindowDC, src_width, src_height); SelectObject(hwindowCompatibleDC, bitmap); // 强制窗口绘制最新客户区内容到兼容DC // PRF_CLIENT: 绘制客户区; PRF_CHILDREN: 绘制子窗口; PRF_ERASEBKGND: 先擦除背景 SendMessage(handle_window, WM_PRINTCLIENT, (WPARAM)hwindowCompatibleDC, PRF_CLIENT | PRF_CHILDREN | PRF_ERASEBKGND); // 如果需要包含标题栏等非客户区,替换为WM_PRINT消息: // SendMessage(handle_window, WM_PRINT, (WPARAM)hwindowCompatibleDC, PRF_CLIENT | PRF_NONCLIENT | PRF_CHILDREN | PRF_ERASEBKGND); BITMAPINFOHEADER bi; bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = src_width; bi.biHeight = -src_height; bi.biPlanes = 1; bi.biBitCount = 32; bi.biCompression = BI_RGB; bi.biSizeImage = 0; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrUsed = 0; bi.biClrImportant = 0; cv::Mat src; src.create(src_height, src_width, CV_8UC4); GetDIBits(hwindowCompatibleDC, bitmap, 0, src_height, src.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS); // 清理资源 DeleteObject(bitmap); DeleteDC(hwindowCompatibleDC); ReleaseDC(handle_window, hwindowDC); return src; }
关键修改说明
- 窗口有效性检查:避免
FindWindowA失败后后续操作出错。 - 强制实时重绘:通过
WM_PRINTCLIENT消息让窗口主动渲染最新内容到目标DC,彻底绕过系统缓存。 - 准确尺寸计算:使用
bottom-top和right-left确保尺寸计算准确,兼容窗口位置非原点的场景。 - 多截图测试:主函数添加循环测试,验证每次截图都能获取最新画面。
内容的提问来源于stack exchange,提问作者Aleksandr
相关产品推荐
相关产品推荐

