COM-összetevők felhasználása C++/WinRT használatával

Note

A cikkben szereplő kódminták az UWP Core App (IFrameworkView) alkalmazásmodellt használják. Egy WinUI 3 asztali alkalmazásban származtasson egy osztályt a Microsoft::UI::Xaml::Application osztályból, és hívja meg az Application::Start(...) függvényt az alkalmazás belépési pontjaként, továbbá a DispatcherQueue használja a CoreDispatcher helyett. Az itt bemutatott COM/Direct2D fogalmak és minták egyformán vonatkoznak a WinUI 3-alkalmazásokra.

A C++/WinRT kódtár szolgáltatásaival COM-összetevőket használhat, például a DirectX API-k nagy teljesítményű 2D és háromdimenziós grafikus elemeit. A C++/WinRT a DirectX használatának legegyszerűbb módja a teljesítmény veszélyeztetése nélkül. Ez a témakör egy Direct2D-kódpéldát használ, amely bemutatja, hogyan használható a C++/WinRT a COM-osztályok és -felületek használatára. A COM és Windows-futtatókörnyezet programozást természetesen egy C++/WinRT projektben is kombinálhatja.

A témakör végén egy minimális Direct2D-alkalmazás teljes forráskódlistát talál. A kódrészleteket a C++/WinRT kódtár különböző eszközeivel használva mutatjuk be, hogyan használhatja fel a COM-összetevőket c++/WinRT használatával.

COM okosmutatók (winrt::com_ptr)

Amikor a COM-val programozza, közvetlenül a felületekkel dolgozik, nem pedig az objektumokkal (ez szintén igaz a színfalak mögött Windows-futtatókörnyezet API-k esetében, amelyek a COM fejlődését képezik). Ha például egy COM-osztályban egy függvényt szeretne meghívni, aktiválja az osztályt, kap egy felületet, majd meghívja a függvényeket az adott felületen. Egy objektum állapotának eléréséhez nem fér hozzá közvetlenül az adattagjaihoz; ehelyett kiegészítő és mutációs függvényeket hív meg egy interfészen.

Pontosabban az interfészmutatókkal való interakcióról beszélünk. Ehhez pedig hasznos lehet a COM intelligens mutatótípus megléte a C++/WinRT-ben – a winrt::com_ptr típusban.

#include <d2d1_1.h>
...
winrt::com_ptr<ID2D1Factory1> factory;

A fenti kód bemutatja, hogyan deklarálhat egy nem inicializált intelligens mutatót egy ID2D1Factory1 COM-felületre. Az intelligens mutató nincs inicializálva, ezért még nem mutat egy tényleges objektumhoz tartozó ID2D1Factory1 felületre (egyáltalán nem mutat interfészre). De megvan benne a lehetőség erre; és (intelligens mutatóként) a COM-referenciaszámlálás révén képes kezelni az általa mutatott interfészt birtokló objektum élettartamát, valamint rajta keresztül metódusokat hívjon meg ezen az interfészen.

COM-függvények, amelyek üresként adnak vissza egy felületmutatót

Meghívhatja a com_ptr::put_void függvényt, hogy egy nem inicializált okosmutató alapjául szolgáló nyers mutatóba írjon.

D2D1_FACTORY_OPTIONS options{ D2D1_DEBUG_LEVEL_NONE };
D2D1CreateFactory(
    D2D1_FACTORY_TYPE_SINGLE_THREADED,
    __uuidof(factory),
    &options,
    factory.put_void()
);

A fenti kód meghívja a D2D1CreateFactory függvényt , amely egy ID2D1Factory1 interfészmutatót ad vissza az utolsó paraméteren keresztül, amely érvénytelen** típusú. Számos COM-függvény ürességet ad vissza**. Ilyen függvények esetén használja a com_ptr::put_void függvényt az alábbiak szerint.

COM-függvények, amelyek adott illesztőmutatót adnak vissza

A D3D11CreateDevice függvény egy ID3D11Device interfészmutatót ad vissza az utolsótól harmadik paraméteren keresztül, amely ID3D11Device** típusú. Az olyan függvények esetében, amelyek egy adott illesztőmutatót adnak vissza, használja a com_ptr::p ut parancsot.

winrt::com_ptr<ID3D11Device> device;
D3D11CreateDevice(
    ...
    device.put(),
    ...);

A fenti szakasz kód példája bemutatja, hogyan hívhatja meg a nyers D2D1CreateFactory függvényt . De valójában, amikor a jelen témakör kód példája meghívja a D2D1CreateFactory-t, egy segédfüggvénysablont használ, amely a nyers API-t burkolja, így a példakód valójában a com_ptr::p ut függvényt használja.

winrt::com_ptr<ID2D1Factory1> factory;
D2D1CreateFactory(
    D2D1_FACTORY_TYPE_SINGLE_THREADED,
    options,
    factory.put());

COM-függvények, amelyek IUnknownként adnak vissza egy interfészmutatót

A DWriteCreateFactory függvény egy DirectWrite factory interfészmutatót ad vissza az utolsó paraméterén keresztül, amely IUnknown típusú. Ilyen függvényhez használja a com_ptr::put elemet, de ezt IUnknown-ra reinterpret_casttal alakítsa.

DWriteCreateFactory(
    DWRITE_FACTORY_TYPE_SHARED,
    __uuidof(dwriteFactory2),
    reinterpret_cast<IUnknown**>(dwriteFactory2.put()));

A winrt::com_ptr újbóli beállítása

Fontos

Ha van egy winrt::com_ptr, amely már be van állítva (a belső nyers mutatója már egy célobjektumra mutat), és át szeretné állítani, hogy egy másik objektumra mutasson, akkor először a nullptr értéket kell hozzárendelnie — ahogy az alábbi kódpéldában is látható. Ha nem, akkor egy már beállított com_ptr felhívja a figyelmet a problémára (amikor meghívja a com_ptr::put vagy a com_ptr::put_void függvényt) azzal, hogy ellenőrzi, hogy a belső mutatója nem null értékű.

winrt::com_ptr<ID2D1SolidColorBrush> brush;
...
    brush.put()
...
brush = nullptr; // Important because we're about to re-seat
target->CreateSolidColorBrush(
    color_orange,
    D2D1::BrushProperties(0.8f),
    brush.put()));

HRESULT hibakódok kezelése

Ha ellenőrizni szeretné egy COM-függvényből visszaadott HRESULT értékét, és kivételt szeretne tenni abban az esetben, ha hibakódot jelöl, hívja meg a winrt::check_hresult.

winrt::check_hresult(D2D1CreateFactory(
    D2D1_FACTORY_TYPE_SINGLE_THREADED,
    __uuidof(factory),
    options,
    factory.put_void()));

COM-függvények, amelyek egy adott interfészmutatót vesznek igénybe

Meghívhatja a com_ptr::get függvényt, amely átadja a com_ptr egy olyan függvénynek, amely egy azonos típusú illesztőmutatót vesz igénybe.

... ExampleFunction(
    winrt::com_ptr<ID2D1Factory1> const& factory,
    winrt::com_ptr<IDXGIDevice> const& dxdevice)
{
    ...
    winrt::check_hresult(factory->CreateDevice(dxdevice.get(), ...));
    ...
}

AZ IUnknown felület mutatóját használó COM-függvények

Használhatja a com_ptr::get parancsot, hogy átadja a com_ptr egy olyan függvénynek, amely egy IUnknown interfészmutatót használ.

A winrt::get_unknown szabad függvénnyel visszaadhatja egy előre jelzett típusú objektum mögöttes nyers IUnknown felületének címét (más szóval mutatót). Ezután átadhatja ezt a címet egy olyan függvénynek, amely egy IUnknown interfészmutatót vesz fel.

A tervezett típusokról további információt az API-k felhasználása C++/WinRT használatával című témakörben talál.

A get_unknown példakódját a jelen témakör winrt::get_unknown vagy egy minimális Direct2D-alkalmazás teljes forráskódlistáiban találhatja meg.

Intelligens COM-mutatók átadása és visszaadása

Egy olyan függvénynek, amely egy COM smart pointert winrt::com_ptr formájában vesz át, azt konstans referenciaként vagy referenciaként kell átvennie.

... GetDxgiFactory(winrt::com_ptr<ID3D11Device> const& device) ...

... CreateDevice(..., winrt::com_ptr<ID3D11Device>& device) ...

A winrt::com_ptr függvénynek ezt érték szerint kell elvégeznie.

winrt::com_ptr<ID2D1Factory1> CreateFactory() ...

COM intelligens mutató lekérdezése másik interfészhez

A com_ptr::as függvénnyel lekérdezheti egy COM okosmutató másik interfészét. A függvény kivételt eredményez, ha a lekérdezés nem sikerül.

void ExampleFunction(winrt::com_ptr<ID3D11Device> const& device)
{
    ...
    winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };
    ...
}

Másik lehetőségként használja a com_ptr::try_as, amely egy olyan értéket ad vissza, amely alapján nullptr ellenőrizheti, hogy a lekérdezés sikeres volt-e.

Minimális Direct2D-alkalmazás teljes forráskódlistázása

Note

A C++/WinRT-fejlesztéshez szükséges Visual Studio beállításáról – beleértve a C++/WinRT Visual Studio bővítmény (VSIX) és a NuGet-csomag telepítését és használatát (amelyek együttesen nyújtanak projektsablont és buildtámogatást) – lásd Visual Studio C++/WinRT támogatását.

Ha ezt a forráskód-példát szeretné létrehozni és futtatni, először telepítse (vagy frissítse) a C++/WinRT Visual Studio bővítmény (VSIX) legújabb verzióját; lásd a fenti megjegyzést. Ezután Visual Studio hozzon létre egy új Core-alkalmazást (C++/WinRT). Direct2D ésszerű nevet ad a projektnek, de tetszés szerint nevezheti el. A Windows SDK legújabb általánosan elérhető (azaz nem előzetes verziójú) verzióját célozza meg.

1. lépés. Szerkeszt pch.h

Nyissa meg a(z) pch.h elemet, és adja hozzá a(z) #include <unknwn.h> elemet közvetlenül a(z) windows.h beillesztése után. Ennek az az oka, hogy winrt::get_unknown használunk. Érdemes explicit módon használni a #include <unknwn.h>winrt::get_unknown parancsot, még akkor is, ha a fejlécet egy másik fejléc is tartalmazza.

Note

Ha kihagyja ezt a lépést, a "get_unknown" összeállítási hiba jelenik meg : az azonosító nem található.

2. lépés. Szerkeszt App.cpp

Nyissa meg a(z) App.cpp fájlt, törölje annak teljes tartalmát, majd illessze be az alábbiakat.

Az alábbi kód a winrt::com_ptr::capture függvényt használja, ahol lehetséges. WINRT_ASSERT egy makródefiníció, és _ASSERTE kifejezésre bontódik ki.

#include "pch.h"
#include <d2d1_1.h>
#include <d3d11.h>
#include <dxgi1_2.h>
#include <winrt/Windows.Graphics.Display.h>

using namespace winrt;

using namespace Windows;
using namespace Windows::ApplicationModel::Core;
using namespace Windows::UI;
using namespace Windows::UI::Core;
using namespace Windows::Graphics::Display;

namespace
{
    winrt::com_ptr<ID2D1Factory1> CreateFactory()
    {
        D2D1_FACTORY_OPTIONS options{};

#ifdef _DEBUG
        options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

        winrt::com_ptr<ID2D1Factory1> factory;

        winrt::check_hresult(D2D1CreateFactory(
            D2D1_FACTORY_TYPE_SINGLE_THREADED,
            options,
            factory.put()));

        return factory;
    }

    HRESULT CreateDevice(D3D_DRIVER_TYPE const type, winrt::com_ptr<ID3D11Device>& device)
    {
        WINRT_ASSERT(!device);

        return D3D11CreateDevice(
            nullptr,
            type,
            nullptr,
            D3D11_CREATE_DEVICE_BGRA_SUPPORT,
            nullptr, 0,
            D3D11_SDK_VERSION,
            device.put(),
            nullptr,
            nullptr);
    }

    winrt::com_ptr<ID3D11Device> CreateDevice()
    {
        winrt::com_ptr<ID3D11Device> device;
        HRESULT hr{ CreateDevice(D3D_DRIVER_TYPE_HARDWARE, device) };

        if (DXGI_ERROR_UNSUPPORTED == hr)
        {
            hr = CreateDevice(D3D_DRIVER_TYPE_WARP, device);
        }

        winrt::check_hresult(hr);
        return device;
    }

    winrt::com_ptr<ID2D1DeviceContext> CreateRenderTarget(
        winrt::com_ptr<ID2D1Factory1> const& factory,
        winrt::com_ptr<ID3D11Device> const& device)
    {
        WINRT_ASSERT(factory);
        WINRT_ASSERT(device);

        winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };

        winrt::com_ptr<ID2D1Device> d2device;
        winrt::check_hresult(factory->CreateDevice(dxdevice.get(), d2device.put()));

        winrt::com_ptr<ID2D1DeviceContext> target;
        winrt::check_hresult(d2device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, target.put()));
        return target;
    }

    winrt::com_ptr<IDXGIFactory2> GetDxgiFactory(winrt::com_ptr<ID3D11Device> const& device)
    {
        WINRT_ASSERT(device);

        winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };

        winrt::com_ptr<IDXGIAdapter> adapter;
        winrt::check_hresult(dxdevice->GetAdapter(adapter.put()));

        winrt::com_ptr<IDXGIFactory2> factory;
        factory.capture(adapter, &IDXGIAdapter::GetParent);
        return factory;
    }

    void CreateDeviceSwapChainBitmap(
        winrt::com_ptr<IDXGISwapChain1> const& swapchain,
        winrt::com_ptr<ID2D1DeviceContext> const& target)
    {
        WINRT_ASSERT(swapchain);
        WINRT_ASSERT(target);

        winrt::com_ptr<IDXGISurface> surface;
        surface.capture(swapchain, &IDXGISwapChain1::GetBuffer, 0);

        D2D1_BITMAP_PROPERTIES1 const props{ D2D1::BitmapProperties1(
            D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
            D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)) };

        winrt::com_ptr<ID2D1Bitmap1> bitmap;

        winrt::check_hresult(target->CreateBitmapFromDxgiSurface(surface.get(),
            props,
            bitmap.put()));

        target->SetTarget(bitmap.get());
    }

    winrt::com_ptr<IDXGISwapChain1> CreateSwapChainForCoreWindow(winrt::com_ptr<ID3D11Device> const& device)
    {
        WINRT_ASSERT(device);

        winrt::com_ptr<IDXGIFactory2> const factory{ GetDxgiFactory(device) };

        DXGI_SWAP_CHAIN_DESC1 props{};
        props.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
        props.SampleDesc.Count = 1;
        props.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        props.BufferCount = 2;
        props.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;

        winrt::com_ptr<IDXGISwapChain1> swapChain;

        winrt::check_hresult(factory->CreateSwapChainForCoreWindow(
            device.get(),
            winrt::get_unknown(CoreWindow::GetForCurrentThread()),
            &props,
            nullptr, // all or nothing
            swapChain.put()));

        return swapChain;
    }

    constexpr D2D1_COLOR_F color_white{ 1.0f,  1.0f,  1.0f,  1.0f };
    constexpr D2D1_COLOR_F color_orange{ 0.92f,  0.38f,  0.208f,  1.0f };
}

struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    winrt::com_ptr<ID2D1Factory1> m_factory;
    winrt::com_ptr<ID2D1DeviceContext> m_target;
    winrt::com_ptr<IDXGISwapChain1> m_swapChain;
    winrt::com_ptr<ID2D1SolidColorBrush> m_brush;
    float m_dpi{};

    IFrameworkView CreateView()
    {
        return *this;
    }

    void Initialize(CoreApplicationView const&)
    {
    }

    void Load(hstring const&)
    {
        CoreWindow const window{ CoreWindow::GetForCurrentThread() };

        window.SizeChanged([&](auto&&...)
        {
            if (m_target)
            {
                ResizeSwapChainBitmap();
                Render();
            }
        });

        DisplayInformation const display{ DisplayInformation::GetForCurrentView() };
        m_dpi = display.LogicalDpi();

        display.DpiChanged([&](DisplayInformation const& display, IInspectable const&)
        {
            if (m_target)
            {
                m_dpi = display.LogicalDpi();
                m_target->SetDpi(m_dpi, m_dpi);
                CreateDeviceSizeResources();
                Render();
            }
        });

        m_factory = CreateFactory();
        CreateDeviceIndependentResources();
    }

    void Uninitialize()
    {
    }

    void Run()
    {
        CoreWindow const window{ CoreWindow::GetForCurrentThread() };
        window.Activate();

        Render();
        CoreDispatcher const dispatcher{ window.Dispatcher() };
        dispatcher.ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
    }

    void SetWindow(CoreWindow const&) {}

    void Draw()
    {
        m_target->Clear(color_white);

        D2D1_SIZE_F const size{ m_target->GetSize() };
        D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
        m_target->DrawRectangle(rect, m_brush.get(), 100.0f);

        char buffer[1024];
        (void)snprintf(buffer, sizeof(buffer), "Draw %.2f x %.2f @ %.2f\n", size.width, size.height, m_dpi);
        ::OutputDebugStringA(buffer);
    }

    void Render()
    {
        if (!m_target)
        {
            winrt::com_ptr<ID3D11Device> const device{ CreateDevice() };
            m_target = CreateRenderTarget(m_factory, device);
            m_swapChain = CreateSwapChainForCoreWindow(device);

            CreateDeviceSwapChainBitmap(m_swapChain, m_target);

            m_target->SetDpi(m_dpi, m_dpi);

            CreateDeviceResources();
            CreateDeviceSizeResources();
        }

        m_target->BeginDraw();
        Draw();
        m_target->EndDraw();

        HRESULT const hr{ m_swapChain->Present(1, 0) };

        if (S_OK != hr && DXGI_STATUS_OCCLUDED != hr)
        {
            ReleaseDevice();
        }
    }

    void ReleaseDevice()
    {
        m_target = nullptr;
        m_swapChain = nullptr;

        ReleaseDeviceResources();
    }

    void ResizeSwapChainBitmap()
    {
        WINRT_ASSERT(m_target);
        WINRT_ASSERT(m_swapChain);

        m_target->SetTarget(nullptr);

        if (S_OK == m_swapChain->ResizeBuffers(0, // all buffers
            0, 0, // client area
            DXGI_FORMAT_UNKNOWN, // preserve format
            0)) // flags
        {
            CreateDeviceSwapChainBitmap(m_swapChain, m_target);
            CreateDeviceSizeResources();
        }
        else
        {
            ReleaseDevice();
        }
    }

    void CreateDeviceIndependentResources()
    {
    }

    void CreateDeviceResources()
    {
        winrt::check_hresult(m_target->CreateSolidColorBrush(
            color_orange,
            D2D1::BrushProperties(0.8f),
            m_brush.put()));
    }

    void CreateDeviceSizeResources()
    {
    }

    void ReleaseDeviceResources()
    {
        m_brush = nullptr;
    }
};

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    CoreApplication::Run(winrt::make<App>());
}

COM-típusokkal való munka, például BSTR és VARIANT

Mint látható, a C++/WinRT támogatja a COM-felületek implementálását és hívását is. COM-típusok( például BSTR és VARIANT) használatához javasoljuk, hogy a Windows implementációs kódtárak (WIL) által biztosított burkolókat használja, például a wil::unique_bstr és a wil::unique_variant (amelyek az erőforrások élettartamát kezelik).

A WIL felülírja az olyan keretrendszereket, mint az Active Template Library (ATL) és a Visual C++ fordító COM-támogatása. És ezt a saját burkolóosztályok írása, illetve a nyers COM-típusok — például a BSTR és a VARIANT — közvetlen használata helyett javasoljuk (a megfelelő API-kkal együtt).

A névtér ütközésének elkerülése

A C++/WinRT-ben – ahogyan a jelen témakör kódlistája is mutatja – általános gyakorlat, hogy szabad felhasználású irányelveket használnak. Bizonyos esetekben azonban ez az ütköző nevek globális névtérbe való importálásának problémájához vezethet. Íme egy példa.

A C++/WinRT egy winrt::Windows::Foundation::IUnknown; míg a COM egy ::IUnknown nevű típust definiál. Ezért fontolja meg a következő kódot egy COM-fejléceket használó C++/WinRT-projektben.

using namespace winrt::Windows::Foundation;
...
void MyFunction(IUnknown*); // error C2872:  'IUnknown': ambiguous symbol

A minősítés nélküli IUnknown név ütközésbe kerül a globális névtérben, ezért a „nem egyértelmű szimbólum” fordítóhiba jelenik meg. Ehelyett elkülönítheti a név C++/WinRT verzióját a winrt-névtérbe , például így.

namespace winrt
{
    using namespace Windows::Foundation;
}
...
void MyFunctionA(IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.

Vagy, ha a using namespace winrt nyújtotta kényelmet szeretné, akkor ezt is megteheti. Ehhez csak az IUnknown globális verzióját kell minősítenie.

using namespace winrt;
namespace winrt
{
    using namespace Windows::Foundation;
}
...
void MyFunctionA(::IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.

Ez természetesen bármilyen C++/WinRT névtérrel működik.

namespace winrt
{
    using namespace Windows::Storage;
    using namespace Windows::System;
}

Ezután hivatkozhat a winrt::Windows::Storage::StorageFile fájlra, például a winrt::StorageFile fájlra.

Fontos API-k