C++ WinAPI 后台窗口截图

6
我希望能截取没有焦点的窗口的屏幕截图。我的功能对一些窗口有效,但不是所有窗口,我不知道原因。我尝试捕获Bluestacks App Player的窗口,它完美地工作。但是对于Nox App Player和其他一些游戏,它根本不起作用。我只得到一个大小为窗口的黑色图像。
以下是目前的代码:
void screenshot_window(HWND handle) {
    RECT client_rect = { 0 };
    GetClientRect(handle, &client_rect);
    int width = client_rect.right - client_rect.left;
    int height = client_rect.bottom - client_rect.top;


    HDC hdcScreen = GetDC(handle);
    HDC hdc = CreateCompatibleDC(hdcScreen);
    HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, width, height);
    SelectObject(hdc, hbmp);

    BitBlt(hdc, 0, 0, width, height, hdcScreen, 0, 0, SRCCOPY);

    BITMAPINFO bmp_info = { 0 };
    bmp_info.bmiHeader.biSize = sizeof(bmp_info.bmiHeader);
    bmp_info.bmiHeader.biWidth = width;
    bmp_info.bmiHeader.biHeight = height;
    bmp_info.bmiHeader.biPlanes = 1;
    bmp_info.bmiHeader.biBitCount = 24;
    bmp_info.bmiHeader.biCompression = BI_RGB;

    int bmp_padding = (width * 3) % 4;
    if (bmp_padding != 0) bmp_padding = 4 - bmp_padding;

    BYTE *bmp_pixels = new BYTE[(width * 3 + bmp_padding) * height];;
    GetDIBits(hdc, hbmp, 0, height, bmp_pixels, &bmp_info, DIB_RGB_COLORS);

    BITMAPFILEHEADER bmfHeader;
    HANDLE bmp_file_handle = CreateFile("TestFile.bmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    // Add the size of the headers to the size of the bitmap to get the total file size
    DWORD dwSizeofDIB = (width * 3 + bmp_padding) * height + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    //Offset to where the actual bitmap bits start.
    bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);

    //Size of the file
    bmfHeader.bfSize = dwSizeofDIB;

    //bfType must always be BM for Bitmaps
    bmfHeader.bfType = 0x4D42; //BM

    DWORD dwBytesWritten = 0;
    WriteFile(bmp_file_handle, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
    WriteFile(bmp_file_handle, (LPSTR)&bmp_info.bmiHeader, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
    WriteFile(bmp_file_handle, (LPSTR)bmp_pixels, (width * 3 + bmp_padding) * height, &dwBytesWritten, NULL);

    //Close the handle for the file that was created
    CloseHandle(bmp_file_handle);

    DeleteDC(hdc);
    DeleteObject(hbmp);
    ReleaseDC(NULL, hdcScreen);
    delete[] bmp_pixels;
}
1个回答

5
这种情况在许多应用程序中都可能发生,其中目标窗口仅是一个容器,不负责绘制消息。像记事本这样的标准win32应用程序不会表现出这种行为。但是,您可能会在许多浏览器中遇到此问题。
您始终可以截取桌面窗口的屏幕截图。您可以获取目标窗口的屏幕坐标,然后bitblt该部分目标窗口。请对您的代码进行以下更改:
//GetClientRect(handle, &client_rect);
GetWindowRect(handle, &client_rect);

//HDC hdcScreen = GetDC(handle);
HDC hdcScreen = GetDC(HWND_DESKTOP);

//BitBlt(hdc, 0, 0, width, height, hdcScreen, 0, 0, SRCCOPY);
BitBlt(hdc, 0, 0, width, height, hdcScreen, client_rect.left, client_rect.top, SRCCOPY);

//ReleaseDC(NULL, hdcScreen);
ReleaseDC(HWND_DESKTOP, hdcScreen);

在进行屏幕截图之前,目标窗口必须是屏幕上最顶层的可见窗口。例如,您可以按照以下顺序调用screenshot_window

HWND hwnd = FindWindow(0, L"Calculator");
SetForegroundWindow(hwnd);
Sleep(1000);
screenshot_window(hwnd);

或者,您可以使用Dwm缩略图 API在自己的窗口中绘制目标窗口。但是,请注意,您不能使用GetDC(my_hWnd)来访问"Dwm缩略图"上的位图。相反,您需要使用GetDC(HWND_DESKTOP)来截取桌面窗口的屏幕截图。这一次,请确保您自己的窗口是顶层窗口。

应用程序必须支持DPI,否则屏幕坐标将不匹配。


此外,原始代码存在资源泄漏问题。GetDC应该使用相同的handle而不是NULL来清理并释放内存,应该使用ReleaseDC

HDC hdcScreen = GetDC(handle);
...
//ReleaseDC(NULL, hdcScreen);
ReleaseDC(handle, hdcScreen);

1
最后一行应该是 ReleaseDC(handle, hdcScreen); - zett42
2
谢谢你的回答,难道没有办法在这些窗口被其他窗口覆盖时捕获它们,而不必将窗口置于前景吗? - SYY99
2
您可以使用Dwm Thumbnail API在自己的窗口中绘制目标窗口。确保您自己的窗口是顶层窗口。同样,您需要使用GetDC(HWND_DESKTOP)来截取桌面窗口的屏幕截图。 - Barmak Shemirani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接