Nutzen von APIs mit C++/WinRT
In diesem Thema wird die Nutzung von C++/WinRT-APIs veranschaulicht. Dabei spielt es keine Rolle, ob diese APIs zu Windows gehören oder von einem Drittanbieter oder von dir selbst implementiert wurden.
Wichtig
Damit die Codebeispiele in diesem Thema kurz sind und Sie sie leicht ausprobieren können, können Sie sie reproduzieren, indem Sie ein neues Windows-Konsolenanwendungsprojekt (C++/WinRT) erstellen und den Code per Kopieren und Einfügen einfügen. Allerdings können Sie nicht willkürliche benutzerdefinierte Windows-Runtime-Typen (von Drittanbietern) aus einer ungepackten App wie dieser verarbeiten. Auf diese Weise können nur Windows-Typen verarbeitet werden.
Um benutzerdefinierte Windows-Runtime-Typen (von Drittanbietern) über eine Konsolen-App zu verarbeiten, müssen Sie der App eine Paketidentität geben, damit sie die Registrierung der verarbeiteten benutzerdefinierten Typen auflösen kann. Weitere Informationen finden Sie unter Paketerstellungsprojekt für Windows-Anwendungen.
Alternativ können Sie ein neues Projekt aus den Projektvorlagen Leere App (C++/WinRT) , Core App (C++/WinRT) oder Komponente für Windows-Runtime (C++/WinRT) erstellen. Diese App-Typen verfügen bereits über eine Paketidentität.
API in einem Windows-Namespace
Dies ist der gängigste Anwendungsfall für die Nutzung einer Windows-Runtime-API. Für jeden Typ in einem Windows-Namespace, der in den Metadaten definiert ist, definiert C++/WinRT ein für C++ geeignetes Äquivalent (den projizierten Typ). Ein projizierter Typ besitzt den gleichen vollqualifizierten Namen wie der Windows-Typ, wird jedoch unter Verwendung der C++-Syntax im C++-Namespace winrt platziert. Windows::Foundation::Uri wird beispielsweise in C++/WinRT als winrt::Windows::Foundation::Uri projiziert.
Hier siehst du ein einfaches Codebeispiel: Wenn du die folgenden Codebeispiele kopieren und direkt in die Hauptquellcodedatei eines Projekts vom Typ Windows-Konsolenanwendung (C++/WinRT) einfügen möchtest, musst du in den Projekteigenschaften zuerst die Option Vorkompilierte Header nicht verwenden festlegen.
// main.cpp
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
int main()
{
winrt::init_apartment();
Uri contosoUri{ L"http://www.contoso.com" };
Uri combinedUri = contosoUri.CombineUri(L"products");
}
Der enthaltene Header winrt/Windows.Foundation.h
ist Teil des SDK und befindet sich im Ordner %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt\
. Die Header in diesem Ordner enthalten für C++/WinRT projizierte Windows-Namespacetypen. In diesem Beispiel enthält winrt/Windows.Foundation.h
den Typ winrt::Windows::Foundation::Uri. Dies ist der projizierte Typ für die Laufzeitklasse Windows::Foundation::Uri.
Tipp
Wenn du einen Typ aus einem Windows-Namespace verwenden möchtest, musst du den C++/WinRT-Header einschließen, der diesem Namespace entspricht. Die using namespace
-Anweisungen sind optional, aber praktisch.
Im obigen Codebeispiel wird nach der Initialisierung von C++/WinRT ein Wert des projizierten Typs winrt::Windows::Foundation::Uri mit Stapelzuordnung über einen seiner öffentlich dokumentierten Konstruktoren (in diesem Beispiel Uri(String)) zugewiesen. Für diesen häufigsten Anwendungsfall sind in der Regel keine weiteren Schritte erforderlich. Projizierte C++/WinRT-Typwerte können wie eine Instanz des tatsächlichen Windows-Runtime-Typs behandelt werden, da sie über genau die gleichen Member verfügen.
Der projizierte Wert ist genau genommen ein Proxy und im Grunde lediglich ein intelligenter Zeiger auf ein Unterstützungsobjekt. Die Konstruktoren des projizierten Werts rufen RoActivateInstance auf, um eine Instanz der zugrunde liegenden Windows-Runtime-Klasse (in diesem Fall Windows.Foundation.Uri) zu erstellen und die Standardschnittstelle dieses Objekts im neuen projizierten Wert zu speichern. Wie unten zu sehen werden die Aufrufe der Member des projizierten Werts tatsächlich über den intelligenten Zeiger an das Unterstützungsobjekt delegiert. Hier erfolgen die Zustandsänderungen.
Wenn der Wert contosoUri
außerhalb des zulässigen Bereichs liegt, wird er gelöscht und gibt den Verweis auf die Standardschnittstelle frei. Ist dieser Verweis der letzte Verweis auf das Windows-Runtime-Unterstützungsobjekt Windows.Foundation.Uri, wird auch das Unterstützungsobjekt gelöscht.
Tipp
Ein projizierter Typ ist ein Wrapper für einen Windows-Runtime-Typ, der es ermöglicht, seine APIs zu nutzen. Eine projizierte Schnittstelle ist beispielsweise ein Wrapper für eine Windows-Runtime-Schnittstelle.
C++/WinRT-Projektionsheader
Um Windows-Namespace-APIs über C++/WinRT nutzen zu können, müssen Header aus dem Ordner %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt
eingeschlossen werden. Sie müssen die Header einschließen, die den von Ihnen verwendeten Namespaces entsprechen.
Ein Beispiel: Für den Namespace Windows::Security::Cryptography::Certificates befinden sich die entsprechenden C++/WinRT-Typdefinitionen in winrt/Windows.Security.Cryptography.Certificates.h
. Durch Einbinden dieses Headers erhalten Sie Zugriff auf alle Typen im Namespace Windows::Security::Cryptography::Certificates.
Manchmal enthält ein Namespace-Header Teile verwandter Namespace-Header, Sie sollten sich jedoch nicht auf dieses Implementierungsdetails verlassen. Schließen Sie die Header explizit für die verwendeten Namespaces ein.
Die Methode Certificate::GetCertificateBlob gibt beispielsweise eine Windows::Storage::Streams::IBuffer-Schnittstelle zurück.
Bevor Sie die Methode Certificate::GetCertificateBlob aufrufen, müssen Sie die Namespace-Headerdatei winrt/Windows.Storage.Streams.h
einschließen, um sicherzustellen, dass Sie die zurückgegebene Windows::Storage::Streams::IBuffer Schnittstelle erhalten und bearbeiten können.
Build-Fehler sind häufig darauf zurückzuführen, dass vergessen wird, die erforderlichen Namespace-Header einzuschließen, bevor Typen in diesem Namespace verwendet werden.
Zugreifen auf Member über das Objekt, eine Schnittstelle oder die ABI
Mit der C++/WinRT-Projektion handelt es sich bei der Laufzeitdarstellung einer Windows-Runtime-Klasse um nichts weiter als um die zugrunde liegenden ABI-Schnittstellen. Der Einfachheit halber kannst du für Klassen aber so programmieren, wie dies vom jeweiligen Autor vorgesehen war. So kannst du beispielsweise die Methode ToString eines URI so aufrufen, als ob es sich um eine Methode der Klasse handelt. (Tatsächlich ist es jedoch eine Methode der separaten Schnittstelle IStringable.)
WINRT_ASSERT
ist eine Makrodefinition, die auf _ASSERTE erweitert wird.
Uri contosoUri{ L"http://www.contoso.com" };
WINRT_ASSERT(contosoUri.ToString() == L"http://www.contoso.com/"); // QueryInterface is called at this point.
Dies wird über eine Abfrage für die entsprechende Schnittstelle erreicht. Du hast aber stets die Kontrolle. Du kannst etwas Komfort für etwas mehr Leistung opfern, indem du die IStringable-Schnittstelle selbst abrufst und direkt verwendest. Im folgenden Codebeispiel wird ein tatsächlicher IStringable-Schnittstellenzeiger (über eine einmalige Abfrage) zur Laufzeit abgerufen. Danach wird ToString direkt aufgerufen, und weitere Aufrufe von QueryInterface werden vermieden.
...
IStringable stringable = contosoUri; // One-off QueryInterface.
WINRT_ASSERT(stringable.ToString() == L"http://www.contoso.com/");
Diese Technik kannst du verwenden, wenn du weißt, dass du mehrere Methoden für die gleiche Schnittstelle aufrufst.
Wenn du außerdem auf Member auf der ABI-Ebene zugreifen möchtest, ist auch das möglich. Wie das geht, siehst du im folgenden Codebeispiel. Weitere Informationen und Codebeispiele findest du unter Interoperabilität zwischen C++/WinRT und der ABI.
#include <Windows.Foundation.h>
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>
using namespace winrt::Windows::Foundation;
int main()
{
winrt::init_apartment();
Uri contosoUri{ L"http://www.contoso.com" };
int port{ contosoUri.Port() }; // Access the Port "property" accessor via C++/WinRT.
winrt::com_ptr<ABI::Windows::Foundation::IUriRuntimeClass> abiUri{
contosoUri.as<ABI::Windows::Foundation::IUriRuntimeClass>() };
HRESULT hr = abiUri->get_Port(&port); // Access the get_Port ABI function.
}
Verzögerte Initialisierung
In C++/WinRT verfügt jeder projizierte Typ über einen speziellen C++/WinRT-Konstruktor: std::nullptr_t. Abgesehen von dieser einen Ausnahme führen alle Konstruktoren eines projizierten Typs – einschließlich des Standardkonstruktors – zur Erstellung eines Windows-Runtime-Sicherungsobjekts und stellen einen intelligenten Zeiger darauf bereit. Diese Regel gilt also überall dort, wo der Standardkonstruktor verwendet wird, z.B. bei nicht initialisierten lokalen Variablen, nicht initialisierten globalen Variablen und nicht initialisierten Membervariablen.
Es ist aber auch möglich, eine Variable eines projizierten Typs zu erstellen, ohne dass dadurch ein Windows-Runtime-Sicherungsobjekt erstellt wird (und diese Arbeit auf einen späteren Zeitpunkt zu verschieben). Deklariere die Variable oder das Feld mit diesem speziellen C++/WinRT-Konstruktor std::nullptr_t (den die C++/WinRT-Projektion in jede Laufzeitklasse einfügt). Wir verwenden diesen speziellen Konstruktor mit m_gamerPicBuffer im folgenden Codebeispiel.
#include <winrt/Windows.Storage.Streams.h>
using namespace winrt::Windows::Storage::Streams;
#define MAX_IMAGE_SIZE 1024
struct Sample
{
void DelayedInit()
{
// Allocate the actual buffer.
m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
}
private:
Buffer m_gamerPicBuffer{ nullptr };
};
int main()
{
winrt::init_apartment();
Sample s;
// ...
s.DelayedInit();
}
Alle Konstruktoren des projizierten Typs (mit Ausnahme des std::nullptr_t-Konstruktors) bewirken die Erstellung eines Windows-Runtime-Unterstützungsobjekts. Dem std::nullptr_t-Konstruktor ist im Wesentlichen keine Aktion zugeordnet. Er erwartet, dass das projizierte Objekt zu einem späteren Zeitpunkt initialisiert wird. Du kannst dieses Verfahren also für eine effiziente verzögerte Initialisierung verwenden – unabhängig davon, ob eine Laufzeitklasse über einen Standardkonstruktor verfügt oder nicht.
Diese Überlegung betrifft auch andere Orte, an denen der Standardkonstruktor aufgerufen wird (etwa Vektoren und Zuordnungen). Sieh dir das folgende Codebeispiel an (Projekt vom Typ Leere App (C++/WinRT) erforderlich):
std::map<int, TextBlock> lookup;
lookup[2] = value;
Die Zuweisung erstellt einen neuen Textblock (TextBlock) und überschreibt ihn sofort mit value
. Hier ist die Lösung:
std::map<int, TextBlock> lookup;
lookup.insert_or_assign(2, value);
Siehe auch Auswirkungen des Standardkonstruktors auf Sammlungen.
Verwenden Sie nicht irrtümlicherweise die verzögerte Initialisierung
Rufen Sie den std::nullptr_t-Konstruktor nicht versehentlich auf. Bei der Konfliktauflösung durch den Compiler wird er vor den Factorykonstruktoren bevorzugt. Betrachten Sie beispielsweise diese beiden Laufzeitklassendefinitionen.
// GiftBox.idl
runtimeclass GiftBox
{
GiftBox();
}
// Gift.idl
runtimeclass Gift
{
Gift(GiftBox giftBox); // You can create a gift inside a box.
}
Angenommen, Sie möchten ein Geschenk (Gift) erstellen, das sich nicht in einem Geschenkkarton befindet (ein mit einer nicht initialisierten GiftBox erstelltes Gift). Betrachten wir zunächst die falsche Vorgehensweise. Wir wissen, dass ein Gift-Konstruktor vorhanden ist, der eine GiftBox akzeptiert. Falls wir jedoch für GiftBox einen NULL-Wert übergeben (den Gift-Konstruktor über eine einheitliche Initialisierung aufrufen, wie dies unten geschieht), erhalten wir nicht das gewünschte Ergebnis.
// These are *not* what you intended. Doing it in one of these two ways
// actually *doesn't* create the intended backing Windows Runtime Gift object;
// only an empty smart pointer.
Gift gift{ nullptr };
auto gift{ Gift(nullptr) };
Sie erhalten stattdessen ein nicht initialisiertes Gift. Sie erhalten kein Gift mit einer nicht initialisierten GiftBox. Hier ist die richtige Vorgehensweise.
// Doing it in one of these two ways creates an initialized
// Gift with an uninitialized GiftBox.
Gift gift{ GiftBox{ nullptr } };
auto gift{ Gift(GiftBox{ nullptr }) };
Im Beispiel für die falsche Vorgehensweise erfolgt die Auflösung durch Übergabe eines nullptr
-Literals zugunsten des Konstruktors mit verzögerter Initialisierung. Damit die Auflösung zugunsten des Factorykonstruktors erfolgt, muss der Typ des Parameters GiftBox lauten. Sie haben weiterhin die Möglichkeit, eine GiftBox mit explizit verzögerter Initialisierung zu übergeben, wie im Beispiel für die richtige Vorgehensweise gezeigt.
Das folgende Beispiel zeigt eine ebenfalls richtige Vorgehensweise, weil der Typ des Parameters „GiftBox“ und nicht std::nullptr_t lautet.
GiftBox giftBox{ nullptr };
Gift gift{ giftBox }; // Calls factory constructor.
Mehrdeutigkeit entsteht nur, wenn Sie ein nullptr
-Literal übergeben.
Verwenden Sie nicht irrtümlicherweise einen Kopierkonstruktor
Dieser Hinweis ähnelt dem Hinweis im obigen Abschnitt Verwenden Sie nicht irrtümlicherweise die verzögerte Initialisierung.
C++/WinRT-Projektion fügt zusätzlich zum Konstruktor mit verzögerter Initialisierung auch einen Kopierkonstruktor in jede Laufzeitklasse ein. Dies ist ein Konstruktor mit einem einzelnen Parameter, der den gleichen Typ wie das zu erstellende Objekt akzeptiert. Der resultierende intelligente Zeiger verweist auf das Windows-Runtime-Unterstützungsobjekt, auf das auch sein Konstruktorparameter verweist. Das Ergebnis sind zwei intelligente Zeiger-Objekte, die auf das gleiche Unterstützungsobjekt verweisen.
In den Codebeispielen wird die folgende Laufzeitklassendefinition verwendet.
// GiftBox.idl
runtimeclass GiftBox
{
GiftBox(GiftBox biggerBox); // You can place a box inside a bigger box.
}
Angenommen, wir möchten eine GiftBox in einer größeren GiftBox erstellen.
GiftBox bigBox{ ... };
// These are *not* what you intended. Doing it in one of these two ways
// copies bigBox's backing-object-pointer into smallBox.
// The result is that smallBox == bigBox.
GiftBox smallBox{ bigBox };
auto smallBox{ GiftBox(bigBox) };
Die richtige Vorgehensweise ist das explizite Aufrufen der Aktivierungsfactory.
GiftBox bigBox{ ... };
// These two ways call the activation factory explicitly.
GiftBox smallBox{
winrt::get_activation_factory<GiftBox, IGiftBoxFactory>().CreateInstance(bigBox) };
auto smallBox{
winrt::get_activation_factory<GiftBox, IGiftBoxFactory>().CreateInstance(bigBox) };
In einer Komponente für Windows-Runtime implementierte API
Dieser Abschnitt gilt unabhängig davon, ob du die Komponente selbst erstellt hast oder ob sie von einem Anbieter stammt.
Hinweis
Informationen zum Installieren und Verwenden der C++/WinRT Visual Studio-Erweiterung (VSIX) und des NuGet-Pakets (die zusammen die Projektvorlage und Buildunterstützung bereitstellen) findest du unter Visual Studio-Unterstützung für C++/WinRT.
Verweise in deinem Anwendungsprojekt auf die Windows-Runtime-Metadatendatei (.winmd
) der Komponente für Windows-Runtime, und führe den Buildvorgang aus. Im Zuge des Buildvorgangs generiert das Tool cppwinrt.exe
eine C++-Standardbibliothek, die die API-Oberfläche der Komponente vollständig beschreibt (bzw. projiziert). Anders ausgedrückt: Die generierte Bibliothek enthält die projizierten Typen für die Komponente.
Anschließend wird genau wie bei einem Windows-Namespacetyp ein Header eingeschlossen und der projizierte Typ über einen seiner Konstruktoren konstruiert. Der Startcode deines Anwendungsprojekts registriert die Laufzeitklasse, und der Konstruktor des projizierten Typs ruft RoActivateInstance auf, um die Laufzeitklasse der referenzierten Komponente zu aktivieren.
#include <winrt/ThermometerWRC.h>
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
ThermometerWRC::Thermometer thermometer;
...
};
Ausführlichere Informationen, Code und eine exemplarische Vorgehensweise für die Nutzung von APIs, die in eine Windows-Runtime-Komponente implementiert sind, finden Sie unter Windows-Runtime-Komponenten mit C++/WinRT und Erstellen von Ereignissen in C++/WinRT.
Im verwendenden Projekt implementierte API
Das Codebeispiel in diesem Abschnitt stammt aus dem Thema XAML-Steuerelemente: Binden an eine C++/WinRT-Eigenschaft. Dort finden Sie ausführlichere Informationen, Code und eine exemplarische Vorgehensweise für die Nutzung einer im verwendenden Projekt implementierten Laufzeitklasse.
Ein Typ, der über die XAML-UI genutzt wird, muss eine Laufzeitklasse sein (auch wenn er sich im gleichen Projekt wie der XAML-Code befindet). Für dieses Szenario wird auf der Grundlage der Windows-Runtime-Metadaten der Laufzeitklasse (.winmd
) ein projizierter Typ generiert. Auch hier fügen Sie einen Header ein, aber dann haben Sie die Wahl zwischen Version 1.0 oder Version 2.0 von C++/WinRT zum Erstellen der Instanz der Laufzeitklasse. Die Methode mit Version 1.0 verwendet winrt::make; die Methode mit Version 2.0 wird als einheitliche Konstruktion bezeichnet. Sehen wir uns beide nacheinander an.
Erstellen mithilfe von winrt::make
Beginnen wir mit der Standardmethode (C++/WinRT Version 1.0), da es sinnvoll ist, zumindest mit dem Muster vertraut zu sein. Der projizierte Typ wird über den zugehörigen Konstruktor vom Typ std::nullptr_t konstruiert. Dieser Konstruktor führt keine Initialisierung durch. Daher musst du der Instanz über die Hilfsfunktion winrt::make einen Wert zuweisen und alle notwendigen Konstruktorargumente übergeben. Eine Laufzeitklasse, die im gleichen Projekt implementiert wird wie der verwendende Code, muss weder registriert noch per Windows-Runtime/COM-Aktivierung instanziiert werden.
Eine vollständige exemplarische Vorgehensweise finden Sie unter XAML-Steuerelemente; Binden an eine C++/WinRT-Eigenschaft. Dieser Abschnitt zeigt Auszüge aus dieser exemplarischen Vorgehensweise.
// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
BookstoreViewModel MainViewModel{ get; };
}
}
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
private:
Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
};
...
// MainPage.cpp
...
#include "BookstoreViewModel.h"
MainPage::MainPage()
{
m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
...
}
Einheitliche Konstruktion
Mit C++/WinRT Version 2.0 und höher steht Ihnen eine optimierte Form der Konstruktion zur Verfügung, die als einheitliche Konstruktion bezeichnet wird (siehe Neuerungen und Änderungen in C++/WinRT 2.0).
Eine vollständige exemplarische Vorgehensweise finden Sie unter XAML-Steuerelemente; Binden an eine C++/WinRT-Eigenschaft. Dieser Abschnitt zeigt Auszüge aus dieser exemplarischen Vorgehensweise.
Um die einheitliche Konstruktion anstelle von winrt::make zu verwenden, benötigen Sie eine Aktivierungsfactory. Eine gute Möglichkeit, diese zu generieren, besteht darin, einen Konstruktor zu Ihrer IDL hinzuzufügen.
// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
MainPage();
BookstoreViewModel MainViewModel{ get; };
}
}
In MainPage.h
deklarieren und initialisieren Sie dann m_mainViewModel in nur einem Schritt, wie unten gezeigt.
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
private:
Bookstore::BookstoreViewModel m_mainViewModel;
...
};
}
...
Im MainPage-Konstruktor in MainPage.cpp
ist der Code m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
dann nicht erforderlich.
Weitere Informationen zur einheitlichen Konstruktion und Codebeispiele finden Sie unter Aktivieren von einheitlicher Konstruktion und direktem Implementierungszugriff.
Instanziieren und Zurückgeben projizierter Typen und Schnittstellen
Das folgende Beispiel zeigt, wie projizierte Typen und Schnittstellen in deinem verwendenden Projekt aussehen können. Vergiss nicht, dass ein projizierter Typ (wie in diesem Beispiel) von einem Tool generiert und nicht von dir selbst erstellt wird.
struct MyRuntimeClass : MyProject::IMyRuntimeClass, impl::require<MyRuntimeClass,
Windows::Foundation::IStringable, Windows::Foundation::IClosable>
MyRuntimeClass ist ein projizierter Typ. Projizierte Schnittstellen sind IMyRuntimeClass, IStringable und IClosable. In diesem Thema wurden die verschiedenen Methoden gezeigt, mit denen du einen projizierten Typ instanziieren kannst. Im Anschluss findest du eine Zusammenfassung anhand des Beispiels MyRuntimeClass.
// The runtime class is implemented in another compilation unit (it's either a Windows API,
// or it's implemented in a second- or third-party component).
MyProject::MyRuntimeClass myrc1;
// The runtime class is implemented in the same compilation unit.
MyProject::MyRuntimeClass myrc2{ nullptr };
myrc2 = winrt::make<MyProject::implementation::MyRuntimeClass>();
- Du kannst auf die Member aller Schnittstellen eines projizierten Typs zugreifen.
- Du kannst einen projizierten Typ an einen Aufrufer zurückgeben.
- Projizierte Typen und Schnittstellen werden von winrt::Windows::Foundation::IUnknown abgeleitet. Du kannst also IUnknown::as für einen projizierten Typ oder für eine projizierte Schnittstelle aufrufen, um nach anderen projizierten Schnittstellen zu suchen, die du ebenfalls verwenden oder an einen Aufrufer zurückgeben kannst. Die Memberfunktion as funktioniert wie QueryInterface.
void f(MyProject::MyRuntimeClass const& myrc)
{
myrc.ToString();
myrc.Close();
IClosable iclosable = myrc.as<IClosable>();
iclosable.Close();
}
Aktivierungsfactorys
Im Anschluss wird die einfache, direkte Methode für die Erstellung eines C++/WinRT-Objekts beschrieben:
using namespace winrt::Windows::Globalization::NumberFormatting;
...
CurrencyFormatter currency{ L"USD" };
Manchmal möchtest du aber vielleicht die Aktivierungsfactory selbst erstellen und anschließend auf deren Grundlage Objekte erstellen. Hier sind einige entsprechende Beispiele unter Verwendung der Funktionsvorlage winrt::get_activation_factory:
using namespace winrt::Windows::Globalization::NumberFormatting;
...
auto factory = winrt::get_activation_factory<CurrencyFormatter, ICurrencyFormatterFactory>();
CurrencyFormatter currency = factory.CreateCurrencyFormatterCode(L"USD");
using namespace winrt::Windows::Foundation;
...
auto factory = winrt::get_activation_factory<Uri, IUriRuntimeClassFactory>();
Uri uri = factory.CreateUri(L"http://www.contoso.com");
Bei den Klassen in den beiden obigen Beispielen handelt es sich um Typen aus einem Windows-Namespace. Im nächsten Beispiel ist ThermometerWRC::Thermometer ein benutzerdefinierter Typ, der in einer Komponente für Windows-Runtime implementiert ist.
auto factory = winrt::get_activation_factory<ThermometerWRC::Thermometer>();
ThermometerWRC::Thermometer thermometer = factory.ActivateInstance<ThermometerWRC::Thermometer>();
Mehrdeutigkeiten bei Membern/Typen
Wenn eine Memberfunktion den gleichen Namen hat wie ein Typ, entsteht Mehrdeutigkeit. Die Regeln für eine Suche nach nicht qualifizierten C++-Namen in Memberfunktionen führen dazu, dass zuerst in der Klasse und dann erst in Namespaces gesucht wird. Die Regel substitution failure is not an error (SFINAE) wird nicht angewendet (sie wird während der Überladungsauflösung von Funktionsvorlagen angewendet). Wenn also der Name innerhalb der Klasse nicht sinnvoll ist, sucht der Compiler nicht weiter nach einem besseren Treffer, sondern meldet einfach einen Fehler.
struct MyPage : Page
{
void DoWork()
{
// This doesn't compile. You get the error
// "'winrt::Windows::Foundation::IUnknown::as':
// no matching overloaded function found".
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<Style>() };
}
}
Im obigen Beispiel geht der Compiler davon aus, dass FrameworkElement.Style() (eine Memberfunktion in C++/WinRT) als Vorlagenparameter an IUnknown::as übergeben wird. Die Lösung hierfür besteht darin, die Interpretation des Namens Style
als Typ Windows::UI::Xaml::Style zu erzwingen.
struct MyPage : Page
{
void DoWork()
{
// One option is to fully-qualify it.
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<Windows::UI::Xaml::Style>() };
// Another is to force it to be interpreted as a struct name.
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<struct Style>() };
// If you have "using namespace Windows::UI;", then this is sufficient.
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<Xaml::Style>() };
// Or you can force it to be resolved in the global namespace (into which
// you imported the Windows::UI::Xaml namespace when you did
// "using namespace Windows::UI::Xaml;".
auto style = Application::Current().Resources().
Lookup(L"MyStyle").as<::Style>();
}
}
Bei der Suche nach nicht qualifizierten Namen gibt es eine spezielle Ausnahme, wenn ::
auf den Namen folgt – in diesem Fall werden Funktionen, Variablen und Enumerationswerte ignoriert. Damit kannst du beispielsweise Folgendes ausführen.
struct MyPage : Page
{
void DoSomething()
{
Visibility(Visibility::Collapsed); // No ambiguity here (special exception).
}
}
Der Aufruf von Visibility()
wird in den Memberfunktionsnamen von UIElement.Visibility aufgelöst. Aber im Parameter Visibility::Collapsed
folgt ::
auf das Wort Visibility
. Daher wird der Methodenname ignoriert, und der Compiler findet die Enumerationsklasse.
Wichtige APIs
- Funktion „QueryInterface“
- Funktion „RoActivateInstance“
- Klasse „Windows::Foundation::Uri“
- Funktionsvorlage „winrt::get_activation_factory“
- Funktionsvorlage „winrt::make“
- Struktur „winrt::Windows::Foundation::IUnknown“