隐藏窗口的 PrintWindow 和 BitBlt

Posted

技术标签:

【中文标题】隐藏窗口的 PrintWindow 和 BitBlt【英文标题】:PrintWindow and BitBlt of hidden windows 【发布时间】:2019-05-11 14:48:16 【问题描述】:

我的程序正在截取其他应用程序窗口的屏幕截图以自动执行某些任务。这些窗口有时会隐藏在屏幕外或被其他窗口遮挡。

为了减少混乱,我从代码清单中删除了所有错误检查。我正在准备这两种类型的屏幕截图

// Get size of the target window.
RECT clientRect;
GetClientRect(hwnd, &clientRect);
int width = clientRect.right - clientRect.left;
int height = clientRect.bottom - clientRect.top;
// Create a memory device context.
HDC windowDC = GetDC(hwnd);
HDC memoryDC = CreateCompatibleDC(windowDC);
// Create a bitmap for rendering.
BITMAPINFO bitmapInfo;
ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO));
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo.bmiHeader.biWidth = width;
bitmapInfo.bmiHeader.biHeight = -height;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biSizeImage = width * height * 4;
RGBQUAD* buffer;
HBITMAP bitmap = CreateDIBSection(windowDC, &bitmapInfo, DIB_RGB_COLORS, (void**)&buffer, 0, 0);
HGDIOBJ previousObject = SelectOBject(memoryDC, bitmap);

然后我会用其中一个来截取实际的屏幕截图

PrintWindow(hwnd, memoryDC, PW_CLIENTONLY); 
GdiFlush();

UpdateWindow(hwnd);
BitBlt(memoryDC, 0, 0, width, height, windowDC, 0, 0, SRCCOPY);
GdiFlush();

然后我将缓冲区内容复制到向量中

std::vector<RGBQUAD> pixels;
pixels.resize(width * height,  0, 0, 0, 0 );
memcpy(&pixels[0], buffer, bitmapInfo.bmiHeader.biSizeImage);

最后我正在清理所有东西

ReleaseDC(hwnd, windowDC);
SelectObject(memoryDC, previousObject);
DeleteDC(memoryDC);
DeleteObject(bitmap);

我对上面的代码有三个问题:

    我需要拨打GdiFlush()吗?我阅读了文档并进行了一些 Google 研究,但我仍然不确定调用它是否有意义。 在BitBlt() 之前调用UpdateWindow() 是否有影响?这是否有助于使设备上下文内容“更新”? 我是否正确假设对于PrintWindow(),所有工作都在目标应用程序内完成(增加目标应用程序的 CPU 使用率),而 BitBlt() 完全在调用线程内执行(从而增加 CPU 使用率我自己的应用程序)? 在什么情况下上述函数可能会失败?我知道BitBlt() 仅在启用桌面组合 (DWM) 的情况下适用于隐藏窗口,但在极少数系统 (Windows 8/10) 上BitBlt()PrintWindow() 似乎对于它们工作的窗口失败在其他系统上就好了(即使启用了 DWM)。不过,我找不到任何模式来解释为什么。

感谢任何信息,谢谢。

【问题讨论】:

我认为这完全没问题,因为隐藏和/或遮挡的窗口不需要保留它们的内容。 你最终想要完成什么?截屏以自动化某些镜头听起来像是一种真的复杂的方式来模仿 UI 自动化。 GdiFlush 我认为是使用 GDI 写入打印机和绘图仪的保留。我不知道即使启用了 DWM,BitBlt 也会成功。 PrintWindow 发送一条 WM_PRINT 消息,因此目标应用程序不能崩溃并且必须实际处理它。如果您尝试与更高的完整性级别交谈,我希望这两种方法都会失败?窗户。 感谢您的 cmets。让我感到困惑的是,这在大多数系统上都能可靠地工作。它可能在一个系统上为一个应用程序工作,而在另一个系统上为同一应用程序(和应用程序版本)失败。这可能与安装了两个图形卡的系统有关吗?我也将不胜感激(2)和(3)的任何答案 - 谢谢。我还在 github 上找到了这条评论:github.com/mozilla/positron/blob/… 检查这些系统是否使用了软件光栅化器。无论如何,您真正想在这里完成什么?这个问题强烈暗示以错误的方式做事。除此之外,应用程序可以explicitly request, that taking a screenshot won't work。 【参考方案1】:

两个多月后,我终于意识到这只发生在 2018 年末的 Windows 更新 1809 中。显然,Windows 通过该更新改变了处理剪辑的方式,因此不再更新部分 Windows 的设备上下文内容位于屏幕外。

回答个别问题:

1) GdiFlush 似乎没有什么不同,至少就我目前所知。

2) 仍然不能 100% 确定,但我也认为这没有什么不同。

3) 还是不知道。

4) 见上面的答案。

【讨论】:

【参考方案2】:

最后,经过几个小时的调查,我找到了解决该问题的方法: 在要成像的表单的 ACTIVATE 事件中调用以下命令就足够了(VB 编码中的示例):

Call SetWindowLong(me.hwnd, GWL_EXSTYLE, WS_EX_LAYERED)

而该命令的定义如下:

Private Declare Function SetWindowLong Lib "User32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Private Const GWL_EXSTYLE = (-20)

Private Const WS_EX_LAYERED = &H80000

请试试这个!

最好的问候, 小鹿斑比66

【讨论】:

我刚刚用记事本测试了这个,它似乎真的有效。下周我将进行更多测试!我真的不相信有人会想出一个解决方案,所以我真的很感激,并希望这不仅适用于记事本。谢谢! 谢谢你,效果很好! (即使我不知道为什么) 不幸的是,这不适用于所有窗口。在设置WS_EX_LAYERED 之后,窗口就不再更新了。一开始我以为是因为窗口使用了CS_CLASSDC或者CS_OWNDC,但是这些样式都没有设置。我试过打电话给SetLayeredWindowAttributes(),但没有成功。有什么想法吗? 抱歉,我无法编辑我的评论,但显然,我正在谈论的窗口来自 Adob​​e Air 应用程序。 我在应用程序的主窗体上注意到了同样的事情,但在其他窗体上却没有。我不知道这是什么结论。 (在 VB.NET 中,也不是在 C# 中......)

以上是关于隐藏窗口的 PrintWindow 和 BitBlt的主要内容,如果未能解决你的问题,请参考以下文章

从 XP 中的隐藏或剪切窗口复制内容?

Windows 10 中非活动桌面上的 PrintWindow

PrintWindow 发送消息 WM_PAINT 还是 WM_PRINT?

Printwindow 打印有空白区域

MFC PrintWindow(CPaintDC) 有效,但 PrintWindow(CDC) 无效

Autoit中用PrintWindow替代ScreenCapture函数实现截图