显然,微软已经改变了在2018年底发布的 Windows 更新1809中使用剪辑的方式。在此更新之前,GetClipBox()
返回窗口的完整客户端矩形,即使它(部分)在屏幕之外。
在更新之后,同一个函数返回一个剪裁过的矩形,只包含屏幕上仍然存在的部分。< strong > 这会导致设备上下文内容没有在离屏区域更新,这会阻止我从这些窗口截屏。
问题是: 我能以某种方式操纵剪切区域吗?
我做了一些研究,似乎最终的裁剪区域受到窗口区域,更新矩形和系统区域的影响-就我所理解的“全局裁剪区域”。我用 GetWindowRgn()
和 GetRgnBox()
检查了窗口区域,它们返回的值对于 Windows1809和旧版本都是相同的。GetUpdateRect()
还返回完整的客户端矩形,因此这也不会成为问题。我还试图钩住 BeginPaint()
方法,看看改变 PAINTSTRUCT.rcPaint
是否有用,但没有成功。
所以我剩下的工作就是调整系统区域,或者有时候称为可见区域。然而,我不知道这是否可能以及如何可能。但我想也许有人确实有一个解决方案的想法! ?
编辑: 为了更清楚地说明这一点,我不认为剪辑是由应用程序本身完成的,因为同一个应用程序版本的屏幕外截图在 Windows 1809之前就可以工作了,而且不适用于升级后的 Windows 版本。相反,Windows 本身似乎会剪切屏幕外的任何表面。
编辑2: 这里有一个截屏的最小工作代码示例。
// Get the client size.
RECT crect;
GetClientRect(hwnd, &crect);
int width = crect.right - crect.left;
int height = crect.bottom - crect.top;
// Create DC and Bitmap.
HDC windowDC = GetDC(hwnd);
HDC memoryDC = CreateCompatibleDC(windowDC);
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;
char* pixels;
HBITMAP bitmap = CreateDIBSection(windowDC, &bitmapInfo, DIB_RGB_COLORS, (void**)&pixels, 0, 0);
HGDIOBJ previousObject = SelectObject(memoryDC, bitmap);
// Take the screenshot. Neither BitBlt nor PrintWindow work.
BitBlt(memoryDC, 0, 0, width, height, windowDC, 0, 0, SRCCOPY);
// ..or..
// PrintWindow(hwnd, memoryDC, PW_CLIENTONLY);
// Save the image.
BITMAPFILEHEADER bitmapFileHeader;
bitmapFileHeader.bfType = 0x4D42;
bitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
std::fstream hFile("./screenshot.bmp", std::ios::out | std::ios::binary);
if(hFile.is_open())
{
hFile.write((char*)&bitmapFileHeader, sizeof(bitmapFileHeader));
hFile.write((char*)&bitmapInfo.bmiHeader, sizeof(bitmapInfo.bmiHeader));
hFile.write(pixels, (((32 * width + 31) & ~31) / 8) * height);
hFile.close();
}
// Free Resources
ReleaseDC(hwnd, windowDC);
SelectObject(memoryDC, previousObject);
DeleteDC(memoryDC);
DeleteObject(bitmap);
你可以下载 编译的可执行文件从谷歌驱动器在这里。使用方法是 Screenshot.exe <HWND>
,其中 HWND 是窗口句柄的十六进制地址,如 Spy + + 中所示。它会在工作目录中保存目标窗口的截图为 screenshot.bmp
(确保你可以写入目录)。这个屏幕快照几乎适用于所有窗口(即使它们隐藏在其他窗口后面) ,但是只要你将窗口部分移出屏幕,这个屏幕快照就会继续显示窗口离开屏幕部分的旧窗口内容(例如,在离开屏幕时调整它的大小,以查看效果)。这只发生在 Windows 1809上,它仍然显示早期 Windows 版本的最新内容。
编辑3: 我对此做了更多的研究。关于使用 WS_EX_LAYERED
样式不起作用的 AdobeAir 应用程序: 我发现它在内部使用 BitBlt
,并将后台缓冲区呈现给窗口 dc。渲染步骤如下:
hdcWin
hdcMem
SelectObject(hdcMem, bmp)
选择一个 HBITMAP
到 hdcMem
hdcMem
到 hdcWin
。
在 BitBlt
调用期间,即使在离屏区域,hdcMem
也包含有效的像素数据,但是这些数据永远不会复制到 hdcWin
。我在 BitBlt
呼叫期间查看了系统区域。对于 hdcMem
,系统区域是 NULLREGION
,但对于 hdcWin
,该区域总是在屏幕边缘被剪切。我还尝试调整系统区域,将所有对 GetDC
的调用替换为 GetDCEx(hwnd, hrgn, DCX_CACHE | DCX_INTERSECTRGN)
(正如前面提到的 在这篇文章中) ,但是这不起作用,而且似乎也没有提供扩展该区域的选项。我真的认为解决这个问题的秘诀在于操作窗口直流电的系统区域,但我不知道如何做到这一点。
如果发现 CreateDC
函数接受一个指向 DEVMODE
结构的指针作为最后一个参数(我不知道)。它依次包含字段 dmPelsWidth
、 dmPelsHeight
和 dmPosition
。我相信这些构成了系统区域,也许如果我能操纵它们,直流将不再被剪切,但我还不能钩住 CreateDC
函数。
如果你有任何新的想法基于我的新见解,请分享他们。我会很感激任何帮助!