Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Hinweis
Die Codebeispiele in diesem Artikel verwenden das UWP Core App (IFrameworkView)-Anwendungsmodell. Leiten Sie in einer WinUI 3-Desktop-App eine Klasse von Microsoft::UI::Xaml::Application ab, rufen Sie Application::Start(...) als Einstiegspunkt der App auf, und verwenden Sie DispatcherQueue anstelle von CoreDispatcher. Die hier gezeigten COM/Direct2D-Konzepte und -Muster gelten gleichermaßen für WinUI 3-Apps.
Sie können die Einrichtungen der C++/WinRT-Bibliothek verwenden, um COM-Komponenten zu nutzen, z. B. die leistungsstarken 2D- und 3D-Grafiken der DirectX-APIs. C++/WinRT ist die einfachste Möglichkeit, DirectX zu verwenden, ohne die Leistung zu beeinträchtigen. In diesem Thema wird anhand eines Direct2D-Codebeispiels gezeigt, wie Sie mit C++/WinRT COM-Klassen und -Schnittstellen nutzen. Sie können natürlich COM und Windows-Runtime Programmierung innerhalb desselben C++/WinRT-Projekts kombinieren.
Am Ende dieses Themas finden Sie eine vollständige Quellcodeauflistung einer minimalen Direct2D-Anwendung. Wir entnehmen diesem Code einige Auszüge und verwenden sie, um zu veranschaulichen, wie man COM-Komponenten mit C++/WinRT unter Verwendung verschiedener Funktionen der C++/WinRT-Bibliothek nutzt.
INTELLIGENTE COM-Zeiger (winrt::com_ptr)
Wenn Sie mit COM programmieren, arbeiten Sie direkt mit Schnittstellen und nicht mit Objekten (das gilt auch im Hintergrund für Windows-Runtime APIs, die eine Weiterentwicklung von COM sind). Wenn Sie beispielsweise eine Funktion für eine COM-Klasse aufrufen möchten, aktivieren Sie die Klasse, rufen eine Schnittstelle zurück und rufen dann Funktionen auf dieser Schnittstelle auf. Um auf den Status eines Objekts zuzugreifen, greifen Sie nicht direkt auf dessen Datenmber zu; Stattdessen rufen Sie Accessor- und Mutatorfunktionen auf einer Schnittstelle auf.
Um spezifischer zu sein, sprechen wir über die Interaktion mit Schnittstellenzeigern. Und dafür profitieren wir von der Existenz des intelligenten COM-Zeigertyps in C++/WinRT – dem winrt::com_ptr-Typ .
#include <d2d1_1.h>
...
winrt::com_ptr<ID2D1Factory1> factory;
Der obige Code zeigt, wie sie einen nicht initialisierten intelligenten Zeiger auf eine ID2D1Factory1-COM-Schnittstelle deklarieren. Der intelligente Zeiger ist nicht initialisiert, sodass er noch nicht auf eine ID2D1Factory1-Schnittstelle zeigt, die zu einem tatsächlichen Objekt gehört (es verweist überhaupt nicht auf eine Schnittstelle). Aber es hat das Potenzial, dies zu tun; und als intelligenter Pointer kann es mittels COM-Referenzzählung die Lebensdauer des Objekts verwalten, dem die Schnittstelle gehört, auf die er zeigt, und als Mittel dienen, über das Sie Funktionen dieser Schnittstelle aufrufen.
COM-Funktionen, die einen Schnittstellenzeiger als void zurückgeben
Sie können die Funktion com_ptr::put_void aufrufen, um in den zugrunde liegenden Rohzeiger eines nicht initialisierten Smartpointers zu schreiben.
D2D1_FACTORY_OPTIONS options{ D2D1_DEBUG_LEVEL_NONE };
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(factory),
&options,
factory.put_void()
);
Der obige Code ruft die D2D1CreateFactory-Funktion auf, die einen ID2D1Factory1-Schnittstellenzeiger über den letzten Parameter zurückgibt, der "void**" aufweist. Viele COM-Funktionen geben " void**" zurück. Verwenden Sie für solche Funktionen com_ptr::put_void wie gezeigt.
COM-Funktionen, die einen bestimmten Schnittstellenzeiger zurückgeben
Die D3D11CreateDevice-Funktion gibt einen ID3D11Device-Schnittstellenzeiger über den dritten von letzten Parameter zurück, der den Typ ID3D11Device** aufweist. Verwenden Sie für Funktionen dieser Art, die einen bestimmten Schnittstellenzeiger zurückgeben, com_ptr::put.
winrt::com_ptr<ID3D11Device> device;
D3D11CreateDevice(
...
device.put(),
...);
Das Codebeispiel im Abschnitt davor zeigt, wie die unformatierte D2D1CreateFactory-Funktion aufgerufen wird. Aber in Wirklichkeit wird, wenn im Codebeispiel für dieses Thema D2D1CreateFactory aufgerufen wird, eine Hilfsfunktionenvorlage verwendet, die die direkte API umschließt, sodass das Codebeispiel tatsächlich com_ptr::put verwendet.
winrt::com_ptr<ID2D1Factory1> factory;
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
options,
factory.put());
COM-Funktionen, die einen Schnittstellenzeiger als IUnknown zurückgeben
Die DWriteCreateFactory-Funktion gibt einen DirectWrite-Factoryschnittstellenzeiger über den letzten Parameter zurück, der den Typ "IUnknown " aufweist. Verwenden Sie für eine solche Funktion com_ptr::put, reinterpretieren Sie es jedoch als IUnknown.
DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(dwriteFactory2),
reinterpret_cast<IUnknown**>(dwriteFactory2.put()));
Erneutes Sitzen eines winrt::com_ptr
Important
Wenn Sie über ein winrt::com_ptr verfügen, das bereits platziert ist (der interne rohe Zeiger hat bereits ein Ziel), und Sie möchten es erneut auf ein anderes Objekt verweisen, dann müssen Sie es zuerst zuweisen nullptr , wie im folgenden Codebeispiel gezeigt. Andernfalls macht Sie ein bereits belegter com_ptr auf das Problem aufmerksam (wenn Sie com_ptr::put oder com_ptr::put_void aufrufen), indem es per Assertion meldet, dass sein interner Zeiger nicht Null ist.
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()));
Behandeln von HRESULT-Fehlercodes
Rufen Sie "winrt::check_hresult" auf, um den Wert eines von einer COM-Funktion zurückgegebenen HRESULT zu überprüfen und eine Ausnahme auszuwerfen, die einen Fehlercode darstellt.
winrt::check_hresult(D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(factory),
options,
factory.put_void()));
COM-Funktionen, die einen bestimmten Schnittstellenzeiger verwenden
Sie können die funktion com_ptr::get aufrufen, um Ihre com_ptr an eine Funktion zu übergeben, die einen bestimmten Schnittstellenzeiger desselben Typs verwendet.
... ExampleFunction(
winrt::com_ptr<ID2D1Factory1> const& factory,
winrt::com_ptr<IDXGIDevice> const& dxdevice)
{
...
winrt::check_hresult(factory->CreateDevice(dxdevice.get(), ...));
...
}
COM-Funktionen, die einen IUnknown-Schnittstellenzeiger verwenden
Sie können com_ptr::get verwenden, um Ihre com_ptr an eine Funktion zu übergeben, die einen IUnknown-Schnittstellenzeiger verwendet.
Sie können die winrt::get_unknown freie Funktion verwenden, um die Adresse (d. h. einen Zeiger auf) die zugrunde liegende unformatierte IUnknown-Schnittstelle eines Objekts eines projizierten Typs zurückzugeben. Sie können diese Adresse dann an eine Funktion übergeben, die einen IUnknown-Schnittstellenzeiger verwendet.
Informationen zu projizierten Typen finden Sie unter Verwenden von APIs mit C++/WinRT.
Ein Codebeispiel für get_unknown finden Sie unter winrt::get_unknown oder die vollständige Quellcodeauflistung einer minimalen Direct2D-Anwendung in diesem Thema.
Übergabe und Rückgabe von COM-Smartpointern
Eine Funktion, die einen intelligenten COM-Zeiger in Form eines winrt::com_ptr verwendet, sollte dies durch Konstantenverweis oder durch Verweis tun.
... GetDxgiFactory(winrt::com_ptr<ID3D11Device> const& device) ...
... CreateDevice(..., winrt::com_ptr<ID3D11Device>& device) ...
Eine Funktion, die einen winrt::com_ptr zurückgibt, sollte dies als Wert tun.
winrt::com_ptr<ID2D1Factory1> CreateFactory() ...
Abfragen eines intelligenten COM-Zeigers für eine andere Schnittstelle
Sie können die funktion com_ptr::as verwenden, um einen intelligenten COM-Zeiger für eine andere Schnittstelle abzufragen. Die Funktion löst eine Ausnahme aus, wenn die Abfrage nicht erfolgreich ist.
void ExampleFunction(winrt::com_ptr<ID3D11Device> const& device)
{
...
winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };
...
}
Alternativ können Sie com_ptr::try_as verwenden, der einen Wert zurückgibt, mit dem Sie überprüfen nullptr können, ob die Abfrage erfolgreich war.
Vollständige Quellcodeauflistung einer minimalen Direct2D-Anwendung
Hinweis
Informationen zum Einrichten von Visual Studio für die C++/WinRT-Entwicklung , einschließlich der Installation und Verwendung der C++/WinRT Visual Studio Extension (VSIX) und des NuGet-Pakets (die zusammen Projektvorlage und Buildunterstützung bereitstellen) finden Sie unter Visual Studio Unterstützung für C++/WinRT.
Wenn Sie dieses Quellcodebeispiel erstellen und ausführen möchten, installieren (oder aktualisieren Sie) zuerst die neueste Version der C++/WinRT-Visual Studio Extension (VSIX). Weitere Informationen finden Sie weiter oben. Erstellen Sie dann in Visual Studio eine neue Core-App (C++/WinRT).
Direct2D ist ein vernünftiger Name für das Projekt, aber Sie können es beliebig benennen. Richten Sie sich auf die neueste allgemein verfügbare (d. h. keine Vorschau)-Version des Windows SDK.
Schritt 1. Bearbeiten pch.h
Öffnen Sie pch.h, und fügen Sie #include <unknwn.h> direkt nach dem Einfügen von windows.h hinzu. Dies liegt daran, dass wir winrt::get_unknown verwenden. Es empfiehlt sich, #include <unknwn.h> immer dann explizit zu verwenden, wenn Sie winrt::get_unknown verwenden, auch wenn diese Kopfzeile von einem anderen Header eingeschlossen wurde.
Hinweis
Wenn Sie diesen Schritt auslassen, wird der Buildfehler "get_unknown" angezeigt: Bezeichner nicht gefunden.
Schritt 2. Bearbeiten App.cpp
Öffnen Sie App.cpp, löschen Sie den gesamten Inhalt und fügen Sie die nachstehende Auflistung ein.
Der folgende Code verwendet nach Möglichkeit die Funktion "winrt::com_ptr::capture" .
WINRT_ASSERT ist eine Makrodefinition und wird auf _ASSERTE erweitert.
#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>());
}
Arbeiten mit COM-Typen, z. B. BSTR und VARIANT
Wie Sie sehen können, bietet C++/WinRT Unterstützung für die Implementierung und das Aufrufen von COM-Schnittstellen. Für die Verwendung von COM-Typen wie BSTR und VARIANT empfehlen wir, Wrapper zu verwenden, die von den Windows Implementierungsbibliotheken (WIL) bereitgestellt werden, z. B. wil::unique_bstr und wil::unique_variant (die Ressourcenlebensdauern verwalten).
WIL ersetzt Frameworks wie die Active Template Library (ATL) und die COM-Unterstützung des Visual C++-Compilers. Und wir empfehlen dies eher, als eigene Wrapper zu schreiben oder COM-Typen wie BSTR und VARIANT in ihrer Rohform zu verwenden (zusammen mit den entsprechenden APIs).
Vermeiden von Namespacekonflikten
In C++/WinRT ist es üblich, using-Direktiven großzügig zu verwenden – wie das Codebeispiel in diesem Thema zeigt. In einigen Fällen kann dies jedoch zu dem Problem führen, dass kollidierende Namen in den globalen Namespace importiert werden. Hier finden Sie ein Beispiel dafür.
C++/WinRT enthält einen Typ namens winrt::Windows::Foundation::IUnknown; während COM einen Typ namens ::IUnknown definiert. Berücksichtigen Sie daher den folgenden Code in einem C++/WinRT-Projekt, das COM-Header verwendet.
using namespace winrt::Windows::Foundation;
...
void MyFunction(IUnknown*); // error C2872: 'IUnknown': ambiguous symbol
Der nicht qualifizierte Name IUnknown kollidiert im globalen Namespace, daher der mehrdeutige Symbolcompilerfehler. Stattdessen können Sie die C++/WinRT-Version des Namens wie folgt in den Winrt-Namespace isolieren.
namespace winrt
{
using namespace Windows::Foundation;
}
...
void MyFunctionA(IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.
Oder, wenn Sie den Komfort von using namespace winrt möchten, können Sie das tun. Sie müssen nur die globale Version von IUnknown wie folgt qualifizieren.
using namespace winrt;
namespace winrt
{
using namespace Windows::Foundation;
}
...
void MyFunctionA(::IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.
Dies funktioniert natürlich mit jedem C++/WinRT-Namespace.
namespace winrt
{
using namespace Windows::Storage;
using namespace Windows::System;
}
Sie können dann auf winrt::Windows::Storage::StorageFile verweisen, z. B. wie winrt::StorageFile.
Wichtige APIs
Windows developer