microsoft / Windows.UI.Composition-Win32-Samples

Windows.UI.Composition Win32 Samples
MIT License
469 stars 186 forks source link

Example cpp/ScreenCaptureforHWND - Function GetMessage(&Msg, NULL, 0, 0) must be called at the same area... #38

Closed gileli121 closed 5 years ago

gileli121 commented 5 years ago

Hello, after I remix the example cpp/ScreenCaptureforHWND to this code:

int CALLBACK WinMain(HINSTANCE instance, HINSTANCE previousInstance, LPSTR cmdLine, int cmdShow)
{

    HWND hwndTarget = (HWND)0x00000000008106D2; // HWND of the window to capture. Edit this as needed
    RECT rectTarget;

    HWND hwndDisplay;

    HRESULT res;

    // Init COM
    //init_apartment(apartment_type::single_threaded);

    // Create the window
    WNDCLASSEX wcex = {};
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = instance;
    wcex.hIcon = LoadIcon(instance, MAKEINTRESOURCE(IDI_APPLICATION));
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = L"ScreenCaptureforHWND";
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
    WINRT_VERIFY(RegisterClassEx(&wcex));

    HWND hwnd = CreateWindow(
        L"ScreenCaptureforHWND",
        L"ScreenCaptureforHWND",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        800,
        600,
        NULL,
        NULL,
        instance,
        NULL);
    WINRT_VERIFY(hwnd);

    ShowWindow(hwnd, SW_SHOW);

    // I don't know why it is but I need this section to make it work
    Windows::System::DispatcherQueueController controller{ nullptr };

    DispatcherQueueOptions options
    {
        sizeof(DispatcherQueueOptions),
        DQTYPE_THREAD_CURRENT,
        DQTAT_COM_STA
    };

    res = CreateDispatcherQueueController(options, reinterpret_cast<ABI::Windows::System::IDispatcherQueueController * *>(put_abi(controller)));
    if (res != S_OK)
        return -1;

    //using namespace Windows::Foundation;
    //using namespace Windows::UI;
    //using namespace Windows::UI::Composition;
    //using namespace Windows::Graphics::Capture;

    // Initialize Composition
    auto compositor = Compositor();

    auto interop = compositor.as<ABI::Windows::UI::Composition::Desktop::ICompositorDesktopInterop>();
    DesktopWindowTarget desktopWindowTarget{ nullptr };
    res = interop->CreateDesktopWindowTarget(hwnd, true, reinterpret_cast<ABI::Windows::UI::Composition::Desktop::IDesktopWindowTarget * *>(put_abi(desktopWindowTarget)));
    if (res != S_OK)
        return -1;

    auto containerRoot = compositor.CreateContainerVisual();
    containerRoot.RelativeSizeAdjustment({ 1.0f, 1.0f });
    //root.Size({ 300,300 });
    desktopWindowTarget.Root(containerRoot);

    winrt::Windows::UI::Composition::SpriteVisual m_renderElement = compositor.CreateSpriteVisual();

    containerRoot.RelativeSizeAdjustment({ 1, 1 });

    m_renderElement.AnchorPoint({ 0.5f, 0.5f });
    m_renderElement.RelativeOffsetAdjustment({ 0.5f, 0.5f, 0 });
    m_renderElement.RelativeSizeAdjustment({ 1, 1 });

    // Create brush for the render element
    auto m_brush = compositor.CreateSurfaceBrush();
    m_renderElement.Brush(m_brush);
    m_brush.HorizontalAlignmentRatio(0.5f);
    m_brush.VerticalAlignmentRatio(0.5f);
    m_brush.Stretch(CompositionStretch::Uniform);

    // Add the render element to the container root
    containerRoot.Children().InsertAtTop(m_renderElement);

    auto d3dDevice = CreateD3DDevice();
    auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
    winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice m_device = CreateDirect3DDevice(dxgiDevice.get());

    auto item = CreateCaptureItemForWindow(hwndTarget);

    std::unique_ptr<SimpleCapture> m_capture{ nullptr };
    m_capture = std::make_unique<SimpleCapture>(m_device, item);

    auto surface = m_capture->CreateSurface(compositor);
    m_brush.Surface(surface);

    m_capture->StartCapture();

    // Message pump
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

I found something very strange.

If you change the code to this:

class classTest
{
public:

    int Test(HINSTANCE instance)
    {
        HWND hwndTarget = (HWND)0x00000000008106D2;
        RECT rectTarget;

        HWND hwndDisplay;

        HRESULT res;

        // Init COM
        //init_apartment(apartment_type::single_threaded);

        // Create the window
        WNDCLASSEX wcex = {};
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = WndProc;
        wcex.cbClsExtra = 0;
        wcex.cbWndExtra = 0;
        wcex.hInstance = instance;
        wcex.hIcon = LoadIcon(instance, MAKEINTRESOURCE(IDI_APPLICATION));
        wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wcex.lpszMenuName = NULL;
        wcex.lpszClassName = L"ScreenCaptureforHWND";
        wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
        WINRT_VERIFY(RegisterClassEx(&wcex));

        HWND hwnd = CreateWindow(
            L"ScreenCaptureforHWND",
            L"ScreenCaptureforHWND",
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            800,
            600,
            NULL,
            NULL,
            instance,
            NULL);
        WINRT_VERIFY(hwnd);

        ShowWindow(hwnd, SW_SHOW);

        // I don't know why it is but I need this section to make it work
        Windows::System::DispatcherQueueController controller{ nullptr };

        DispatcherQueueOptions options
        {
            sizeof(DispatcherQueueOptions),
            DQTYPE_THREAD_CURRENT,
            DQTAT_COM_STA
        };

        res = CreateDispatcherQueueController(options, reinterpret_cast<ABI::Windows::System::IDispatcherQueueController * *>(put_abi(controller)));
        if (res != S_OK)
            return -1;

        //using namespace Windows::Foundation;
        //using namespace Windows::UI;
        //using namespace Windows::UI::Composition;
        //using namespace Windows::Graphics::Capture;

        // Initialize Composition
        auto compositor = Compositor();

        auto interop = compositor.as<ABI::Windows::UI::Composition::Desktop::ICompositorDesktopInterop>();
        DesktopWindowTarget desktopWindowTarget{ nullptr };
        res = interop->CreateDesktopWindowTarget(hwnd, true, reinterpret_cast<ABI::Windows::UI::Composition::Desktop::IDesktopWindowTarget * *>(put_abi(desktopWindowTarget)));
        if (res != S_OK)
            return -1;

        auto containerRoot = compositor.CreateContainerVisual();
        containerRoot.RelativeSizeAdjustment({ 1.0f, 1.0f });
        //root.Size({ 300,300 });
        desktopWindowTarget.Root(containerRoot);

        winrt::Windows::UI::Composition::SpriteVisual m_renderElement = compositor.CreateSpriteVisual();

        containerRoot.RelativeSizeAdjustment({ 1, 1 });

        m_renderElement.AnchorPoint({ 0.5f, 0.5f });
        m_renderElement.RelativeOffsetAdjustment({ 0.5f, 0.5f, 0 });
        m_renderElement.RelativeSizeAdjustment({ 1, 1 });

        // Create brush for the render element
        auto m_brush = compositor.CreateSurfaceBrush();
        m_renderElement.Brush(m_brush);
        m_brush.HorizontalAlignmentRatio(0.5f);
        m_brush.VerticalAlignmentRatio(0.5f);
        m_brush.Stretch(CompositionStretch::Uniform);

        // Add the render element to the container root
        containerRoot.Children().InsertAtTop(m_renderElement);

        auto d3dDevice = CreateD3DDevice();
        auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
        winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice m_device = CreateDirect3DDevice(dxgiDevice.get());

        auto item = CreateCaptureItemForWindow(hwndTarget);

        std::unique_ptr<SimpleCapture> m_capture{ nullptr };
        m_capture = std::make_unique<SimpleCapture>(m_device, item);

        auto surface = m_capture->CreateSurface(compositor);
        m_brush.Surface(surface);

        m_capture->StartCapture();

    }

};

int CALLBACK WinMain(HINSTANCE instance, HINSTANCE previousInstance, LPSTR cmdLine, int cmdShow)
{

    classTest test;

    test.Test(instance);

    // Message pump
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

Then the window will not draw the screenshot of the target window to capture. The only difference here is that the while loop with the GetMessage is out of the Test function inside the class.

Now, If I move the while loop to the end of the Test function, it will work. I have no idea why it is that..

It is blocking me from using the API.

Please help me to solve this problem. I want that the message loop will be outside of the class while it still draw the window screenshot.

Thanks

robmikh commented 5 years ago

Do you have a repo I can clone? I'm unsure what other changes you've made. For example, I'm not sure if you're calling init_apartment, the only code you've shown has it commented out and that's a big red flag.

gileli121 commented 5 years ago

Hi, This time I changed only the main.cpp file to this:

//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 
// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//*********************************************************

#include "pch.h"
#include "App.h"
#include "SimpleCapture.h"
#include <ShObjIdl.h>
#include "Win32WindowEnumeration.h"

using namespace winrt;
using namespace Windows::UI;
using namespace Windows::UI::Composition;
using namespace Windows::UI::Composition::Desktop;

// Direct3D11CaptureFramePool requires a DispatcherQueue
auto CreateDispatcherQueueController()
{
    namespace abi = ABI::Windows::System;

    DispatcherQueueOptions options
    {
        sizeof(DispatcherQueueOptions),
        DQTYPE_THREAD_CURRENT,
        DQTAT_COM_STA
    };

    Windows::System::DispatcherQueueController controller{ nullptr };
    check_hresult(CreateDispatcherQueueController(options, reinterpret_cast<abi::IDispatcherQueueController**>(put_abi(controller))));
    return controller;
}

DesktopWindowTarget CreateDesktopWindowTarget(Compositor const& compositor, HWND window)
{
    namespace abi = ABI::Windows::UI::Composition::Desktop;

    auto interop = compositor.as<abi::ICompositorDesktopInterop>();
    DesktopWindowTarget target{ nullptr };
    check_hresult(interop->CreateDesktopWindowTarget(window, true, reinterpret_cast<abi::IDesktopWindowTarget**>(put_abi(target))));
    return target;
}

int CALLBACK WinMain(
    HINSTANCE instance,
    HINSTANCE previousInstance,
    LPSTR     cmdLine,
    int       cmdShow);

auto g_app = std::make_shared<App>();
auto g_windows = EnumerateWindows();

LRESULT CALLBACK WndProc(
    HWND   hwnd,
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam);

class TestClass
{
public:
    void Test(HINSTANCE   instance)
    {
        // Init COM
        init_apartment(apartment_type::single_threaded);

        // Create the window
        WNDCLASSEX wcex = {};
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = WndProc;
        wcex.cbClsExtra = 0;
        wcex.cbWndExtra = 0;
        wcex.hInstance = instance;
        wcex.hIcon = LoadIcon(instance, MAKEINTRESOURCE(IDI_APPLICATION));
        wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wcex.lpszMenuName = NULL;
        wcex.lpszClassName = L"ScreenCaptureforHWND";
        wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
        WINRT_VERIFY(RegisterClassEx(&wcex));

        HWND hwnd = CreateWindow(
            L"ScreenCaptureforHWND",
            L"ScreenCaptureforHWND",
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            800,
            600,
            NULL,
            NULL,
            instance,
            NULL);
        WINRT_VERIFY(hwnd);

        ShowWindow(hwnd, SW_SHOW);
        UpdateWindow(hwnd);

        // Create combo box
        HWND comboBoxHwnd = CreateWindow(
            WC_COMBOBOX,
            L"",
            CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_VSCROLL | WS_CHILD | WS_OVERLAPPED | WS_VISIBLE,
            10,
            10,
            200,
            200,
            hwnd,
            NULL,
            instance,
            NULL);
        WINRT_VERIFY(comboBoxHwnd);

        // Populate combo box
        for (auto& window : g_windows)
        {
            SendMessage(comboBoxHwnd, CB_ADDSTRING, 0, (LPARAM)window.Title().c_str());
        }
        //SendMessage(comboBoxHwnd, CB_SETCURSEL, 0, 0);

        // Create a DispatcherQueue for our thread
        auto controller = CreateDispatcherQueueController();

        // Initialize Composition
        auto compositor = Compositor();
        auto target = CreateDesktopWindowTarget(compositor, hwnd);
        auto root = compositor.CreateContainerVisual();
        root.RelativeSizeAdjustment({ 1.0f, 1.0f });
        target.Root(root);

        // Enqueue our capture work on the dispatcher
        auto queue = controller.DispatcherQueue();
        auto success = queue.TryEnqueue([=]() -> void
        {
            g_app->Initialize(root);
        });
        WINRT_VERIFY(success);
    }
};

int CALLBACK WinMain(
    HINSTANCE instance,
    HINSTANCE previousInstance,
    LPSTR     cmdLine,
    int       cmdShow)
{
    TestClass test;
    test.Test(instance);

    // Message pump
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(
    HWND   hwnd,
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_COMMAND:
        if (HIWORD(wParam) == CBN_SELCHANGE)
        {
            auto index = SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0);
            auto window = g_windows[index];
            g_app->StartCapture(window.Hwnd());
        }
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
        break;
    }

    return 0;
}

My goal is that it will work while the message loop will be outside. It doesn't. This time, it throws exception and the program crashed here: image

I think that it is the same problem.. just this time I did not removed code that throws exception and stop the execution.

Notice that it will work in one of the following ways:

  1. If I move the code

    // Message pump
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    To the end of the Test function in TestClass

  2. If I move all the code in Test function to WinMain function so it will be before the message loop.

I don't know why it is that.. what make it fail.. it is very strage. My desing in my software is that some logic must be in class. The logic that creates the UI will be in class. something like this And the message loop will be outside the class - in the WinMain function.

I can't continue to work with the API unless the problem will solved. I tried a lot to figure out what the problem is and the only thing I found is that the message loop code should be in the same code block that create the UI.

Please help me to find the solution.

Thanks.

gileli121 commented 5 years ago

In addition the previeus message, I could reproduce the exact same problem if you change in the last changed code I post here the lines:

        // Enqueue our capture work on the dispatcher
        auto queue = controller.DispatcherQueue();
        auto success = queue.TryEnqueue([=]() -> void
        {
            g_app->Initialize(root);
        });
        WINRT_VERIFY(success);

to

        // Enqueue our capture work on the dispatcher
        auto queue = controller.DispatcherQueue();
        g_app->Initialize(root);
        //WINRT_VERIFY(success);

Next, you will see that if you don't add code

    // Message pump
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

Right after this (and this UI code is inside function in class), and that message loop code is written only in WinMain function after test.Test() call, then it will not draw the capture. This is what I got in my original example (not an exception). And it will draw the capture if you add these lines right after it

gileli121 commented 5 years ago

I found an ugly workaround - I use std:: thread with lamda...

Inside TestClass, inside Test function, I warp the ui code with

std::thread uiThread([&]
{
    // UI code

    // Message loop
    MSG msg;
    while (!killThread && GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}
});

uiThread.detach();

In WinMain function, I also write message loop that will be this:

    // Message pump
    MSG msg;
    while (1)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) //Or use an if statement
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

Here is the full code

//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 
// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//*********************************************************

#include "pch.h"
#include "App.h"
#include "SimpleCapture.h"
#include <ShObjIdl.h>
#include "Win32WindowEnumeration.h"

using namespace winrt;
using namespace Windows::UI;
using namespace Windows::UI::Composition;
using namespace Windows::UI::Composition::Desktop;

// Direct3D11CaptureFramePool requires a DispatcherQueue
auto CreateDispatcherQueueController()
{
    namespace abi = ABI::Windows::System;

    DispatcherQueueOptions options
    {
        sizeof(DispatcherQueueOptions),
        DQTYPE_THREAD_CURRENT,
        DQTAT_COM_STA
    };

    Windows::System::DispatcherQueueController controller{ nullptr };
    check_hresult(CreateDispatcherQueueController(options, reinterpret_cast<abi::IDispatcherQueueController**>(put_abi(controller))));
    return controller;
}

DesktopWindowTarget CreateDesktopWindowTarget(Compositor const& compositor, HWND window)
{
    namespace abi = ABI::Windows::UI::Composition::Desktop;

    auto interop = compositor.as<abi::ICompositorDesktopInterop>();
    DesktopWindowTarget target{ nullptr };
    check_hresult(interop->CreateDesktopWindowTarget(window, true, reinterpret_cast<abi::IDesktopWindowTarget**>(put_abi(target))));
    return target;
}

int CALLBACK WinMain(
    HINSTANCE instance,
    HINSTANCE previousInstance,
    LPSTR     cmdLine,
    int       cmdShow);

auto g_app = std::make_shared<App>();
auto g_windows = EnumerateWindows();

LRESULT CALLBACK WndProc(
    HWND   hwnd,
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam);

class TestClass
{
public:

    std::atomic<bool> killThread = false;

    void Test(HINSTANCE   instance)
    {
        std::thread uiThread([&]
        {

        // Init COM
        init_apartment(apartment_type::single_threaded);

        // Create the window
        WNDCLASSEX wcex = {};
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = WndProc;
        wcex.cbClsExtra = 0;
        wcex.cbWndExtra = 0;
        wcex.hInstance = instance;
        wcex.hIcon = LoadIcon(instance, MAKEINTRESOURCE(IDI_APPLICATION));
        wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wcex.lpszMenuName = NULL;
        wcex.lpszClassName = L"ScreenCaptureforHWND";
        wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
        WINRT_VERIFY(RegisterClassEx(&wcex));

        HWND hwnd = CreateWindow(
            L"ScreenCaptureforHWND",
            L"ScreenCaptureforHWND",
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            800,
            600,
            NULL,
            NULL,
            instance,
            NULL);
        WINRT_VERIFY(hwnd);

        ShowWindow(hwnd, SW_SHOW);
        UpdateWindow(hwnd);

        // Create combo box
        HWND comboBoxHwnd = CreateWindow(
            WC_COMBOBOX,
            L"",
            CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_VSCROLL | WS_CHILD | WS_OVERLAPPED | WS_VISIBLE,
            10,
            10,
            200,
            200,
            hwnd,
            NULL,
            instance,
            NULL);
        WINRT_VERIFY(comboBoxHwnd);

        // Populate combo box
        for (auto& window : g_windows)
        {
            SendMessage(comboBoxHwnd, CB_ADDSTRING, 0, (LPARAM)window.Title().c_str());
        }
        //SendMessage(comboBoxHwnd, CB_SETCURSEL, 0, 0);

        // Create a DispatcherQueue for our thread
        auto controller = CreateDispatcherQueueController();

        // Initialize Composition
        auto compositor = Compositor();
        auto target = CreateDesktopWindowTarget(compositor, hwnd);
        auto root = compositor.CreateContainerVisual();
        root.RelativeSizeAdjustment({ 1.0f, 1.0f });
        target.Root(root);

        // Enqueue our capture work on the dispatcher
        auto queue = controller.DispatcherQueue();
        g_app->Initialize(root);
        //WINRT_VERIFY(success);

            MSG msg;
            while (!killThread && GetMessage(&msg, NULL, 0, 0))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }

        });

        uiThread.detach();

    }
};

int CALLBACK WinMain(
    HINSTANCE instance,
    HINSTANCE previousInstance,
    LPSTR     cmdLine,
    int       cmdShow)
{

    // Init COM
    init_apartment(apartment_type::single_threaded);

    TestClass test;
    test.Test(instance);

    // Message pump
    MSG msg;
    while (1)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) //Or use an if statement
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)0;
}

LRESULT CALLBACK WndProc(
    HWND   hwnd,
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_COMMAND:
        if (HIWORD(wParam) == CBN_SELCHANGE)
        {
            auto index = SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0);
            auto window = g_windows[index];
            g_app->StartCapture(window.Hwnd());
        }
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
        break;
    }

    return 0;
}

The question is, is this is the best solution? I don't know

robmikh commented 5 years ago

Couple things:

As it stands, the code blows up with RO_E_CLOSED, which means the object has already been cleaned up. It appears you aren't keeping any of the objects you're creating in the Test method alive. Make them members of the class. Think of these WinRT objects as smart pointers, they'll get cleaned up when they go out of scope and they won't leak unless you mess with the ref count.

After I made those changes it all worked:

//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 
// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//*********************************************************

#include "pch.h"
#include "App.h"
#include "SimpleCapture.h"
#include <ShObjIdl.h>
#include "Win32WindowEnumeration.h"

using namespace winrt;
using namespace Windows::UI;
using namespace Windows::UI::Composition;
using namespace Windows::UI::Composition::Desktop;

// Direct3D11CaptureFramePool requires a DispatcherQueue
auto CreateDispatcherQueueController()
{
    namespace abi = ABI::Windows::System;

    DispatcherQueueOptions options
    {
        sizeof(DispatcherQueueOptions),
        DQTYPE_THREAD_CURRENT,
        DQTAT_COM_STA
    };

    Windows::System::DispatcherQueueController controller{ nullptr };
    check_hresult(CreateDispatcherQueueController(options, reinterpret_cast<abi::IDispatcherQueueController * *>(put_abi(controller))));
    return controller;
}

DesktopWindowTarget CreateDesktopWindowTarget(Compositor const& compositor, HWND window)
{
    namespace abi = ABI::Windows::UI::Composition::Desktop;

    auto interop = compositor.as<abi::ICompositorDesktopInterop>();
    DesktopWindowTarget target{ nullptr };
    check_hresult(interop->CreateDesktopWindowTarget(window, true, reinterpret_cast<abi::IDesktopWindowTarget * *>(put_abi(target))));
    return target;
}

int CALLBACK WinMain(
    HINSTANCE instance,
    HINSTANCE previousInstance,
    LPSTR     cmdLine,
    int       cmdShow);

auto g_app = std::make_shared<App>();
auto g_windows = EnumerateWindows();

LRESULT CALLBACK WndProc(
    HWND   hwnd,
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam);

class TestClass
{
public:
    void Test(HINSTANCE   instance)
    {
        // Create the window
        WNDCLASSEX wcex = {};
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = WndProc;
        wcex.cbClsExtra = 0;
        wcex.cbWndExtra = 0;
        wcex.hInstance = instance;
        wcex.hIcon = LoadIcon(instance, MAKEINTRESOURCE(IDI_APPLICATION));
        wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wcex.lpszMenuName = NULL;
        wcex.lpszClassName = L"ScreenCaptureforHWND";
        wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
        WINRT_VERIFY(RegisterClassEx(&wcex));

        HWND hwnd = CreateWindow(
            L"ScreenCaptureforHWND",
            L"ScreenCaptureforHWND",
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            800,
            600,
            NULL,
            NULL,
            instance,
            NULL);
        WINRT_VERIFY(hwnd);

        ShowWindow(hwnd, SW_SHOW);
        UpdateWindow(hwnd);

        // Create combo box
        HWND comboBoxHwnd = CreateWindow(
            WC_COMBOBOX,
            L"",
            CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_VSCROLL | WS_CHILD | WS_OVERLAPPED | WS_VISIBLE,
            10,
            10,
            200,
            200,
            hwnd,
            NULL,
            instance,
            NULL);
        WINRT_VERIFY(comboBoxHwnd);

        // Populate combo box
        for (auto& window : g_windows)
        {
            SendMessage(comboBoxHwnd, CB_ADDSTRING, 0, (LPARAM)window.Title().c_str());
        }

        // Initialize Composition
        m_compositor = Compositor();
        m_target = CreateDesktopWindowTarget(m_compositor, hwnd);
        m_root = m_compositor.CreateContainerVisual();
        m_root.RelativeSizeAdjustment({ 1.0f, 1.0f });
        m_target.Root(m_root);

        g_app->Initialize(m_root);
    }
private:
    Compositor m_compositor{ nullptr };
    CompositionTarget m_target{ nullptr };
    ContainerVisual m_root{ nullptr };
};

int CALLBACK WinMain(
    HINSTANCE instance,
    HINSTANCE previousInstance,
    LPSTR     cmdLine,
    int       cmdShow)
{
    // Init COM
    init_apartment(apartment_type::single_threaded);

    // Create a DispatcherQueue for our thread
    auto controller = CreateDispatcherQueueController();

    TestClass test;
    test.Test(instance);

    // Message pump
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(
    HWND   hwnd,
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_COMMAND:
        if (HIWORD(wParam) == CBN_SELCHANGE)
        {
            auto index = SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0);
            auto window = g_windows[index];
            g_app->StartCapture(window.Hwnd());
        }
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
        break;
    }

    return 0;
}
gileli121 commented 5 years ago

WOW, I did what you said and now my code works! I tried to solve it so hard.

Thank you very much!

One more question:

What does the following lines doing:

// Init COM
    init_apartment(apartment_type::single_threaded);

    // Create a DispatcherQueue for our thread
    auto controller = CreateDispatcherQueueController();

Thanks.

gileli121 commented 5 years ago

Hi again, I just found a bug that if you create a WS_POPUP window that is positioned exactly at the location of the window to capture, and the window will display the captured window you will get the following bug:

bug

To reproduce the bug you need: 1) Create WS_POPUP window with the RECT rect that you get by calling to

RECT rect;
DwmGetWindowAttribute(hwndCaptureWindow, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(RECT));

Then use rect to create the window at the exact location of the window you capture that is hwndCaptureWindow

2) Enable for the window you created the capture using the new API so that the window you created will display hwndCaptureWindow.

This will happen when the window is positioned exactly where hwndCaptureWindow positioned.

As you can see in the gif, when I move the window it will not happen. It will also not happen if I don't create the window exactly where the hwndCaptureWindow is positioned

robmikh commented 5 years ago

No problem!

init_apartment is really a wrapper around RoInitialize, which is required to be called in order to use Windows Runtime objects (per thread). You may be able to get things to work without it, but you'll see all sorts of lifetime bugs. UWP applications do this automatically for you, but Win32 applications have to do this explicitly.

The DispatcherQueueController is needed because the Direct3D11CaptureFramePool uses the DispatcherQueue to do callbacks. This way you're always called back on the thread that created the frame pool. It's also required to use any of the Windows.UI.Composition APIs.

If you didn't want to take the DispatcherQueue dependency, you can create your frame pool using CreateFreeThreaded instead of Create. This will remove the requirement of a DispatcherQueue, but it will also mean you will be responsible for threading. The OnFrameArrived callback will fire on an arbitrary thread, not the thread you created the frame pool on, when you create it this way. However you wouldn't be able to use any of the Composition APIs to present your content without a dispatcher queue anyway.

I'll take a look to see if I reproduce the bug you seem to have found. Does it only happen when the window you create is styled with WS_POPUP?

robmikh commented 5 years ago

What build of the OS are you running? I'm unable to reproduce this bug on 18362.239 (Go to Settings -> System -> About -> Windows specifications -> OS build).

gileli121 commented 5 years ago

My version is 1903. Maybe I should open it as another issue. It does not matter. I found a workaround... Please open another issue here and I will respond there.

I am looking for a way to draw the captured image with Alpha channel.

Currently, I know how to do it with BitBlt function. But BitBlt is slow.

My setup is that I create WS_POPUP window with hbrBackground = (HBRUSH)CreateSolidBrush(RGB(121, 122, 123));

This is the full window class:

        //Step 1: Registering the Window Class
        wndclassex.cbSize = sizeof(WNDCLASSEX);
        wndclassex.style = 0;
        wndclassex.lpfnWndProc = WndProc;
        wndclassex.cbClsExtra = 0;
        wndclassex.cbWndExtra = 0;
        wndclassex.hInstance = hInstance;
        wndclassex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wndclassex.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndclassex.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(121, 122, 123));

        wndclassex.lpszMenuName = NULL;
        wndclassex.lpszClassName = ccharClassName;
        wndclassex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

        if (!RegisterClassEx(&wndclassex)) return -1;

Next, I create WS_POPUP window called 'hwndDisplay' and set it to be with this style:

            SetWindowLong(hwndDisplay, GWL_EXSTYLE, GetWindowLong(hwndDisplay, GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT);
            SetLayeredWindowAttributes(hwndDisplay, RGB(121, 122, 123), 0, LWA_COLORKEY);

Then I use 'BitBlt' function that draws a processed frame. Each pixel with the color of (121,122,123) will be transparent (in a way that you see what behind the window). This is how it works.

But this trick does not work with hardware drawing as in the example. Do you know how to do it?

I tried to modify each pixel that is at position 4...

for (int point = 0; point < xSize * ySize * 4; point += 4)
{
   bits[point+4] = 128; // Edit the alpha channel 
}

I expected that

m_swapChain->Present1(1, 0, &presentParameters);

will not ignore the alpha channel. but it did not worked..

bits is what I get from by using the code:

                // First verify that we can map the texture
                D3D11_TEXTURE2D_DESC desc;
                backBuffer->GetDesc(&desc);

                // translate texture format to WIC format. We support only BGRA and ARGB.
                GUID wicFormatGuid;
                switch (desc.Format) {
                case DXGI_FORMAT_R8G8B8A8_UNORM:
                    wicFormatGuid = GUID_WICPixelFormat32bppRGBA;
                    break;
                case DXGI_FORMAT_B8G8R8A8_UNORM:
                    wicFormatGuid = GUID_WICPixelFormat32bppBGRA;
                    break;
                default:
                    return;
                }

                // Get the device context
                ID3D11Device* d3dDevice;
                backBuffer->GetDevice(&d3dDevice);
                ID3D11DeviceContext* d3dContext;
                d3dDevice->GetImmediateContext(&d3dContext);

                // Create new textrue with CPU Access right
                ID3D11Texture2D* texTemp = NULL;
                desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ;
                desc.Usage = D3D11_USAGE_STAGING;
                desc.BindFlags = 0;
                HRESULT hr2 = d3dDevice->CreateTexture2D(
                    &desc,
                    NULL,
                    &texTemp
                );
                if (hr2 != S_OK)
                    return;

                m_d3dContext->CopyResource(texTemp, frameSurface.get());

                // map the texture
                ID3D11Texture2D* mappedTexture;
                D3D11_MAPPED_SUBRESOURCE mapInfo;
                ZeroMemory(&mapInfo, sizeof(D3D11_MAPPED_SUBRESOURCE));

                HRESULT hr = d3dContext->Map(texTemp, 0, D3D11_MAP_READ,0,&mapInfo);

bits is stored in mapInfo.pData; I am doing this:

byte* bits= (byte*)mapInfo.pData;

Next I try to do some processing on it and copy it back to backBuffer using m_d3dContext->CopyResource(backBuffer, texTemp);

I know that I am doing it right because I saw edited pixels on the screen. So pixels alpha 128 must be exists.. It just that the device ignoring it.

I want that it will not ignore this value.

Do you know how to solve it?

Thanks.

robmikh commented 5 years ago

Unfortunately, I’m unsure how to solve your transparency issue. I’m going to close this issue since the original issue has been addressed. Best of luck.

gileli121 commented 5 years ago

I solved the transparency issue. You need to change the code that set m_swapChain variable to this:


captureItem = CreateCaptureItemForWindow(hwndTarget);

d3dDevice = CreateD3DDevice();
dxgiDevice = d3dDevice.as<IDXGIDevice>();
device = CreateDirect3DDevice(dxgiDevice.get());

IDXGIAdapter* dxgi_adapter;
dxgiDevice->GetAdapter(&dxgi_adapter);
IDXGIFactory2* dxgi_factory;
dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory));

RECT rectLayer;
DwmGetWindowAttribute(hwndDisplay, DWMWA_EXTENDED_FRAME_BOUNDS, &rectLayer, sizeof(RECT));

DXGI_SWAP_CHAIN_DESC1 desc = {};
desc.Width = rectLayer.right- rectLayer.left;
desc.Height = rectLayer.bottom - rectLayer.top;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.Stereo = FALSE;
desc.SampleDesc.Count = 1;
desc.BufferCount = 2;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.Scaling = DXGI_SCALING_STRETCH;
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
desc.Flags = 0;

HRESULT hr = dxgi_factory->CreateSwapChainForComposition(
    d3dDevice.get(), &desc, nullptr, m_swapChain.put());

I found the reference here: https://chromium.googlesource.com/chromium/src.git/+/62.0.3178.1/gpu/ipc/service/direct_composition_child_surface_win.cc

I don't know what this code does but I found the pattern I was looking for and I connected the dots, edited the example again and it works!

Thanks anyway