Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Note
Ukázky kódu v tomto článku používají aplikační model aplikace IFrameworkView (UPW Core App). V desktopové aplikaci WinUI 3 odvozujte třídu z Microsoft::UI::Xaml::Application a volejte Application::Start(...) jako vstupní bod aplikace a místo CoreDispatcher použijte DispatcherQueue. Koncepty a vzory modelu COM/Direct2D uvedené zde platí stejně pro aplikace WinUI 3.
Pomocí funkcí knihovny C++/WinRT můžete využívat komponenty modelu COM, jako jsou vysoce výkonné 2D a 3D grafické rozhraní API DirectX. C++/WinRT je nejjednodušší způsob, jak používat DirectX bez ohrožení výkonu. Toto téma využívá ukázku kódu v Direct2D, aby ukázalo, jak pomocí C++/WinRT používat třídy a rozhraní COM. Samozřejmě můžete kombinovat com a prostředí Windows Runtime programování ve stejném projektu C++/WinRT.
Na konci tohoto tématu najdete úplný seznam zdrojového kódu minimální aplikace Direct2D. Vybereme části tohoto kódu a použijeme je k ilustraci toho, jak používat komponenty COM pomocí C++/WinRT s využitím různých prostředků knihovny C++/WinRT.
Inteligentní ukazatele COM (winrt::com_ptr)
Když programujete pomocí modelu COM, pracujete přímo s rozhraními místo s objekty (to platí také na pozadí pro rozhraní API prostředí Windows Runtime, což je vývoj modelu COM). Chcete-li zavolat funkci objektu COM, například nejprve aktivujete třídu, získáte rozhraní a potom prostřednictvím tohoto rozhraní voláte funkce. Pokud chcete získat přístup ke stavu objektu, nemáte přímý přístup ke svým datovým členům; místo toho voláte funkce přístupového objektu a mutátoru v rozhraní.
Abychom byli konkrétnější, mluvíme o interakci s ukazateli rozhraní. A pro to získáme výhodu existence inteligentního ukazatele modelu COM v jazyce C++/WinRT – typu winrt::com_ptr .
#include <d2d1_1.h>
...
winrt::com_ptr<ID2D1Factory1> factory;
Výše uvedený kód ukazuje, jak deklarovat neinicializovaný inteligentní ukazatel na rozhraní COM ID2D1Factory1 . Inteligentní ukazatel není inicializován, takže zatím neodkazuje na rozhraní ID2D1Factory1 patřící k žádnému skutečnému objektu (vůbec neukazuje na rozhraní). Ale má potenciál tak učinit; a (jako inteligentní ukazatel) má schopnost prostřednictvím počítání referencí v modelu COM spravovat životnost objektu, který vlastní rozhraní, na něž ukazuje, a sloužit jako prostředek, jehož prostřednictvím voláte funkce tohoto rozhraní.
Funkce modelu COM, které vracejí ukazatel na rozhraní jako typ void
Pomocí funkce com_ptr::put_void můžete zapsat do podkladového nezpracovaného ukazatele neinicializovaného inteligentního ukazatele.
D2D1_FACTORY_OPTIONS options{ D2D1_DEBUG_LEVEL_NONE };
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(factory),
&options,
factory.put_void()
);
Výše uvedený kód volá funkci D2D1CreateFactory, která vrací ukazatel rozhraní ID2D1Factory1 prostřednictvím posledního parametru, který má typ void**. Mnoho funkcí modelu COM vrací void**. Pro tyto funkce použijte com_ptr::put_void, jak je uvedeno níže.
Funkce modelu COM, které vracejí určitý ukazatel rozhraní
Funkce D3D11CreateDevice vrátí ukazatel rozhraní ID3D11Device prostřednictvím svého třetího parametru od posledního, který má typ ID3D11Device** . U funkcí, které takto vracejí ukazatel na konkrétní rozhraní, použijte com_ptr::put.
winrt::com_ptr<ID3D11Device> device;
D3D11CreateDevice(
...
device.put(),
...);
Příklad kódu v části před touto částí ukazuje, jak volat nezpracovanou funkci D2D1CreateFactory . Ale ve skutečnosti, když ukázka kódu k tomuto tématu volá D2D1CreateFactory, používá pomocnou šablonu funkce, která obaluje nezpracované rozhraní API, takže ukázka kódu ve skutečnosti používá com_ptr::put.
winrt::com_ptr<ID2D1Factory1> factory;
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
options,
factory.put());
Funkce COM, které vracejí ukazatel na rozhraní typu IUnknown
Funkce DWriteCreateFactory vrátí ukazatel rozhraní továrny DirectWrite prostřednictvím posledního parametru, který má typ IUnknown . Pro takovou funkci použijte com_ptr::put, ale přetypujte jej pomocí reinterpret_cast na IUnknown.
DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(dwriteFactory2),
reinterpret_cast<IUnknown**>(dwriteFactory2.put()));
Znovu nastavit winrt::com_ptr
Důležité
Pokud máte winrt::com_ptr , který už sedí (jeho interní nezpracovaný ukazatel už má cíl) a chcete ho přemístit tak, aby odkazoval na jiný objekt, musíte ho nejdřív přiřadit nullptr – jak je znázorněno v příkladu kódu níže. Pokud ne, pak již sedící com_ptr vás upozorní na váš problém (když zavoláte com_ptr::p ut nebo com_ptr::p ut_void) tím, že zjistíte, že jeho interní ukazatel není 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()));
Zpracování kódů chyb HRESULT
Pokud chcete zkontrolovat hodnotu HRESULT vrácenou z funkce COM a vyvolat výjimku v případě, že představuje kód chyby, zavolejte winrt::check_hresult.
winrt::check_hresult(D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(factory),
options,
factory.put_void()));
Funkce COM, které přijímají ukazatel na konkrétní rozhraní
Funkci com_ptr::get můžete zavolat, chcete-li svůj objekt com_ptr předat funkci, která přijímá konkrétní ukazatel na rozhraní stejného typu.
... ExampleFunction(
winrt::com_ptr<ID2D1Factory1> const& factory,
winrt::com_ptr<IDXGIDevice> const& dxdevice)
{
...
winrt::check_hresult(factory->CreateDevice(dxdevice.get(), ...));
...
}
Funkce COM, které přebírají ukazatel rozhraní IUnknown
Můžete použít com_ptr::get k předání com_ptr funkci, která přebírá ukazatel rozhraní IUnknown .
Pomocí volné funkce winrt::get_unknown můžete získat adresu (jinými slovy ukazatel na) podkladové nezpracované rozhraní IUnknown objektu promítaného typu. Tuto adresu pak můžete předat funkci, která přebírá ukazatel rozhraní IUnknown .
Informace o projektovaných typech najdete v tématu Využívání rozhraní API s C++/WinRT.
Příklad kódu get_unknown najdete v tématu winrt::get_unknown nebo úplný zdrojový kód s minimální aplikací Direct2D v tomto tématu.
Předávání a vracení inteligentních ukazatelů COM
Funkce, která přijímá inteligentní ukazatel COM ve formě winrt::com_ptr, by jej měla přijímat jako konstantní referenci nebo jako referenci.
... GetDxgiFactory(winrt::com_ptr<ID3D11Device> const& device) ...
... CreateDevice(..., winrt::com_ptr<ID3D11Device>& device) ...
Funkce, která vrátí winrt::com_ptr by to měla provést podle hodnoty.
winrt::com_ptr<ID2D1Factory1> CreateFactory() ...
Dotaz na inteligentní ukazatel COM pro jiné rozhraní
Funkci com_ptr::as můžete použít k dotazování inteligentního ukazatele COM na jiné rozhraní. Pokud dotaz neuspěje, funkce vyvolá výjimku.
void ExampleFunction(winrt::com_ptr<ID3D11Device> const& device)
{
...
winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };
...
}
Případně můžete použít com_ptr::try_as, která vrátí hodnotu, se kterou můžete zkontrolovat nullptr , jestli dotaz proběhl úspěšně.
Úplný výpis zdrojového kódu minimální aplikace Direct2D
Note
Informace o nastavení Visual Studio pro vývoj pro C++/WinRT – včetně instalace a použití rozšíření C++/WinRT Visual Studio (VSIX) a balíčku NuGet (který společně poskytuje podporu šablony projektu a sestavení) – viz Visual Studio podpora pro C++/WinRT.
Pokud chcete sestavit a spustit tento příklad zdrojového kódu, nejprve nainstalujte (nebo aktualizujte) nejnovější verzi rozšíření C++/WinRT Visual Studio (VSIX); viz poznámka výše. Pak v Visual Studio vytvořte novou základní aplikaci (C++/WinRT).
Direct2D je rozumný název projektu, ale můžete ho pojmenovat cokoliv, co se vám líbí. Cilte na nejnovější běžně dostupnou verzi sady Windows SDK (tedy ne ve verzi Preview).
Krok 1. Upravit pch.h
Otevřete pch.h, a přidejte #include <unknwn.h> ihned po zahrnutí windows.h. Je to proto, že používáme winrt::get_unknown. Je vhodné #include <unknwn.h> explicitně uvést při každém použití winrt::get_unknown, i když ji zahrnuje jiná hlavička.
Note
Pokud tento krok vynecháte, zobrazí se chyba sestavení get_unknown: identifikátor nebyl nalezen.
Krok 2. Upravit App.cpp
Otevřete App.cpp, odstraňte celý jeho obsah a vložte následující výpis.
Níže uvedený kód používá funkci winrt::com_ptr::capture , pokud je to možné.
WINRT_ASSERT je definice makra a rozbalí se na _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>());
}
Práce s datovými typy COM, jako jsou BSTR a VARIANT
Jak vidíte, C++/WinRT poskytuje podporu pro implementaci i volání rozhraní COM. Pro používání typů COM, jako jsou BSTR a VARIANT, doporučujeme používat obalové třídy poskytované knihovnami Windows Implementation Libraries (WIL), jako jsou wil::unique_bstr a wil::unique_variant (které zajišťují správu životnosti prostředků).
WIL nahrazuje architektury, jako je knihovna ATL (Active Template Library) a podpora modelu COM kompilátoru Visual C++. A doporučujeme to spíše než psát vlastní obálky nebo používat typy COM, jako jsou BSTR a VARIANT, v jejich surové podobě (společně s příslušnými API).
Vyhýbání se kolizím jmenných prostorů
V C++/WinRT je běžnou praxí, jak ukazuje výpis kódu v tomto tématu, ve velké míře používat direktivy using. V některých případech to ale může vést k problému importu kolísajících názvů do globálního oboru názvů. Tady je příklad.
C++/WinRT obsahuje typ winrt::Windows::Foundation::IUnknown; zatímco com definuje typ s názvem ::IUnknown. Uvažujme tedy následující kód v projektu C++/WinRT, který využívá hlavičkové soubory COM.
using namespace winrt::Windows::Foundation;
...
void MyFunction(IUnknown*); // error C2872: 'IUnknown': ambiguous symbol
Nekvalifikovaný název IUnknown koliduje v globálním oboru názvů, což vede k chybě kompilátoru ambiguous symbol. Místo toho můžete izolovat verzi názvu C++/WinRT do oboru názvů winrt , například takto.
namespace winrt
{
using namespace Windows::Foundation;
}
...
void MyFunctionA(IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.
Nebo pokud chcete využít using namespace winrt pro větší pohodlí, můžete. Stačí takto kvalifikovat globální verzi IUnknown.
using namespace winrt;
namespace winrt
{
using namespace Windows::Foundation;
}
...
void MyFunctionA(::IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.
Přirozeně to funguje s jakýmkoli oborem názvů C++/WinRT.
namespace winrt
{
using namespace Windows::Storage;
using namespace Windows::System;
}
Potom můžete odkazovat na winrt::Windows::Storage::StorageFile, například jako winrt::StorageFile.
Důležitá rozhraní API
Windows developer