Windows C++基于DWM截图

Home / C++ MrLee 1天前 10

#include <windows.h>
#include <unknwn.h> // 必须在 WinRT 之前包含,处理 IUnknown 冲突
#include <d3d11.h>
#include <dxgi1_2.h>
#include <wrl/client.h>
#include <iostream>
#include <vector>
#include <future>
#include <Inspectable.h> // 必须包含这个处理基础接口
#include <fstream>
#include <iostream>
// C++/WinRT 头文件
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Graphics.Capture.h>
#include <winrt/Windows.Graphics.DirectX.Direct3D11.h>
#include <windows.graphics.capture.interop.h>
#include <windows.graphics.directx.direct3d11.interop.h>
#pragma comment(lib, "windowsapp.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
// 明确指定命名空间,避免 "Windows" 冲突
namespace winrt_cap = winrt::Windows::Graphics::Capture;
namespace winrt_d3d = winrt::Windows::Graphics::DirectX::Direct3D11;
using namespace Microsoft::WRL;
class VulkanWindowCapturer {
public:
    VulkanWindowCapturer() {
        // 1. 初始化 D3D11 设备
        UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
        HRESULT hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags,
            nullptr, 0, D3D11_SDK_VERSION, &m_d3dDevice, nullptr, &m_d3dContext);
        if (FAILED(hr)) throw std::runtime_error("D3D11 Device Creation Failed");
        // 2. 将 D3D11 设备转换为 WinRT 设备
        ComPtr<IDXGIDevice> dxgiDevice;
        m_d3dDevice.As(&dxgiDevice);
        winrt::com_ptr<::IInspectable> inspectable;
        // 使用全局作用域限定符调用 API
        hr = ::CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.Get(), inspectable.put());
        if (FAILED(hr)) throw std::runtime_error("WinRT Device Bridge Failed");
        m_winrtDevice = inspectable.as<winrt_d3d::IDirect3DDevice>();
    }
    bool Capture(HWND hwnd) {
        if (!IsWindow(hwnd)) return false;
        auto factory = winrt::get_activation_factory<winrt_cap::GraphicsCaptureItem, IGraphicsCaptureItemInterop>();
        winrt_cap::GraphicsCaptureItem item{ nullptr };
        factory->CreateForWindow(hwnd, winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(), winrt::put_abi(item));
        auto size = item.Size();
        // 使用 promise 来同步异步回调
        auto isProcessed = std::make_shared<std::atomic<bool>>(false);
        auto framePromise = std::make_shared<std::promise<cv::Mat>>();
        auto frameFuture = framePromise->get_future();
        auto framePool = winrt_cap::Direct3D11CaptureFramePool::CreateFreeThreaded(
            m_winrtDevice,
            winrt::Windows::Graphics::DirectX::DirectXPixelFormat::R8G8B8A8UIntNormalized,
            1,
            size);
        auto session = framePool.CreateCaptureSession(item);
        auto token = framePool.FrameArrived([&, isProcessed, framePromise, session](winrt_cap::Direct3D11CaptureFramePool const& sender, auto const&) {
            // 如果已经处理过了,直接返回
            if (isProcessed->load()) return;
            auto frame = sender.TryGetNextFrame();
            if (!frame) return;
            // 标记已处理,防止并发进入
            isProcessed->store(true);
            // 获取纹理
            auto frameSurface = frame.Surface();
            auto access = frameSurface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>();
            ComPtr<ID3D11Texture2D> frameTexture;
            access->GetInterface(IID_PPV_ARGS(&frameTexture));
            // 创建并拷贝到 Staging Texture
            D3D11_TEXTURE2D_DESC desc;
            frameTexture->GetDesc(&desc);
            desc.Usage = D3D11_USAGE_STAGING;
            desc.BindFlags = 0;
            desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
            desc.MiscFlags = 0;
            ComPtr<ID3D11Texture2D> stagingTexture;
            m_d3dDevice->CreateTexture2D(&desc, nullptr, &stagingTexture);
            // 重要:将 GPU 数据同步到 Staging 纹理
            m_d3dContext->CopyResource(stagingTexture.Get(), frameTexture.Get());
            D3D11_MAPPED_SUBRESOURCE mapped;
            if (SUCCEEDED(m_d3dContext->Map(stagingTexture.Get(), 0, D3D11_MAP_READ, 0, &mapped))) {
                // 保存到 OpenCV Mat
                cv::Mat raw(size.Height, size.Width, CV_8UC4, mapped.pData, mapped.RowPitch);
                cv::Mat finalImg;
                cv::cvtColor(raw, finalImg, cv::COLOR_RGBA2BGR); // 纠正偏红问题
                framePromise->set_value(finalImg.clone()); // 传出数据
                m_d3dContext->Unmap(stagingTexture.Get(), 0);
            }
         });
        session.StartCapture();
        // 等待结果
        if (frameFuture.wait_for(std::chrono::seconds(2)) == std::future_status::ready) {
            cv::Mat result = frameFuture.get();
            cv::imshow("output.png", result); cv::waitKey();
            // --- 安全退出的关键 ---
            // 1. 先取消事件订阅
            framePool.FrameArrived(token);
            // 2. 退出作用域或显式让 session 变量失效即可,不必强行 Close()
            // 或者在外部调用:
            session.Close();
        }
        return true;
    }
private:
    ComPtr<ID3D11Device> m_d3dDevice;
    ComPtr<ID3D11DeviceContext> m_d3dContext;
    winrt_d3d::IDirect3DDevice m_winrtDevice{ nullptr };
};
int main() {
    // 显式调用 winrt 命名空间
    winrt::init_apartment(winrt::apartment_type::multi_threaded);
    HWND targetHwnd = FindWindowW(NULL, L"MuMu安卓设备");
    if (!targetHwnd) {
        std::cout << "Window not found!" << std::endl;
        return -1;
    }
    try {
        VulkanWindowCapturer capturer;
        capturer.Capture(targetHwnd);
    }
    catch (const std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }
    return 0;
}

以上方法,虽然可以抓图,但是底层并未将所有图像数据绘制超出部分。目前临时解决方法,强制刷新

void SuperForceRefresh(HWND m_hWnd) {
    if (!IsWindow(m_hWnd)) return;
    WINDOWPLACEMENT wp;
    wp.length = sizeof(WINDOWPLACEMENT);
    GetWindowPlacement(m_hWnd, &wp);
    wp.showCmd = SW_SHOWMINIMIZED;
    SetWindowPlacement(m_hWnd, &wp);
    wp.showCmd = SW_SHOWDEFAULT;
    SetWindowPlacement(m_hWnd, &wp);
}

后续再研究。。。

本文链接:https://it72.com/12802.htm

推荐阅读
最新回复 (0)
返回