简体   繁体   English

使用Windows API获取黑色屏幕截图

[英]Getting Black Screenshots with Windows API

I'm dumping the screen of a client window and writing it to the disk using C++ and the Windows API as follows: 我正在转储客户端窗口的屏幕并使用C++Windows API将其写入磁盘,如下所示:

const auto window_handle = FindWindow(nullptr, ...);
const auto output_file_path = L"output.png";

// Get the window screenshot
std::cout << "Performing screenshot... ";
HBITMAP bitmap;
const auto screen_shot_successfully_performed = perform_screen_shot(window_handle, bitmap);

if (screen_shot_successfully_performed)
{
    std::cout << "OK!" << std::endl;

    // Save it
    std::cout << "Saving image... ";
    const auto saving_image_succeeded = save_image(bitmap, output_file_path);
    if (saving_image_succeeded)
    {
        std::cout << "OK!" << std::endl;
    }
}

ImageEncoding.h : ImageEncoding.h

#pragma once

#include <windows.h>

#include <gdiplus.h>
#include <gdiplustypes.h>
#include <iostream>
#include "ErrorHandling.h"
#include "Conversions.h"
using namespace Gdiplus;
#pragma comment (lib, "Gdiplus.lib")

inline int get_encoder(const WCHAR* format, CLSID* p_clsid)
{
    UINT image_encoders_count = 0;
    UINT image_encoder_array_size = 0;

    GetImageEncodersSize(&image_encoders_count, &image_encoder_array_size);
    if (image_encoder_array_size == 0)
    {
        return -1; // Failure
    }

    const auto p_image_codec_info = static_cast<ImageCodecInfo*>(malloc(image_encoder_array_size));
    if (p_image_codec_info == nullptr)
    {
        return -1; // Failure
    }

    GetImageEncoders(image_encoders_count, image_encoder_array_size, p_image_codec_info);

    for (UINT image_encoder_index = 0; image_encoder_index < image_encoders_count; image_encoder_index++)
    {
        const auto image_codec_info = p_image_codec_info[image_encoder_index];
        const auto mime_type = image_codec_info.MimeType;
        const auto comparison_result = wcscmp(mime_type, format);
        if (comparison_result == 0)
        {
            *p_clsid = image_codec_info.Clsid;
            free(p_image_codec_info);
            return image_encoder_index; // Success
        }
    }

    free(p_image_codec_info);
    return -1; // Failure
}

inline WCHAR* get_image_format_from_filename(const WCHAR* filename)
{
    std::wstring wide_string_filename(filename);
    const std::string filename_string(wide_string_filename.begin(),
                                      wide_string_filename.end());
    const auto file_extension = get_file_extension(filename_string);
    std::stringstream image_format_buffer;
    image_format_buffer << "image/" << file_extension;
    const auto encoder_format = image_format_buffer.str();
    return to_wide_char(encoder_format.c_str());
}

inline bool save_image(const HBITMAP bitmap, const WCHAR* filename)
{
    GdiplusStartupInput gdiplus_startup_input;
    ULONG_PTR gdiplus_token;
    const auto startup_status = GdiplusStartup(&gdiplus_token, &gdiplus_startup_input, nullptr);
    if (startup_status != Gdiplus::Ok)
    {
        std::cout << "[ERROR] GdiplusStartup() failed: " << startup_status << std::endl;
        return false;
    }

    auto image = new Bitmap(bitmap, nullptr);

    CLSID my_cls_id;
    const auto format = get_image_format_from_filename(filename);
    const auto encoder_return_value = get_encoder(format, &my_cls_id);

    if (encoder_return_value == -1)
    {
        std::cout << "[ERROR] Encoder not available: ";
        std::wcout << format << std::endl;
        delete image;
        return false;
    }

    const auto image_saving_status = image->Save(filename, &my_cls_id, nullptr);
    if (image_saving_status != Gdiplus::Ok)
    {
        std::cout << "[ERROR] Saving image failed: " << startup_status << std::endl;
        delete image;
        return false;
    }

    delete image;
    GdiplusShutdown(gdiplus_token);

    return true;
}

inline bool perform_screen_shot(const HWND window_handle, HBITMAP& bitmap)
{
    RECT rectangle;
    const auto successful = GetClientRect(window_handle, &rectangle);
    if (!successful)
    {
        const auto last_error_message = get_last_error_as_string();
        std::cout << "[ERROR] Cannot get client rectangle: " << last_error_message << std::endl;
        exit(EXIT_FAILURE);
    }

    if (IsRectEmpty(&rectangle))
    {
        std::cout << "[ERROR] The client rectangle is empty: Maybe the window is minimized?" << std::endl;
        return false;
    }

    const auto hdc_screen = GetDC(nullptr);
    const auto hdc = CreateCompatibleDC(hdc_screen);
    bitmap = CreateCompatibleBitmap(hdc_screen,
                                    rectangle.right - rectangle.left,
                                    rectangle.bottom - rectangle.top);
    SelectObject(hdc, bitmap);
    const auto window_successfully_printed = PrintWindow(window_handle, hdc, PW_CLIENTONLY);
    if (!window_successfully_printed)
    {
        const auto last_error_message = get_last_error_as_string();
        std::cout << "[ERROR] Window not printed: " << last_error_message << std::endl;
        exit(EXIT_FAILURE);
    }

    return true;
}

On my machine ( Windows 10 Pro 64-bit ) this code always works perfectly. 在我的机器上( Windows 10 Pro 64-bit ),此代码始终完美无缺。 As you can see, the return values are validated so errors should be caught. 如您所见,返回值已经过验证,因此应该捕获错误。 However, another user on Windows 10 64-bit always gets a black screen image despite all the functions succeeding. 但是,尽管所有功能都成功,但Windows 10 64-bit上的另一个用户始终会获得黑屏图像。 Is there anything I'm not addressing correctly? 有什么我没有正确解决? How can this bug be fixed? 如何修复这个bug? I've tried using PNG and BMP encoders but same outcome (most likely because the HBITMAP was already black at that point). 我尝试过使用PNGBMP编码器但结果相同(很可能是因为此时HBITMAP已经是黑色)。

As commented by Remy Lebeau , PrintWindow() does not seem to be reliable since it directly captures the client part of a window, even if it isn't visible on the screen (eg it's behind another window). 正如Remy Lebeau评论的那样, PrintWindow()似乎不可靠,因为它直接捕获窗口的客户端部分,即使它在屏幕上不可见(例如它在另一个窗口后面)。 One thing which seems to work much better is to perform a conventional screenshot and restricting the area to the client area of the target window. 似乎工作得更好的一件事是执行传统的屏幕截图并将区域限制到目标窗口的客户区域。 This has the disadvantage that the window must be visible to be captured. 这样做的缺点是窗口必须是可见的才能被捕获。 The code for this is eg the following: 这个代码如下:

// Get the horizontal and vertical screen sizes in pixel
inline POINT get_window_resolution(const HWND window_handle)
{
    RECT rectangle;
    GetClientRect(window_handle, &rectangle);
    const POINT coordinates{rectangle.right, rectangle.bottom};
    return coordinates;
}

#include <iostream>
#include <ole2.h>
#include <olectl.h>

inline bool save_bitmap(const LPCSTR file_path,
                        const HBITMAP bitmap, const HPALETTE palette)
{
    PICTDESC pict_description;

    pict_description.cbSizeofstruct = sizeof(PICTDESC);
    pict_description.picType = PICTYPE_BITMAP;
    pict_description.bmp.hbitmap = bitmap;
    pict_description.bmp.hpal = palette;

    LPPICTURE picture;
    auto initial_result = OleCreatePictureIndirect(&pict_description, IID_IPicture, false,
                                                   reinterpret_cast<void**>(&picture));

    if (!SUCCEEDED(initial_result))
    {
        return false;
    }

    LPSTREAM stream;
    initial_result = CreateStreamOnHGlobal(nullptr, true, &stream);

    if (!SUCCEEDED(initial_result))
    {
        picture->Release();
        return false;
    }

    LONG bytes_streamed;
    initial_result = picture->SaveAsFile(stream, true, &bytes_streamed);

    const auto file = CreateFile(file_path, GENERIC_WRITE, FILE_SHARE_READ, nullptr,
                                 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);

    if (!SUCCEEDED(initial_result) || !file)
    {
        stream->Release();
        picture->Release();
        return false;
    }

    HGLOBAL mem = nullptr;
    GetHGlobalFromStream(stream, &mem);
    const auto data = GlobalLock(mem);

    DWORD bytes_written;
    auto result = WriteFile(file, data, bytes_streamed, &bytes_written, nullptr);
    result &= bytes_written == static_cast<DWORD>(bytes_streamed);

    GlobalUnlock(mem);
    CloseHandle(file);

    stream->Release();
    picture->Release();

    return result;
}

inline POINT get_client_window_position(const HWND window_handle)
{
    RECT rectangle;

    GetClientRect(window_handle, static_cast<LPRECT>(&rectangle));
    MapWindowPoints(window_handle, nullptr, reinterpret_cast<LPPOINT>(& rectangle), 2);

    const POINT coordinates = {rectangle.left, rectangle.top};

    return coordinates;
}

// https://stackoverflow.com/a/9525788/3764804
inline bool capture_screen_client_window(const HWND window_handle, const LPCSTR file_path)
{
    SetActiveWindow(window_handle);

    const auto hdc_source = GetDC(nullptr);
    const auto hdc_memory = CreateCompatibleDC(hdc_source);

    const auto window_resolution = get_window_resolution(window_handle);

    const auto width = window_resolution.x;
    const auto height = window_resolution.y;

    const auto client_window_position = get_client_window_position(window_handle);

    auto h_bitmap = CreateCompatibleBitmap(hdc_source, width, height);
    const auto h_bitmap_old = static_cast<HBITMAP>(SelectObject(hdc_memory, h_bitmap));

    BitBlt(hdc_memory, 0, 0, width, height, hdc_source, client_window_position.x, client_window_position.y, SRCCOPY);
    h_bitmap = static_cast<HBITMAP>(SelectObject(hdc_memory, h_bitmap_old));

    DeleteDC(hdc_source);
    DeleteDC(hdc_memory);

    const HPALETTE h_palette = nullptr;
    if (save_bitmap(file_path, h_bitmap, h_palette))
    {
        return true;
    }

    return false;
}

It is based on this answer. 它基于这个答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM