Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Note
Kodexemplen i den här artikeln använder UWP Core App-programmodellen (IFrameworkView). I en WinUI 3-skrivbordsapp härleder du en klass från Microsoft::UI::Xaml::Application och anropar Application::Start(...) som startpunkt för appen och använder DispatcherQueue i stället för CoreDispatcher. COM/Direct2D-begreppen och mönstren som visas här gäller lika för WinUI 3-appar.
Du kan använda funktionerna i C++/WinRT-biblioteket för att använda COM-komponenter, till exempel högpresterande 2D- och 3D-grafik för DirectX-API:erna. C++/WinRT är det enklaste sättet att använda DirectX utan att äventyra prestanda. Det här avsnittet använder ett Direct2D-kodexempel för att visa hur du använder C++/WinRT för att använda COM-klasser och gränssnitt. Du kan naturligtvis blanda COM och Windows Runtime programmering inom samma C++/WinRT-projekt.
I slutet av det här avsnittet hittar du en fullständig källkodslista för ett minimalt Direct2D-program. Vi lyfter utdrag ur koden och använder dem för att illustrera hur du använder COM-komponenter med C++/WinRT med hjälp av olika faciliteter i C++/WinRT-biblioteket.
COM-smarta pekare (winrt::com_ptr)
När du programmerar med COM arbetar du direkt med gränssnitt i stället för med objekt (det gäller även i bakgrunden för Windows Runtime API:er, som är en utveckling av COM). Om du till exempel vill anropa en funktion i en COM-klass aktiverar du klassen, hämtar tillbaka ett gränssnitt och anropar sedan funktioner i gränssnittet. För att få åtkomst till tillståndet för ett objekt får du inte åtkomst till dess datamedlemmar direkt. I stället anropar du funktioner för accessor och mutator i ett gränssnitt.
För att vara mer specifik pratar vi om att interagera med gränssnittspekare. Och för det drar vi nytta av förekomsten av com-smartpekartypen i C++/WinRT – winrt::com_ptr typ.
#include <d2d1_1.h>
...
winrt::com_ptr<ID2D1Factory1> factory;
Koden ovan visar hur du deklarerar en enkelriktad smart pekare till ett ID2D1Factory1 COM-gränssnitt. Den smarta pekaren är oinitierad, så den pekar ännu inte på ett ID2D1Factory1-gränssnitt som tillhör något faktiskt objekt (det pekar inte alls på ett gränssnitt). Men det har potentialen att göra det; och eftersom det är en smart pekare har det förmågan att, genom COM-referensräkning, hantera livslängden för det objekt som äger gränssnittet som det pekar på, och att vara det medel genom vilket du anropar funktioner i gränssnittet.
COM-funktioner som returnerar en gränssnittspekare av typen void
Du kan anropa funktionen com_ptr::put_void för att skriva till den underliggande råpekaren för en oinitierad smartpekare.
D2D1_FACTORY_OPTIONS options{ D2D1_DEBUG_LEVEL_NONE };
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(factory),
&options,
factory.put_void()
);
Koden ovan anropar funktionen D2D1CreateFactory , som returnerar en ID2D1Factory1-gränssnittspekare via den sista parametern, som har typen void** . Många COM-funktioner returnerar ett tomrum**. För sådana funktioner använder du com_ptr::put_void enligt nedan.
COM-funktioner som returnerar en specifik gränssnittspekare
Funktionen D3D11CreateDevice returnerar en ID3D11Device-gränssnittspekare via den tredje parametern från slutet, som har typen ID3D11Device**. För funktioner som returnerar en specifik gränssnittspekare på det sättet, använd com_ptr::put.
winrt::com_ptr<ID3D11Device> device;
D3D11CreateDevice(
...
device.put(),
...);
Kodexemplet i avsnittet före det här visar hur du anropar funktionen raw D2D1CreateFactory . Men när kodexemplet för det här avsnittet anropar D2D1CreateFactory använder det en hjälpfunktionsmall som omsluter det råa API:et, så kodexemplet använder faktiskt com_ptr::p ut.
winrt::com_ptr<ID2D1Factory1> factory;
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
options,
factory.put());
COM-funktioner som returnerar en gränssnittspekare av typen IUnknown
Funktionen DWriteCreateFactory returnerar en DirectWrite-fabriksgränssnittspekare via den sista parametern, som har typen IUnknown . För en sådan funktion använder du com_ptr::put, men omtolkar den då till IUnknown.
DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(dwriteFactory2),
reinterpret_cast<IUnknown**>(dwriteFactory2.put()));
Tilldela en winrt::com_ptr på nytt
Viktigt!
Om du har en winrt::com_ptr som redan finns (dess interna råpekare redan har ett mål) och du vill placera den på nytt så att den pekar på ett annat objekt, måste du först tilldela nullptr det , som du ser i kodexemplet nedan. Om du inte gör det kommer en redan upptagen com_ptr att göra dig uppmärksam på problemet (när du anropar com_ptr::put eller com_ptr::put_void) genom att utlösa en assertion om att dess interna pekare inte är null.
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()));
Hantera HRESULT-felkoder
Om du vill kontrollera värdet för en HRESULT som returneras från en COM-funktion och utlösa ett undantag om det representerar en felkod anropar du winrt::check_hresult.
winrt::check_hresult(D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(factory),
options,
factory.put_void()));
COM-funktioner som tar en specifik gränssnittspekare
Du kan anropa funktionen com_ptr::get för att skicka com_ptr till en funktion som tar en specifik gränssnittspekare av samma typ.
... ExampleFunction(
winrt::com_ptr<ID2D1Factory1> const& factory,
winrt::com_ptr<IDXGIDevice> const& dxdevice)
{
...
winrt::check_hresult(factory->CreateDevice(dxdevice.get(), ...));
...
}
COM-funktioner som tar en pekare till IUnknown-gränssnittet
Du kan använda com_ptr::get för att skicka din com_ptr till en funktion som tar en pekare till gränssnittet IUnknown.
Du kan använda funktionen winrt::get_unknown free för att returnera adressen till (med andra ord en pekare till) det underliggande råa IUnknown-gränssnittet för ett objekt av en projekterad typ. Du kan sedan vidarebefordra den adressen till en funktion som tar en IUnknown-gränssnittspekare.
Information om projicerade typer finns i Använd API:er med C++/WinRT.
Ett kodexempel på get_unknown finns i winrt::get_unknown eller den fullständiga källkodslistan för ett minimalt Direct2D-program i det här avsnittet.
Skicka och returnera smarta COM-pekare
En funktion som tar emot en COM-smartpekare i form av en winrt::com_ptr bör göra det som en konstant referens eller som en referens.
... GetDxgiFactory(winrt::com_ptr<ID3D11Device> const& device) ...
... CreateDevice(..., winrt::com_ptr<ID3D11Device>& device) ...
En funktion som returnerar en winrt::com_ptr bör göra det med värde.
winrt::com_ptr<ID2D1Factory1> CreateFactory() ...
Fråga efter en SMART COM-pekare för ett annat gränssnitt
Du kan använda funktionen com_ptr::as för att fråga efter en SMART COM-pekare för ett annat gränssnitt. Funktionen utlöser ett undantag om frågan inte lyckas.
void ExampleFunction(winrt::com_ptr<ID3D11Device> const& device)
{
...
winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };
...
}
Du kan också använda com_ptr::try_as, som returnerar ett värde som du kan kontrollera mot nullptr för att se om frågan lyckades.
Fullständig källkodslista för ett minimalt Direct2D-program
Note
Information om hur du konfigurerar Visual Studio för C++/WinRT-utveckling – inklusive installation och användning av VSIX (C++/WinRT Visual Studio Extension) och NuGet-paketet (som tillsammans tillhandahåller projektmall och byggstöd) – finns i Visual Studio stöd för C++/WinRT.
Om du vill skapa och köra det här källkodsexemplet ska du först installera (eller uppdatera till) den senaste versionen av VSIX (C++/WinRT Visual Studio Extension), se anteckningen ovan. Skapa sedan en ny Core App (C++/WinRT) i Visual Studio.
Direct2D är ett rimligt namn för projektet, men du kan kalla det vad du vill. Rikta in dig på den senaste allmänt tillgängliga (dvs. inte förhandsversionen) av Windows SDK.
Steg 1. Redigera pch.h
Öppna pch.hoch lägg till #include <unknwn.h> omedelbart efter att du har inkluderat windows.h. Det beror på att vi använder winrt::get_unknown. Det är en bra idé att #include <unknwn.h> uttryckligen när du använder winrt::get_unknown, även om den headerfilen redan har inkluderats via en annan headerfil.
Note
Om du utelämnar det här steget visas byggfelet "get_unknown": identifieraren hittades inte.
Steg 2. Redigera App.cpp
Öppna App.cpp, ta bort hela innehållet och klistra in i listan nedan.
Koden nedan använder funktionen winrt::com_ptr::capture där det är möjligt.
WINRT_ASSERT är en makrodefinition och expanderas till _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>());
}
Arbeta med COM-typer, till exempel BSTR och VARIANT
Som du ser har C++/WinRT stöd för både implementering och anrop av COM-gränssnitt. För att använda COM-typer, till exempel BSTR och VARIANT, rekommenderar vi att du använder omslutningar som tillhandahålls av Windows Implementeringsbibliotek (WIL), till exempel wil::unique_bstr och wil::unique_variant (som hanterar resursernas livslängd).
WIL ersätter ramverk som ACTIVE Template Library (ATL) och Visual C++-kompilatorns COM-stöd. Och vi rekommenderar detta snarare än att skriva egna wrapper-klasser eller använda COM-typer som BSTR och VARIANT direkt (tillsammans med relevanta API:er).
Undvika namnområdeskollisioner
Det är vanligt i C++/WinRT – som kodlistan i det här avsnittet visar – att använda användningsdirektiv liberalt. I vissa fall kan det dock leda till problem med att importera kolliderande namn till det globala namnområdet. Här följer ett exempel.
C++/WinRT innehåller en typ med namnet winrt::Windows::Foundation::IUnknown, medan COM definierar en typ med namnet ::IUnknown. Tänk dig därför följande kod i ett C++/WinRT-projekt som använder COM-huvuden.
using namespace winrt::Windows::Foundation;
...
void MyFunction(IUnknown*); // error C2872: 'IUnknown': ambiguous symbol
Det okvalificerade namnet IUnknown kolliderar i det globala namnområdet, därav det tvetydiga symbolkompileringsfelet. I stället kan du isolera C++/WinRT-versionen av namnet till winrt-namnområdet , så här.
namespace winrt
{
using namespace Windows::Foundation;
}
...
void MyFunctionA(IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.
Eller, om du vill ha bekvämligheten med using namespace winrt, så kan du. Du behöver bara kvalificera den globala versionen av IUnknown, så här.
using namespace winrt;
namespace winrt
{
using namespace Windows::Foundation;
}
...
void MyFunctionA(::IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.
Detta fungerar naturligtvis med valfritt C++/WinRT-namnområde.
namespace winrt
{
using namespace Windows::Storage;
using namespace Windows::System;
}
Du kan sedan referera till winrt::Windows::Storage::StorageFile, till exempel som just winrt::StorageFile.
Viktiga API:er
Windows developer