COM-onderdelen gebruiken met C++/WinRT

Note

In de codevoorbeelden in dit artikel wordt het UWP Core App-toepassingsmodel (IFrameworkView) gebruikt. In een WinUI 3-desktop-app leidt u een klasse af van Microsoft::UI::Xaml::Application en roept u Application::Start(...) aan als het entry point van de app, en gebruikt u DispatcherQueue in plaats van CoreDispatcher. De COM/Direct2D-concepten en -patronen die hier worden weergegeven, zijn evenzeer van toepassing op WinUI 3-apps.

U kunt de faciliteiten van de C++/WinRT-bibliotheek gebruiken om COM-onderdelen te gebruiken, zoals de krachtige 2D- en 3D-afbeeldingen van de DirectX-API's. C++/WinRT is de eenvoudigste manier om DirectX te gebruiken zonder de prestaties in gevaar te brengen. In dit onderwerp wordt een Direct2D-codevoorbeeld gebruikt om te laten zien hoe u C++/WinRT gebruikt om COM-klassen en -interfaces te gebruiken. U kunt natuurlijk COM en Windows Runtime programmeren in hetzelfde C++/WinRT-project.

Aan het einde van dit onderwerp vindt u een volledige broncodevermelding van een minimale Direct2D-toepassing. We halen fragmenten uit die code op en gebruiken ze om te illustreren hoe u COM-onderdelen gebruikt met behulp van C++/WinRT met behulp van verschillende faciliteiten van de C++/WinRT-bibliotheek.

COM smart pointers (winrt::com_ptr)

Wanneer u met COM programmeert, werkt u rechtstreeks met interfaces in plaats van met objecten (dat geldt ook achter de schermen voor Windows Runtime API's, die een evolutie van COM zijn). Als u bijvoorbeeld een functie wilt aanroepen in een COM-klasse, activeert u de klasse, haalt u een interface terug en roept u functies op die interface aan. Als u toegang wilt krijgen tot de status van een object, hebt u niet rechtstreeks toegang tot de gegevensleden; In plaats daarvan roept u accessor- en mutatorfuncties aan op een interface.

Om specifieker te zijn, hebben we het over interactie met interfacepointers. En daarvoor profiteren we van het bestaan van het com smart pointer-type in C++/WinRT, het winrt::com_ptr type.

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

De bovenstaande code laat zien hoe u een niet-geïnitialiseerde slimme aanwijzer declareert naar een COM-interface van ID2D1Factory1 . De slimme aanwijzer is niet-geïnitialiseerd, dus deze wijst nog niet naar een ID2D1Factory1-interface die hoort bij een werkelijk object (het verwijst helemaal niet naar een interface). Maar het heeft de potentie om dat te doen; en (als slimme pointer) heeft het via COM-referentietelling de mogelijkheid om de levensduur te beheren van het object dat eigenaar is van de interface waarnaar deze verwijst, en om het middel te zijn waarmee u functies op die interface aanroept.

COM-functies die een interfaceaanwijzer retourneren als ongeldig

U kunt de functie com_ptr::put_void aanroepen om te schrijven naar de onderliggende ruwe pointer van een niet-geïnitialiseerde slimme pointer.

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

Met de bovenstaande code wordt de functie D2D1CreateFactory aangeroepen, die een ID2D1Factory1-interfaceaanwijzer retourneert via de laatste parameter, die het type void** heeft. Veel COM-functies retourneren een ongeldige waarde**. Gebruik bij dergelijke functies com_ptr::put_void zoals weergegeven.

COM-functies die een specifieke interfacepointer retourneren

De functie D3D11CreateDevice retourneert een id3D11Device-interfaceaanwijzer via de derde van laatste parameter, met het type ID3D11Device** . Voor functies die op die manier een specifieke interfaceaanwijzer retourneren, gebruikt u com_ptr::put.

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

Het codevoorbeeld in de sectie voordat deze wordt weergegeven, laat zien hoe u de onbewerkte D2D1CreateFactory-functie aanroept. Maar in feite gebruikt het codevoorbeeld voor dit onderwerp, wanneer het D2D1CreateFactory aanroept, een sjabloon voor een hulpfunctie dat de ruwe API omhult, en daarom gebruikt het codevoorbeeld in werkelijkheid com_ptr::put.

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

COM-functies die een interfaceaanwijzer retourneren als IUnknown

De functie DWriteCreateFactory retourneert een directWrite factory-interfaceaanwijzer via de laatste parameter, die het type IUnknown heeft. Gebruik voor een dergelijke functie com_ptr::put, maar cast die dan opnieuw naar IUnknown.

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

Een winrt opnieuw plaatsen::com_ptr

Belangrijk

Als u een winrt::com_ptr hebt die al is ingesteld (de interne raw pointer verwijst al naar een doel) en u deze opnieuw wilt laten verwijzen naar een ander object, moet u er eerst nullptr aan toewijzen, zoals wordt weergegeven in het onderstaande codevoorbeeld. Als u dat niet doet, trekt een reeds geplaatste com_ptr het probleem naar uw aandacht (wanneer u com_ptr::p ut of com_ptr::p ut_void) aanroept door te bevestigen dat de interne aanwijzer niet null is.

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-foutcodes verwerken

Als u de waarde van een HRESULT wilt controleren die is geretourneerd door een COM-functie en een uitzondering genereert in het geval dat deze een foutcode vertegenwoordigt, roept u winrt::check_hresult aan.

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

COM-functies die een specifieke interfacepointer gebruiken

U kunt de functie com_ptr::get aanroepen om uw com_ptr door te geven aan een functie die een specifieke interfacepointer van hetzelfde type gebruikt.

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

COM-functies die een IUnknown-interfaceaanwijzer gebruiken

U kunt com_ptr::get gebruiken om uw com_ptr door te geven aan een functie die een IUnknown-interfaceaanwijzer gebruikt.

U kunt de winrt::get_unknown-vrije functie gebruiken om het adres op te halen van (met andere woorden, een aanwijzer naar) de onderliggende ruwe IUnknown-interface van een object van een geprojecteerd type. U kunt dat adres vervolgens doorgeven aan een functie die een IUnknown-interfaceaanwijzer gebruikt.

Zie API's gebruiken met C++/WinRT voor informatie over projecttypen.

Zie winrt::get_unknown of de volledige broncode van een minimale Direct2D-toepassing in dit onderwerp voor een codevoorbeeld van get_unknown.

Slimme COM-aanwijzers doorgeven en retourneren

Een functie die een COM-slimme pointer aanneemt in de vorm van een winrt::com_ptr, moet die als constante referentie of als referentie doorgeven.

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

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

Een functie die een winrt::com_ptr teruggeeft, moet dat als waarde doen.

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

Een query uitvoeren op een COM-slimme aanwijzer voor een andere interface

U kunt de functie com_ptr::as gebruiken om een query uit te voeren op een COM-slimme aanwijzer voor een andere interface. De functie genereert een uitzondering als de query niet slaagt.

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

U kunt ook com_ptr::try_as gebruiken, waarmee een waarde wordt geretourneerd die u kunt controleren nullptr om te zien of de query is geslaagd.

Volledige broncodevermelding van een minimale Direct2D-toepassing

Note

Zie Visual Studio support for C++/WinRT voor informatie over het configureren van Visual Studio voor C++/WinRT-ontwikkeling, waaronder het installeren en gebruiken van de C++/WinRT Visual Studio-extensie (VSIX) en het NuGet-pakket (die samen projectsjablonen en ondersteuning voor builds bieden).

Als u dit broncodevoorbeeld wilt bouwen en uitvoeren, installeert u eerst de meest recente versie van de C++/WinRT-Visual Studio-extensie (VSIX). Zie de bovenstaande opmerking. Maak vervolgens in Visual Studio een nieuwe Core-app (C++/WinRT). Direct2D is een redelijke naam voor het project, maar u kunt het alles wat u wilt noemen. Richt u op de meest recente algemeen beschikbare (dus niet preview)-versie van de Windows SDK.

Stap 1. Bewerken pch.h

Open pch.h en voeg #include <unknwn.h> direct toe na het opnemen van windows.h. Dit komt omdat we winrt::get_unknown gebruiken. Het is een goed idee om expliciet te #include <unknwn.h> gebruiken wanneer u winrt::get_unknown gebruikt, zelfs als die header is opgenomen in een andere header.

Note

Als u deze stap weglaat, ziet u de buildfout 'get_unknown': id niet gevonden.

Stap 2. Bewerken App.cpp

Open App.cpp, verwijder de volledige inhoud en plak de onderstaande vermelding.

In de onderstaande code wordt waar mogelijk de winrt::com_ptr::capture-functie gebruikt. WINRT_ASSERT is een macrodefinitie en wordt uitgebreid naar _ASSERTE.

#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>());
}

Werken met COM-typen, zoals BSTR en VARIANT

Zoals u ziet, biedt C++/WinRT ondersteuning voor het implementeren en aanroepen van COM-interfaces. Voor het gebruik van COM-typen, zoals BSTR en VARIANT, raden we u aan wrappers te gebruiken die worden geleverd door de Windows Implementatiebibliotheken (WIL), zoals wil::unique_bstr en wil::unique_variant (waarmee de levensduur van resources wordt beheerd).

WIL vervangt frameworks zoals de Active Template Library (ATL) en de COM-ondersteuning van de Visual C++-compiler. En we raden dit eerder aan dan zelf wrappers te schrijven of COM-typen zoals BSTR en VARIANT in hun ruwe vorm te gebruiken (samen met de juiste API's).

Naamruimteconflicten voorkomen

Het is gebruikelijk in C++/WinRT, zoals de codevermelding in dit onderwerp laat zien, om gebruik te maken van gebruiksrichtlijnen. In sommige gevallen kan dat echter leiden tot het probleem van het importeren van botsende namen in de globale naamruimte. Dit is een voorbeeld.

C++/WinRT bevat een type met de naam winrt::Windows::Foundation::IUnknown; terwijl COM een type met de naam ::IUnknown definieert. Houd dus rekening met de volgende code in een C++/WinRT-project dat COM-headers verbruikt.

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

De niet-gekwalificeerde naam IUnknown conflicteert in de globale naamruimte, waardoor de compilerfout vanwege een dubbelzinnig symbool optreedt. In plaats daarvan kunt u de C++/WinRT-versie van de naam isoleren in de winrt-naamruimte , zoals deze.

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

Of, als u het gemak van using namespace winrt wilt, dan kan dat. U hoeft alleen de globale versie van IUnknown te kwalificeren, zoals deze.

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

Dit werkt natuurlijk met elke C++/WinRT-naamruimte.

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

U kunt vervolgens naar winrt::Windows::Storage::StorageFile verwijzen, bijvoorbeeld als alleen winrt::StorageFile.

Belangrijke API's