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.
In diesem Thema wird gezeigt, wie Sie C++/WinRT-APIs mithilfe der winrt::implements-Basisstruktur direkt oder indirekt erstellen. Synonyme für erstellen in diesem Kontext sind erzeugen oder implementieren. In diesem Thema werden die folgenden Szenarien für die Implementierung von APIs für einen C++/WinRT-Typ in dieser Reihenfolge behandelt.
Hinweis
Dieses Thema berührt das Thema der Windows-Runtime-Komponenten, jedoch nur im Kontext von C++/WinRT. Wenn Sie nach Inhalten zu Windows-Runtime Komponenten suchen, die alle Windows-Runtime Sprachen umfassen, lesen Sie Windows-Runtime Komponenten.
- Sie erstellen keine Windows-Runtime Klasse (Laufzeitklasse). Sie möchten lediglich eine oder mehrere Windows-Runtime Schnittstellen für den lokalen Verbrauch in Ihrer App implementieren. Sie leiten direkt von winrt::implements in diesem Fall ab und implementieren Funktionen.
- Sie erstellen eine Laufzeitklasse. Möglicherweise erstellen Sie eine Komponente, die von einer App genutzt werden soll. Oder Sie erstellen möglicherweise einen Typ, der über die XAML-Benutzeroberfläche (UI) genutzt werden soll, und in diesem Fall implementieren und verwenden Sie eine Laufzeitklasse innerhalb derselben Kompilierungseinheit. In diesen Fällen können Sie mit den Tools Klassen generieren, die von winrt::implements abgeleitet werden.
In beiden Fällen wird der Typ, der Ihre C++/WinRT-APIs implementiert, als Implementierungstyp bezeichnet.
Important
Es ist wichtig, das Konzept eines Implementierungstyps von dem eines projizierten Typs zu unterscheiden. Der projizierte Typ wird in "Nutzen von APIs mit C++/WinRT" beschrieben.
Wenn Sie nicht eine Laufzeitklasse erstellen
Das einfachste Szenario besteht darin, dass Ihr Typ eine Windows-Runtime Schnittstelle implementiert, und Sie verwenden diesen Typ innerhalb derselben App. In diesem Fall muss Ihr Typ keine Laufzeitklasse sein. nur eine normale C++-Klasse. Sie können beispielsweise eine WinUI 3-Desktop-App basierend auf Microsoft::UI::Xaml::Application schreiben.
Wenn Ihr Typ von der XAML-Benutzeroberfläche referenziert wird, muss er eine Laufzeitklasse sein, obwohl er sich im selben Projekt wie die XAML befindet. In diesem Fall finden Sie den Abschnitt "Wenn Sie eine Laufzeitklasse erstellen, auf die in der XAML-Benutzeroberfläche verwiesen wird".
Hinweis
Informationen zum Installieren und Verwenden 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.
In Visual Studio veranschaulicht die Projektvorlage Leere, gepackte App (WinUI 3 für Desktop) für C++ das WinUI 3-Anwendungsmuster. Die App-Klasse wird von Microsoft::UI::Xaml::Application abgeleitet, und der Einstiegspunkt ruft die Startmethode auf.
#include "App.xaml.h"
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
winrt::init_apartment();
::winrt::Microsoft::UI::Xaml::Application::Start(
[](auto&&) { ::winrt::make<App>(); });
}
Die App-Klasse erstellt eine Microsoft::UI::Xaml::Window und aktiviert sie in OnLaunched.
// App.xaml.h
struct App : AppT<App>
{
App();
void OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&);
private:
winrt::Microsoft::UI::Xaml::Window window{ nullptr };
};
// App.xaml.cpp
void App::OnLaunched(LaunchActivatedEventArgs const&)
{
window = make<MainWindow>();
window.Activate();
}
C++/WinRT verfügt über die Basisstrukturvorlage winrt::implements , um die Implementierung einer Schnittstelle (oder mehrerer) zu vereinfachen, ohne auf die PROGRAMMIERUNG im COM-Stil zurückgreifen zu müssen. Sie leiten ihren Typ einfach von Implementierungen ab und implementieren dann die Funktionen der Schnittstelle. Hier ist ein Beispiel, das eine benutzerdefinierte Schnittstelle implementiert.
struct MyType : implements<MyType, IStringable>
{
hstring ToString()
{
return L"MyType";
}
};
Wenn Sie eine Laufzeitklasse in einer Windows-Runtime Komponente erstellen
Wenn Ihr Typ in einer Windows-Runtime Komponente für die Nutzung aus einer anderen Binärdatei verpackt ist (die andere Binärdatei ist in der Regel eine Anwendung), muss ihr Typ eine Laufzeitklasse sein. Sie deklarieren eine Laufzeitklasse in einer Datei der Microsoft Interface Definition Language (IDL) (.idl) (siehe Auslagern von Laufzeitklassen in MIDL-Dateien (.idl)).
Jede IDL-Datei erzeugt eine .winmd-Datei, und Visual Studio führt all diese in einer einzelnen Datei mit demselben Namen wie Ihr Stamm-Namespace zusammen. Diese endgültige .winmd Datei ist die, auf die die Verbraucher Ihrer Komponente verweisen.
Hier ist ein Beispiel für das Deklarieren einer Laufzeitklasse in einer IDL-Datei.
// MyRuntimeClass.idl
namespace MyProject
{
runtimeclass MyRuntimeClass
{
// Declaring a constructor (or constructors) in the IDL causes the runtime class to be
// activatable from outside the compilation unit.
MyRuntimeClass();
String Name;
}
}
Diese IDL definiert eine Windows-Runtimeklasse (Laufzeitklasse). Eine Laufzeitklasse ist ein Typ, der über moderne COM-Schnittstellen aktiviert und genutzt werden kann, in der Regel über ausführbare Grenzen hinweg. Wenn Sie Ihrem Projekt eine IDL-Datei hinzufügen und erstellen, generieren die C++/WinRT-Toolkette (midl.exe und cppwinrt.exe) einen Implementierungstyp für Sie. Ein Beispiel für den IDL-Dateiworkflow in Aktion finden Sie unter XAML-Steuerelemente; Binden an eine C++/WinRT-Eigenschaft.
Mit dem obigen Beispiel-IDL ist der Implementierungstyp ein C++-Struktur-Stub mit dem Namen "winrt::MyProject::implementation::MyRuntimeClass " in Quellcodedateien namens \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h und MyRuntimeClass.cpp.
Der Implementierungstyp sieht wie folgt aus.
// MyRuntimeClass.h
...
namespace winrt::MyProject::implementation
{
struct MyRuntimeClass : MyRuntimeClassT<MyRuntimeClass>
{
MyRuntimeClass() = default;
winrt::hstring Name();
void Name(winrt::hstring const& value);
};
}
// winrt::MyProject::factory_implementation::MyRuntimeClass is here, too.
Beachten Sie, dass hier das Muster des F-gebundenen Polymorphismus verwendet wird (MyRuntimeClass verwendet sich selbst als Vorlagenargument an seine Basisklasse MyRuntimeClassT). Dies wird auch als Curiously Recurring Template Pattern (CRTP) bezeichnet. Wenn Sie der Vererbungskette nach oben folgen, stoßen Sie auf MyRuntimeClass_base.
Sie können die Implementierung einfacher Eigenschaften vereinfachen, indem Sie Windows Implementierungsbibliotheken (WIL) verwenden. Gehen Sie dazu wie folgt vor:
// MyRuntimeClass.h
...
namespace winrt::MyProject::implementation
{
struct MyRuntimeClass : MyRuntimeClassT<MyRuntimeClass>
{
MyRuntimeClass() = default;
wil::single_threaded_rw_property<winrt::hstring> Name;
};
}
Siehe "Einfache Eigenschaften".
template <typename D, typename... I>
struct MyRuntimeClass_base : implements<D, MyProject::IMyRuntimeClass, I...>
Also ist in diesem Szenario erneut die Strukturvorlage winrt::implements die Basis der Vererbungshierarchie.
Weitere Details, Code und eine exemplarische Vorgehensweise zum Erstellen von APIs in einer Windows-Runtime Komponente finden Sie unter Windows-Runtime Komponenten mit C++/WinRT- und Author-Ereignissen in C++/WinRT.
Wenn Sie eine Laufzeitklasse erstellen, auf die in der XAML-Benutzeroberfläche verwiesen wird
Wenn Ihr Typ in Ihrer XAML-Benutzeroberfläche referenziert wird, muss er eine Laufzeitklasse sein, obwohl er sich im selben Projekt wie das XAML befindet. Obwohl sie in der Regel über ausführbare Grenzen hinweg aktiviert werden, kann eine Laufzeitklasse stattdessen innerhalb der Kompilierungseinheit verwendet werden, die sie implementiert.
In diesem Szenario erstellen und nutzen Sie die APIs. Das Verfahren für die Implementierung der Laufzeitklasse ist im Wesentlichen identisch mit der für eine Windows-Runtime Komponente. Lesen Sie also den vorherigen Abschnitt – Wenn Sie eine Laufzeitklasse in einer Windows-Runtime Komponente erstellen. Das einzige Detail, das sich unterscheidet, ist, dass die C++/WinRT-Toolkette aus der IDL nicht nur einen Implementierungstyp, sondern auch einen projizierten Typ generiert. Es ist wichtig, sich bewusst zu machen, dass es in diesem Szenario mehrdeutig sein kann, nur "MyRuntimeClass" zu sagen; es gibt mehrere Elemente mit diesem Namen, die unterschiedlicher Art sind.
- MyRuntimeClass ist der Name einer Laufzeitklasse. Aber dies ist wirklich eine Abstraktion: in IDL deklariert und in einer Programmiersprache implementiert.
-
MyRuntimeClass ist der Name der C++-Struktur winrt::MyProject::implementation::MyRuntimeClass, die die C++/WinRT-Implementierung der Laufzeitklasse ist. Wie wir gesehen haben, ist diese Struktur nur im Implementierungsprojekt vorhanden, wenn es separate Implementierungs- und Nutzungsprojekte gibt. Dies ist der Implementierungstyp oder die Implementierung. Dieser Typ wird (durch das Tool
cppwinrt.exe) in den Dateien\MyProject\MyProject\Generated Files\sources\MyRuntimeClass.hundMyRuntimeClass.cppgeneriert. -
MyRuntimeClass ist der Name des projizierten Typs in Form der C++-Struktur winrt::MyProject::MyRuntimeClass. Wenn es separate Implementierungs- und Nutzungsprojekte gibt, ist diese Struktur nur im verbrauchenden Projekt vorhanden. Dies ist der projizierte Typ oder die Projektion. Dieser Typ wird (von
cppwinrt.exe) in der Datei\MyProject\MyProject\Generated Files\winrt\impl\MyProject.2.hgeneriert.
Hier sind die Teile des projizierten Typs, die für dieses Thema relevant sind.
// MyProject.2.h
...
namespace winrt::MyProject
{
struct MyRuntimeClass : MyProject::IMyRuntimeClass
{
MyRuntimeClass(std::nullptr_t) noexcept {}
MyRuntimeClass();
};
}
Ein Beispiel für eine exemplarische Vorgehensweise zum Implementieren der INotifyPropertyChanged-Schnittstelle in einer Laufzeitklasse finden Sie unter XAML-Steuerelemente; Binden an eine C++/WinRT-Eigenschaft.
Das Verfahren zur Nutzung Ihrer Laufzeitklasse in diesem Szenario wird in APIs mit C++/WinRT nutzen beschrieben.
Auslagern von Laufzeitklassen in MIDL-Dateien (.idl)
Die Visual Studio Projekt- und Elementvorlagen erzeugen eine separate IDL-Datei für jede Laufzeitklasse. Dies gibt eine logische Entsprechung zwischen einer IDL-Datei und den generierten Quellcodedateien.
Wenn Sie jedoch alle Laufzeitklassen Ihres Projekts in eine einzelne IDL-Datei konsolidieren, kann dies die Buildzeit erheblich verbessern. Wenn Sie andernfalls komplexe (oder zirkuläre) import Abhängigkeiten darunter haben würden, kann die Konsolidierung tatsächlich erforderlich sein. Und Sie können es einfacher finden, Ihre Laufzeitklassen zu erstellen und zu überprüfen, wenn sie zusammen sind.
Konstruktoren von Runtimeklassen
Hier sind einige Punkte, die Sie aus den oben gezeigten Einträgen mitnehmen können.
- Jeder Konstruktor, den Sie in Ihrer IDL deklarieren, bewirkt, dass ein Konstruktor sowohl für ihren Implementierungstyp als auch für den projizierten Typ generiert wird. IDL-deklarierte Konstruktoren werden verwendet, um die Laufzeitklasse aus einer anderen Kompilierungseinheit zu nutzen.
- Unabhängig davon, ob Sie in IDL deklarierte Konstruktoren haben oder nicht, wird in Ihrem projizierten Typ eine Konstruktorüberladung generiert, die std::nullptr_t akzeptiert. Das Aufrufen des std::nullptr_t-Konstruktors ist die erste von zwei Schritten bei der Verwendung der Laufzeitklasse aus derselben Kompilierungseinheit. Weitere Details und ein Codebeispiel finden Sie unter Verwenden von APIs mit C++/WinRT.
- Wenn Sie die Laufzeitklasse aus derselben Kompilierungseinheit verwenden, können Sie auch nicht standardmäßige Konstruktoren direkt auf den Implementierungstyp implementieren (was sich daran erinnert, dass sie sich befindet
MyRuntimeClass.h).
Hinweis
Wenn Sie davon ausgehen, dass Ihre Laufzeitklasse von einer anderen Kompilierungseinheit (die üblich ist) verwendet wird, schließen Sie Konstruktoren (mindestens einen Standardkonstruktor) in Ihre IDL ein. Auf diese Weise erhalten Sie neben Ihrem Implementierungstyp auch eine Factory-Implementierung.
Wenn Sie Ihre Laufzeitklasse nur innerhalb derselben Kompilierungseinheit erstellen und nutzen möchten, deklarieren Sie in Ihrer IDL keine Konstruktoren. Sie benötigen keine Factoryimplementierung, und eine wird nicht generiert. Der Standardkonstruktor Ihres Implementierungstyps wird gelöscht, Sie können ihn aber ganz einfach bearbeiten und stattdessen als Standard festlegen.
Wenn Sie Ihre Laufzeitklasse nur innerhalb derselben Kompilierungseinheit erstellen und nutzen möchten, und Sie Konstruktorparameter benötigen, erstellen Sie dann die Konstruktoren, die Sie direkt für den Implementierungstyp benötigen.
Methoden, Eigenschaften und Ereignisse der Laufzeitklasse
Wir haben gesehen, dass der Workflow darin besteht, IDL zu verwenden, um Ihre Laufzeitklasse und ihre Member zu deklarieren, und dass die Tools anschließend Prototypen und Stubimplementierungen für Sie generieren. Wie bei diesen automatisch generierten Prototypen für die Member Ihrer Laufzeitklasse können Sie sie bearbeiten, sodass sie verschiedene Typen von den Typen übergeben, die Sie in Ihrer IDL deklarieren. Sie können dies jedoch nur so lange tun, wie der typ, den Sie in IDL deklarieren, an den Typ weitergeleitet werden kann, den Sie in der implementierten Version deklarieren.
Im Folgenden finden Sie einige Beispiele hierfür.
- Sie können Parametertypen lockern. Wenn Ihre Methode beispielsweise in IDL eine SomeClass als Parameter annimmt, können Sie diesen Typ in Ihrer Implementierung in IInspectable ändern. Dies funktioniert, da eine someClass an IInspectable weitergeleitet werden kann (die Umgekehrte funktioniert natürlich nicht).
- Sie können einen kopierbaren Parameter nach Wert anstelle eines Verweises akzeptieren. Ändern Sie beispielsweise
SomeClass const&inSomeClass. Das ist erforderlich, wenn Sie vermeiden müssen, dass eine Referenz von einer Coroutine erfasst wird (siehe Parameterübergabe). - Sie können die Anforderungen an den Rückgabewert lockern. Sie können beispielsweise "void " in "winrt::fire_and_forget" ändern.
Die letzten beiden sind sehr nützlich, wenn Sie einen asynchronen Ereignishandler schreiben.
Instanziieren und Zurückgeben von Implementierungstypen und Schnittstellen
In diesem Abschnitt nehmen wir uns als Beispiel einen Implementierungstyp namens MyType an, der die IStringable - und IClosable-Schnittstellen implementiert.
Sie können MyType direkt von winrt::implements ableiten (es ist keine Laufzeitklasse).
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
struct MyType : implements<MyType, IStringable, IClosable>
{
winrt::hstring ToString(){ ... }
void Close(){}
};
Sie können es auch aus IDL generieren (es ist eine Laufzeitklasse).
// MyType.idl
namespace MyProject
{
runtimeclass MyType: Windows.Foundation.IStringable, Windows.Foundation.IClosable
{
MyType();
}
}
Sie können Ihren Implementierungstyp nicht direkt zuordnen.
MyType myimpl; // error C2259: 'MyType': cannot instantiate abstract class
Sie können jedoch von MyType zu einem IStringable - oder IClosable-Objekt wechseln, das Sie als Teil der Projektion verwenden oder zurückgeben können, indem Sie die Funktionsvorlage winrt::make aufrufen. make gibt die Standardschnittstelle des Implementierungstyps zurück.
IStringable istringable = winrt::make<MyType>();
Hinweis
Wenn Sie jedoch über die XAML-Benutzeroberfläche auf Ihren Typ verweisen, gibt es sowohl einen Implementierungstyp als auch einen projizierten Typ im selben Projekt. In diesem Fall gibt make eine Instanz des projizierten Typs zurück. Ein Codebeispiel für dieses Szenario finden Sie unter XAML-Steuerelemente; Binden an eine C++/WinRT-Eigenschaft.
Wir können istringable (im obigen Codebeispiel) nur verwenden, um die Member der IStringable-Schnittstelle aufzurufen. Eine C++/WinRT-Schnittstelle (die eine projizierte Schnittstelle ist) wird jedoch von winrt::Windows::Foundation::IUnknown abgeleitet. Sie können also IUnknown::as (oder IUnknown::try_as) aufrufen, um nach anderen projizierten Typen oder Schnittstellen abzufragen, die Sie auch verwenden oder zurückgeben können.
Tip
Ein Szenario, in dem Sie nichtas oder try_as aufrufen sollten, ist die Laufzeit-Klassenableitung („komponierbare Klassen“). Wenn ein Implementierungstyp eine andere Klasse zusammensetzt, rufen Sie nicht as oder try_as auf, um eine ungeprüfte oder geprüfte QueryInterface der zusammengesetzten Klasse durchzuführen. Greifen Sie stattdessen auf das (this->) m_inner Datenmember zu und rufen Sie darauf as oder try_as auf. Weitere Informationen finden Sie unter der Ableitung der Runtime-Klasse in diesem Thema.
istringable.ToString();
IClosable iclosable = istringable.as<IClosable>();
iclosable.Close();
Wenn Sie auf alle Member der Implementierung zugreifen und später eine Schnittstelle an einen Aufrufer zurückgeben müssen, verwenden Sie dann die Funktionsvorlage winrt::make_self . make_self gibt einen winrt::com_ptr zurück, der den Implementierungstyp kapselt. Sie können auf die Member aller seiner Schnittstellen zugreifen (mithilfe des Pfeiloperators), es unverändert an einen Aufrufer zurückgeben oder as darauf aufrufen und das resultierende Schnittstellenobjekt an einen Aufrufer zurückgeben.
winrt::com_ptr<MyType> myimpl = winrt::make_self<MyType>();
myimpl->ToString();
myimpl->Close();
IClosable iclosable = myimpl.as<IClosable>();
iclosable.Close();
Die MyType-Klasse ist nicht Teil der Projektion; es ist die Implementierung. Auf diese Weise können Sie die Implementierungsmethoden jedoch direkt aufrufen, ohne dass der Aufwand eines aufrufs einer virtuellen Funktion besteht. Auch wenn "MyType::ToString " dieselbe Signatur wie die projizierte Methode für IStringable verwendet, rufen wir die nicht virtuelle Methode direkt auf, ohne die Binäre Schnittstelle (Application Binary Interface, ABI) zu überschreiten. Die com_ptr enthält einfach einen Zeiger auf die MyType-Struktur, sodass Sie auch über die Variable und den Pfeiloperator auf alle anderen internen Details von myimpl zugreifen können.
Wenn Sie über ein Schnittstellenobjekt verfügen und wissen, dass es sich bei der Implementierung um eine Schnittstelle handelt, können Sie mithilfe der Funktionsvorlage winrt::get_self zur Implementierung zurückkehren. Auch dies ist eine Technik, die virtuelle Funktionsaufrufe vermeidet und Ihnen direkten Zugriff auf die Implementierung ermöglicht.
Hinweis
Wenn Sie die Windows SDK-Version 10.0.17763.0 (Windows 10, Version 1809) oder höher nicht installiert haben, müssen Sie winrt::from_abi anstelle von winrt::get_self aufrufen.
Hier finden Sie ein Beispiel dafür. Es gibt ein weiteres Beispiel in Implement the BgLabelControl custom control class.
void ImplFromIClosable(IClosable const& from)
{
MyType* myimpl = winrt::get_self<MyType>(from);
myimpl->ToString();
myimpl->Close();
}
Aber nur das ursprüngliche Schnittstellenobjekt enthält einen Verweis. Wenn Sie es behalten möchten, können Sie com_ptr::copy_from aufrufen.
winrt::com_ptr<MyType> impl;
impl.copy_from(winrt::get_self<MyType>(from));
// com_ptr::copy_from ensures that AddRef is called.
Der Implementierungstyp selbst leitet sich nicht von winrt::Windows::Foundation::IUnknown ab, sodass er keine as-Funktion hat. Dennoch können Sie, wie Sie in der obigen ImplFromIClosable-Funktion sehen können, auf die Member all ihrer Schnittstellen zugreifen. Aber wenn Sie das tun, geben Sie die rohe Instanz des Implementierungstyps dann nicht an den Aufrufer zurück. Verwenden Sie stattdessen eine der bereits gezeigten Techniken, und geben Sie eine projizierte Schnittstelle oder einen com_ptr zurück.
Wenn Sie über eine Instanz Ihres Implementierungstyps verfügen und sie an eine Funktion übergeben müssen, die den entsprechenden projizierten Typ erwartet, können Sie dies tun, wie im folgenden Codebeispiel gezeigt. Für Ihren Implementierungstyp ist ein Konvertierungsoperator verfügbar (vorausgesetzt, dass der Implementierungstyp mit dem cppwinrt.exe-Tool generiert wurde), mit dem dies möglich ist. Sie können einen Implementierungstypwert direkt an eine Methode übergeben, die einen Wert des entsprechenden projizierten Typs erwartet. Aus einer Memberfunktion eines Implementierungstyps können Sie *this an eine Methode übergeben, die einen Wert des entsprechenden projizierten Typs erwartet.
// MyClass.idl
import "MyOtherClass.idl";
namespace MyProject
{
runtimeclass MyClass
{
MyClass();
void MemberFunction(MyOtherClass oc);
}
}
// MyClass.h
...
namespace winrt::MyProject::implementation
{
struct MyClass : MyClassT<MyClass>
{
MyClass() = default;
void MemberFunction(MyProject::MyOtherClass const& oc) { oc.DoWork(*this); }
};
}
...
// MyOtherClass.idl
import "MyClass.idl";
namespace MyProject
{
runtimeclass MyOtherClass
{
MyOtherClass();
void DoWork(MyClass c);
}
}
// MyOtherClass.h
...
namespace winrt::MyProject::implementation
{
struct MyOtherClass : MyOtherClassT<MyOtherClass>
{
MyOtherClass() = default;
void DoWork(MyProject::MyClass const& c){ /* ... */ }
};
}
...
//main.cpp
#include "pch.h"
#include <winrt/base.h>
#include "MyClass.h"
#include "MyOtherClass.h"
using namespace winrt;
// MyProject::MyClass is the projected type; the implementation type would be MyProject::implementation::MyClass.
void FreeFunction(MyProject::MyOtherClass const& oc)
{
auto defaultInterface = winrt::make<MyProject::implementation::MyClass>();
MyProject::implementation::MyClass* myimpl = winrt::get_self<MyProject::implementation::MyClass>(defaultInterface);
oc.DoWork(*myimpl);
}
...
Ableitung der Laufzeitklassen
Sie können eine Laufzeitklasse erstellen, die von einer anderen Laufzeitklasse abgeleitet wird, vorausgesetzt, die Basisklasse wird als "unsealed" deklariert. Der Windows-Runtime Begriff für die Klassenableitung ist "kompposierbare Klassen". Der Code für die Implementierung einer abgeleiteten Klasse hängt davon ab, ob die Basisklasse von einer anderen Komponente oder von derselben Komponente bereitgestellt wird. Glücklicherweise müssen Sie diese Regeln nicht erlernen – Sie können einfach die Beispielimplementierungen aus dem sources Ausgabeordner kopieren, der cppwinrt.exe vom Compiler erstellt wird.
Betrachten Sie dieses Beispiel.
// MyProject.idl
namespace MyProject
{
[default_interface]
runtimeclass MyButton : Microsoft.UI.Xaml.Controls.Button
{
MyButton();
}
unsealed runtimeclass MyBase
{
MyBase();
overridable Int32 MethodOverride();
}
[default_interface]
runtimeclass MyDerived : MyBase
{
MyDerived();
}
}
Im obigen Beispiel wird MyButton vom XAML-Schaltflächen-Steuerelement abgeleitet, das von einer anderen Komponente bereitgestellt wird. In diesem Fall sieht die Implementierung genauso aus wie die Implementierung einer nicht komponierbaren Klasse:
namespace winrt::MyProject::implementation
{
struct MyButton : MyButtonT<MyButton>
{
};
}
namespace winrt::MyProject::factory_implementation
{
struct MyButton : MyButtonT<MyButton, implementation::MyButton>
{
};
}
Im obigen Beispiel wird MyDerived dagegen von einer anderen Klasse in derselben Komponente abgeleitet. In diesem Fall erfordert die Implementierung einen zusätzlichen Vorlagenparameter, der die Implementierungsklasse für die Basisklasse angibt.
namespace winrt::MyProject::implementation
{
struct MyDerived : MyDerivedT<MyDerived, implementation::MyBase>
{ // ^^^^^^^^^^^^^^^^^^^^^^
};
}
namespace winrt::MyProject::factory_implementation
{
struct MyDerived : MyDerivedT<MyDerived, implementation::MyDerived>
{
};
}
In beiden Fällen kann Ihre Implementierung eine Methode aus der Basisklasse aufrufen, indem sie mit dem base_type Typalias qualifiziert wird:
namespace winrt::MyProject::implementation
{
struct MyButton : MyButtonT<MyButton>
{
void OnApplyTemplate()
{
// Call base class method
base_type::OnApplyTemplate();
// Do more work after the base class method is done
DoAdditionalWork();
}
};
struct MyDerived : MyDerivedT<MyDerived, implementation::MyBase>
{
int MethodOverride()
{
// Return double what the base class returns
return 2 * base_type::MethodOverride();
}
};
}
Tip
Wenn ein Implementierungstyp eine andere Klasse zusammensetzt, rufen Sie nicht as oder try_as auf, um eine ungeprüfte oder geprüfte QueryInterface der zusammengesetzten Klasse durchzuführen. Greifen Sie stattdessen auf das (this->) m_inner Datenmember zu und rufen Sie darauf as oder try_as auf.
Ableiten von einem Typ mit einem nicht standardmäßigen Konstruktor
ToggleButtonAutomationPeer::ToggleButtonAutomationPeer(ToggleButton) ist ein Beispiel für einen nicht standardmäßigen Konstruktor. Da es keinen Standardkonstruktor gibt, müssen Sie zum Instanziieren eines ToggleButtonAutomationPeer einen owner übergeben. Wenn Sie daher von ToggleButtonAutomationPeer ableiten, müssen Sie einen Konstruktor bereitstellen, der owner entgegennimmt und an die Basisklasse weitergibt. Sehen wir uns an, wie das in der Praxis aussieht.
// MySpecializedToggleButton.idl
namespace MyNamespace
{
runtimeclass MySpecializedToggleButton :
Microsoft.UI.Xaml.Controls.Primitives.ToggleButton
{
...
};
}
// MySpecializedToggleButtonAutomationPeer.idl
namespace MyNamespace
{
runtimeclass MySpecializedToggleButtonAutomationPeer :
Microsoft.UI.Xaml.Automation.Peers.ToggleButtonAutomationPeer
{
MySpecializedToggleButtonAutomationPeer(MySpecializedToggleButton owner);
};
}
Der generierte Konstruktor für Ihren Implementierungstyp sieht wie folgt aus.
// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
(MyNamespace::MySpecializedToggleButton const& owner)
{
...
}
...
Der einzige fehlende Teil besteht darin, dass Sie diesen Konstruktorparameter an die Basisklasse übergeben müssen. Erinnern Sie sich an das F-gebundene Polymorphismusmuster, das wir oben erwähnt haben? Sobald Sie mit den Details dieses Musters vertraut sind, wie es von C++/WinRT verwendet wird, können Sie herausfinden, welche Basisklasse aufgerufen wird (oder Sie können einfach in der Headerdatei der Implementierungsklasse suchen). Hier erfahren Sie, wie Sie den Basisklassenkonstruktor in diesem Fall aufrufen.
// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
(MyNamespace::MySpecializedToggleButton const& owner) :
MySpecializedToggleButtonAutomationPeerT<MySpecializedToggleButtonAutomationPeer>(owner)
{
...
}
...
Der Konstruktor der Basisklasse erwartet ein ToggleButton. Und MySpecializedToggleButtonist einToggleButton.
Bis Sie die oben beschriebene Änderung vornehmen (um diesen Konstruktorparameter an die Basisklasse weiterzugeben), markiert der Compiler Ihren Konstruktor als fehlerhaft und weist darauf hin, dass für einen Typ namens (in diesem Fall) MySpecializedToggleButtonAutomationPeer_base<MySpecializedToggleButtonAutomationPeer> kein geeigneter Standardkonstruktor verfügbar ist. Das ist eigentlich die Basisklasse der Bassklasse Ihres Implementierungstyps.
Namespaces: projizierte Typen, Implementierungstypen und Fabriken
Wie Sie bereits in diesem Thema gesehen haben, ist eine C++/WinRT-Laufzeitklasse in Form von mehr als einer C++-Klasse in mehreren Namespaces vorhanden. Der Name MyRuntimeClass hat also eine Bedeutung im winrt::MyProject-Namespace und eine andere Bedeutung im winrt::MyProject::implementation-Namespace . Beachten Sie, über welchen Namespace Sie derzeit im Kontext verfügen, und verwenden Sie dann Namespacepräfixe, wenn Sie einen Namen aus einem anderen Namespace benötigen. Sehen wir uns die betreffenden Namespaces genauer an.
- winrt::MyProject. Dieser Namespace enthält projizierte Typen. Ein Objekt eines projizierten Typs ist ein Proxy; Es handelt sich im Wesentlichen um einen intelligenten Zeiger auf ein Sicherungsobjekt, bei dem dieses Sicherungsobjekt hier in Ihrem Projekt implementiert werden kann oder in einer anderen Kompilierungseinheit implementiert wird.
- winrt::MyProject::implementation. Dieser Namespace enthält Implementierungstypen. Ein Objekt eines Implementierungstyps ist kein Zeiger; es ist ein Wert – ein vollständiges C++-Stapelobjekt. Erstellen Sie keinen Implementierungstyp direkt; Rufen Sie stattdessen winrt::make auf, und übergeben Sie den Implementierungstyp als Vorlagenparameter. Wir haben Beispiele für winrt::make in Aktion zuvor in diesem Thema gezeigt, und es gibt ein weiteres Beispiel in XAML-Steuerelementen; Binden an eine C++/WinRT-Eigenschaft. Siehe auch Diagnostizieren direkter Zuweisungen.
- winrt::MyProject::factory_implementation. Dieser Namespace enthält Fabriken. Ein Objekt in diesem Namespace unterstützt IActivationFactory.
Diese Tabelle zeigt die mindeste Namespacequalifizierung, die Sie in verschiedenen Kontexten verwenden müssen.
| Der Namespace im aktuellen Kontext | Zum Angeben des projizierten Typs | So geben Sie den Implementierungstyp an |
|---|---|---|
| winrt::MyProject | MyRuntimeClass |
implementation::MyRuntimeClass |
| winrt::MyProject::implementation | MyProject::MyRuntimeClass |
MyRuntimeClass |
Important
Wenn Sie einen projizierten Typ aus Ihrer Implementierung zurückgeben möchten, achten Sie darauf, den Implementierungstyp nicht durch Schreiben MyRuntimeClass myRuntimeClass;zu instanziieren. Die richtigen Techniken und Code für dieses Szenario werden zuvor in diesem Thema im Abschnitt Instanziieren und Zurückgeben von Implementierungstypen und Schnittstellen gezeigt.
Das Problem mit MyRuntimeClass myRuntimeClass; in diesem Szenario ist, dass ein winrt::MyProject::implementation::MyRuntimeClass-Objekt auf dem Stack erstellt wird. Dieses Objekt (des Implementierungstyps) verhält sich auf verschiedene Weise wie der projizierte Typ – Sie können Methoden darauf auf die gleiche Weise aufrufen; und wird sogar in einen projizierten Typ konvertiert. Das Objekt wird jedoch nach den üblichen C++-Regeln zerstört, wenn der Gültigkeitsbereich verlassen wird. Wenn Sie also einen projizierten Typ (einen Smart Pointer) auf dieses Objekt zurückgegeben haben, ist dieser Zeiger jetzt hängend.
Dieser Speicherbeschädigungstyp ist schwer zu diagnostizieren. Für Debug-Builds hilft Ihnen also eine C++/WinRT-Assertion mithilfe eines Stack-Detektors, diesen Fehler zu erkennen. Aber Coroutinen werden auf dem Heap alloziert, daher wird Ihnen bei diesem Fehler nicht geholfen, wenn er Ihnen innerhalb einer Coroutine unterläuft. Weitere Informationen finden Sie unter Diagnose von direkten Zuordnungen.
Verwenden von projizierten Typen und Implementierungstypen mit verschiedenen C++/WinRT-Features
Hier sind verschiedene Orte, an denen eine C++/WinRT-Funktion einen Typ erwartet und welche Art von Typ sie erwartet (projizierter Typ, Implementierungstyp oder beides).
| Funktion | Akzeptiert | Notes |
|---|---|---|
T (stellt einen intelligenten Zeiger dar) |
Projiziert | Beachten Sie den Warnhinweis in Namespaces: projizierte Typen, Implementierungstypen und Fabriken zur versehentlichen Verwendung des Implementierungstyps. |
agile_ref<T> |
Beides | Wenn Sie den Implementierungstyp verwenden, muss das Konstruktorargument sein com_ptr<T>. |
com_ptr<T> |
Implementation | Die Verwendung des projizierten Typs generiert den Fehler: 'Release' is not a member of 'T'. |
default_interface<T> |
Beides | Wenn Sie den Implementierungstyp verwenden, wird die erste implementierte Schnittstelle zurückgegeben. |
get_self<T> |
Implementation | Die Verwendung des projizierten Typs generiert den Fehler: '_abi_TrustLevel': is not a member of 'T'. |
guid_of<T>() |
Beides | Gibt die GUID der Standardschnittstelle zurück. |
IWinRTTemplateInterface<T> |
Projiziert | Die Verwendung des Implementierungstyps wird zwar kompiliert, ist jedoch ein Fehler – siehe den Warnhinweis unter Namespaces: projizierte Typen, Implementierungstypen und Fabriken. |
make<T> |
Implementation | Die Verwendung des projizierten Typs generiert den Fehler: 'implements_type': is not a member of any direct or indirect base class of 'T' |
make_agile(T const&) |
Beides | Wenn Sie den Implementierungstyp verwenden, muss das Argument sein com_ptr<T>. |
make_self<T> |
Implementation | Die Verwendung des projizierten Typs generiert den Fehler: 'Release': is not a member of any direct or indirect base class of 'T' |
name_of<T> |
Projiziert | Wenn Sie den Implementierungstyp verwenden, erhalten Sie die Zeichenfolgen-GUID der Standardschnittstelle. |
weak_ref<T> |
Beides | Wenn Sie den Implementierungstyp verwenden, muss das Konstruktorargument sein com_ptr<T>. |
Aktivieren Sie die einheitliche Konstruktion und den direkten Implementierungszugriff
In diesem Abschnitt wird eine C++/WinRT 2.0-Funktion beschrieben, die optional ist, für neue Projekte jedoch standardmäßig aktiviert wird. Für ein vorhandenes Projekt müssen Sie sich anmelden, indem Sie das cppwinrt.exe Tool konfigurieren. Legen Sie in Visual Studio die Projekteigenschaft "Common Properties>C++/WinRT>" auf "Ja" optimiert fest. Dies bewirkt, dass <CppWinRTOptimized>true</CppWinRTOptimized> zu Ihrer Projektdatei hinzugefügt wird. Und es hat den gleichen Effekt wie das Hinzufügen des Schalters beim Aufrufen cppwinrt.exe über die Befehlszeile.
Der -opt[imize] Schalter ermöglicht, was oft als einheitliche Konstruktion bezeichnet wird. Bei der uniformen (oder vereinheitlichten) Erstellung verwenden Sie die C++/WinRT-Sprachprojektion selbst, um Ihre Implementierungstypen (von Ihrer Komponente implementierte Typen, zur Nutzung durch Anwendungen) effizient und ohne Probleme mit dem Ladevorgang zu erstellen und zu verwenden.
Bevor wir das Feature beschreiben, zeigen wir zunächst die Situation ohne einheitliche Konstruktion. Zur Veranschaulichung beginnen wir mit diesem Beispiel Windows-Runtime Klasse.
// MyClass.idl
namespace MyProject
{
runtimeclass MyClass
{
MyClass();
void Method();
static void StaticMethod();
}
}
Als C++-Entwickler, der mit der Verwendung der C++/WinRT-Bibliothek vertraut ist, sollten Sie die Klasse wie folgt verwenden.
using namespace winrt::MyProject;
MyClass c;
c.Method();
MyClass::StaticMethod();
Und das wäre vollkommen vernünftig, vorausgesetzt, dass sich der angezeigte Verbrauchcode nicht in derselben Komponente befindet, die diese Klasse implementiert. Als Sprachprojektion schirmt C++/WinRT Sie als Entwickler von der ABI ab (die COM-basierte Anwendungs-Binärschnittstelle, die von der Windows-Runtime definiert wird). C++/WinRT ruft die Implementierung nicht direkt auf; der Aufruf erfolgt über die ABI.
Folglich ruft die C++/WinRT-Projektion in der Codezeile, in der Sie ein MyClass-Objekt (MyClass c;) erstellen, RoGetActivationFactory auf, um die Klasse oder Aktivierungsfactory abzurufen, und verwendet diese Factory dann zum Erstellen des Objekts. Die letzte Zeile verwendet ebenfalls die Factory, um einen scheinbar statischen Methodenaufruf zu erzeugen. Dies erfordert, dass Ihre Klasse registriert wird und dass Ihr Modul den Einstiegspunkt "DllGetActivationFactory" implementiert. C++/WinRT verfügt über einen sehr schnellen Factorycache, daher verursacht keines dieser Probleme für eine Anwendung, die Ihre Komponente verbraucht. Das Problem besteht darin, dass Sie in Ihrer Komponente gerade etwas getan haben, das ein wenig problematisch ist.
Erstens, unabhängig davon, wie schnell der C++/WinRT-Factorycache ist, ist das Aufrufen über RoGetActivationFactory (oder sogar nachfolgende Aufrufe über den Factorycache) immer langsamer als das direkte Aufrufen in die Implementierung. Ein Aufruf von RoGetActivationFactory gefolgt von IActivationFactory::ActivateInstance gefolgt von QueryInterface wird offensichtlich nicht so effizient sein wie die Verwendung eines C++ new -Ausdrucks für einen lokal definierten Typ. Daher sind erfahrene C++/WinRT-Entwickler daran gewöhnt, beim Erstellen von Objekten innerhalb einer Komponente die Winrt::make or winrt::make_self Hilfsfunktionen zu verwenden.
// MyClass c;
MyProject::MyClass c{ winrt::make<implementation::MyClass>() };
Aber wie Sie sehen können, ist das nicht fast so bequem oder prägnant. Sie müssen eine Hilfsfunktion verwenden, um das Objekt zu erstellen, und Sie müssen auch zwischen dem Implementierungstyp und dem projizierten Typ unterscheiden.
Zweitens führt die Verwendung der Projektion zum Erstellen der Klasse dazu, dass ihre Aktivierungsfactory zwischengespeichert wird. Normalerweise ist das das gewünschte, aber wenn sich die Factory in demselben Modul (DLL) befindet, das den Aufruf durchführt, haben Sie die DLL effektiv angeheftet und verhindert, dass sie jemals entladen wird. In vielen Fällen spielt das keine Rolle; einige Systemkomponenten müssen jedoch das Entladen unterstützen.
Hier kommt der Begriff einheitliche Bauweise ins Spiel. Unabhängig davon, ob sich der Erstellungscode in einem Projekt befindet, das lediglich die Klasse verwendet, oder ob er sich im Projekt befindet, das tatsächlich die Klasse implementiert , können Sie die gleiche Syntax frei verwenden, um das Objekt zu erstellen.
// MyProject::MyClass c{ winrt::make<implementation::MyClass>() };
MyClass c;
Wenn Sie Ihr Komponentenprojekt mit dem -opt[imize] Schalter erstellen, wird der Aufruf über die Sprachprojektion zu demselben effizienten Aufruf der Funktion winrt::make kompiliert, der den Implementierungstyp direkt erstellt. Dadurch wird Ihre Syntax einfach und vorhersehbar, Leistungseinbußen durch Aufrufe über die Factory werden vermieden, und die Komponente wird dabei nicht fixiert. Zusätzlich zu Komponentenprojekten ist dies auch für XAML-Anwendungen nützlich. Wenn Sie RoGetActivationFactory für klassen umgehen, die in derselben Anwendung implementiert sind, können Sie sie (ohne Registrierung) auf die gleiche Weise erstellen, wie sie sich außerhalb der Komponente befanden.
Einheitliche Struktur trifft auf jeden Aufruf zu, der intern von der Factory verarbeitet wird. In der Praxis bedeutet das, dass die Optimierung sowohl für Konstruktoren als auch für statische Mitglieder gilt. Hier sehen Sie das ursprüngliche Beispiel erneut.
MyClass c;
c.Method();
MyClass::StaticMethod();
Ohne -opt[imize] müssen die erste und die letzte Anweisung über das Factory-Objekt aufgerufen werden.
Mit-opt[imize] macht es keiner von beiden. Und diese Aufrufe werden direkt gegen die Implementierung kompiliert und haben sogar das Potenzial, inline zu sein. Das deutet auf den anderen Begriff hin, der häufig verwendet wird, wenn von -opt[imize] die Rede ist, nämlich direkter Implementierungszugang.
Sprachprojektionen sind praktisch, aber wenn Sie direkt auf die Implementierung zugreifen können, können und sollten Sie dies nutzen, um den effizientesten Code zu erzeugen. C++/WinRT kann dies für Sie tun, ohne sie zu zwingen, die Sicherheit und Produktivität der Projektion zu verlassen.
Dies ist eine bahnbrechende Änderung, da die Komponente zusammenarbeiten muss, um der Sprachprojektion den Direkten Zugriff auf die Implementierungstypen zu ermöglichen. Da C++/WinRT eine Header-Only-Bibliothek ist, können Sie einen Blick hineinwerfen und sehen, wie sie funktioniert. Ohne -opt[imize] werden der MyClass-Konstruktor und das StaticMethod-Member von der Projektion wie folgt definiert.
namespace winrt::MyProject
{
inline MyClass::MyClass() :
MyClass(impl::call_factory<MyClass>([](auto&& f){
return f.template ActivateInstance<MyClass>(); }))
{
}
inline void MyClass::StaticMethod()
{
impl::call_factory<MyClass, MyProject::IClassStatics>([&](auto&& f) {
return f.StaticMethod(); });
}
}
Es ist nicht notwendig, alle oben genannten Punkte zu befolgen; Die Absicht besteht darin, zu zeigen, dass beide Aufrufe einen Aufruf einer Funktion mit dem Namen call_factory umfassen. Das ist Ihr Hinweis darauf, dass diese Aufrufe den Factorycache umfassen und nicht direkt auf die Implementierung zugreifen.
Mit-opt[imize] sind dieselben Funktionen überhaupt nicht definiert. Stattdessen werden sie von der Projektion deklariert, und ihre Definitionen bleiben der Komponente überlassen.
Die Komponente kann dann Definitionen bereitstellen, die direkt in die Implementierung aufrufen. Jetzt sind wir an der bahnbrechenden Änderung angekommen. Diese Definitionen werden für Sie generiert, wenn Sie sowohl -component als auch -opt[imize] verwenden, und sie erscheinen in einer Datei namens Type.g.cpp, wobei Type der Name der implementierten Laufzeitklasse ist. Deshalb können verschiedene Linkerfehler auftreten, wenn Sie -opt[imize] erstmals in einem vorhandenen Projekt aktivieren. Sie müssen diese generierte Datei in Ihre Implementierung einschließen, um Dinge zu verknüpfen.
In unserem Beispiel könnte MyClass.h so aussehen (unabhängig davon, ob -opt[imize] verwendet wird).
// MyClass.h
#pragma once
#include "MyClass.g.h"
namespace winrt::MyProject::implementation
{
struct MyClass : ClassT<MyClass>
{
MyClass() = default;
static void StaticMethod();
void Method();
};
}
namespace winrt::MyProject::factory_implementation
{
struct MyClass : ClassT<MyClass, implementation::MyClass>
{
};
}
Ihr MyClass.cpp ist der Ort, an dem alles zusammenkommt.
#include "pch.h"
#include "MyClass.h"
#include "MyClass.g.cpp" // !!It's important that you add this line!!
namespace winrt::MyProject::implementation
{
void MyClass::StaticMethod()
{
}
void MyClass::Method()
{
}
}
Um eine einheitliche Konstruktion in einem vorhandenen Projekt zu verwenden, müssen Sie die .cpp-Datei jeder Implementierung so bearbeiten, dass Sie #include <Sub/Namespace/Type.g.cpp> nach der Einbindung (und Definition) der Implementierungsklasse. Diese Datei stellt die Definitionen dieser Funktionen bereit, die die Projektion nicht definiert hat. Hier sehen Sie, wie diese Definitionen in der MyClass.g.cpp Datei aussehen.
namespace winrt::MyProject
{
MyClass::MyClass() :
MyClass(make<MyProject::implementation::MyClass>())
{
}
void MyClass::StaticMethod()
{
return MyProject::implementation::MyClass::StaticMethod();
}
}
Und das schließt die Projektion gut mit effizienten Aufrufen direkt in die Implementierung ab, vermeidet Aufrufe an den Factorycache und erfüllt den Linker.
Das Letzte, was -opt[imize] Für Sie tut, besteht darin, die Implementierung der module.g.cpp Projektimplementierung (die Datei, die Ihnen hilft, die DllGetActivationFactory - und DllCanUnloadNow-Exporte Ihrer DLL zu implementieren) so zu ändern, dass inkrementelle Builds tendenziell viel schneller sein werden, indem die starke Typkopplung eliminiert wird, die von C++/WinRT 1.0 benötigt wurde. Dies wird häufig als typradierierte Fabriken bezeichnet. Ohne -opt[imize] beginnt die für Ihre Komponente generierte module.g.cpp-Datei damit, die Definitionen all Ihrer Implementierungsklassen einzuschließen – die MyClass.h in diesem Beispiel. Anschließend wird für jede Klasse direkt die Implementierungsfactory wie folgt erstellt.
if (requal(name, L"MyProject.MyClass"))
{
return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}
Auch hier müssen Sie nicht alle Details befolgen. Hilfreich ist dabei zu sehen, dass dies die vollständige Definition sämtlicher Klassen erfordert, die von Ihrer Komponente implementiert werden. Dies kann erhebliche Auswirkungen auf Ihren inneren Entwicklungszyklus haben, da jede Änderung an einer einzigen Implementierung dazu führt, dass module.g.cpp neu kompiliert wird. Mit -opt[imize], ist dies nicht mehr der Fall. Stattdessen passieren zwei Dinge mit der generierten module.g.cpp Datei. Die erste besteht darin, dass sie keine Implementierungsklassen mehr enthält. In diesem Beispiel wird MyClass.h überhaupt nicht berücksichtigt. Stattdessen werden die Implementierungsfabriken erstellt, ohne etwas über deren Implementierung zu wissen.
void* winrt_make_MyProject_MyClass();
if (requal(name, L"MyProject.MyClass"))
{
return winrt_make_MyProject_MyClass();
}
Offensichtlich ist es nicht erforderlich, ihre Definitionen einzuschließen, und es liegt an dem Linker, die Definition der winrt_make_Component_Class Funktion aufzulösen. Natürlich müssen Sie dies nicht berücksichtigen, da die MyClass.g.cpp datei, die für Sie generiert wird (und die Sie zuvor zur Unterstützung der einheitlichen Konstruktion eingeschlossen haben) diese Funktion definiert. Hier ist die vollständige MyClass.g.cpp-Datei, die für dieses Beispiel generiert wird.
void* winrt_make_MyProject_MyClass()
{
return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}
namespace winrt::MyProject
{
MyClass::MyClass() :
MyClass(make<MyProject::implementation::MyClass>())
{
}
void MyClass::StaticMethod()
{
return MyProject::implementation::MyClass::StaticMethod();
}
}
Wie Sie sehen können, erstellt die winrt_make_MyProject_MyClass-Funktion die Factory Ihrer Implementierung direkt. Das alles bedeutet, dass Sie jede beliebige Implementierung problemlos ändern können und module.g.cpp überhaupt nicht neu kompiliert werden muss. Nur wenn Sie Windows-Runtime-Klassen hinzufügen oder entfernen, wird module.g.cpp aktualisiert und muss neu kompiliert werden.
Überschreiben virtueller Methoden der Basisklasse
Ihre abgeleitete Klasse kann Probleme mit virtuellen Methoden haben, wenn sowohl die Basis- als auch die abgeleitete Klasse appdefinierte Klassen sind, die virtuelle Methode wird jedoch in einer Großeltern-Windows-Runtime Klasse definiert. In der Praxis geschieht dies, wenn Sie von XAML-Klassen ableiten. Der Rest dieses Abschnitts wird aus dem Beispiel in abgeleiteten Klassen fortgesetzt.
namespace winrt::MyNamespace::implementation
{
struct BasePage : BasePageT<BasePage>
{
void OnNavigatedFrom(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
struct DerivedPage : DerivedPageT<DerivedPage>
{
void OnNavigatedFrom(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
}
Die Hierarchie ist Microsoft::UI::Xaml::Controls::Page<- BasePage<- DerivedPage. Die BasePage::OnNavigatedFrom-Methode überschreibt Page::OnNavigatedFrom korrekt, aber DerivedPage::OnNavigatedFrom überschreibt BasePage::OnNavigatedFrom nicht.
Hier verwendet DerivedPage die IPageOverrides-vtable von BasePage, was bedeutet, dass die IPageOverrides::OnNavigatedFrom-Methode nicht außer Kraft gesetzt werden kann. Eine mögliche Lösung erfordert , dass BasePage selbst eine Vorlagenklasse ist und die Implementierung vollständig in einer Headerdatei vorhanden ist, aber das macht dinge unakzeptabel kompliziert.
Als Workaround deklarieren Sie die Methode OnNavigatedFrom in der Basisklasse explizit als virtuell. Auf diese Weise ruft der Produzent, wenn der vtable-Eintrag für DerivedPage::IPageOverrides::OnNavigatedFromBasePage::IPageOverrides::OnNavigatedFrom aufruft, BasePage::OnNavigatedFrom auf, die aufgrund ihrer virtuellen Natur schließlich DerivedPage::OnNavigatedFrom aufruft.
namespace winrt::MyNamespace::implementation
{
struct BasePage : BasePageT<BasePage>
{
// Note the `virtual` keyword here.
virtual void OnNavigatedFrom(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
struct DerivedPage : DerivedPageT<DerivedPage>
{
void OnNavigatedFrom(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
}
Dies erfordert, dass alle Mitglieder der Klassenhierarchie hinsichtlich des Rückgabewerts und der Parametertypen der Methode OnNavigatedFrom übereinstimmen. Wenn sie nicht einverstanden sind, sollten Sie die oben genannte Version als virtuelle Methode verwenden und die Alternativen umschließen.
Hinweis
Ihre IDL muss die überschriebene Methode nicht deklarieren. Weitere Informationen finden Sie unter Implementierung überschreibbarer Methoden.
Wichtige APIs
- winrt::com_ptr Strukturvorlage
- winrt::com_ptr::copy_from-Funktion
- winrt::from_abi Funktionsvorlage
- winrt::get_self Funktionsvorlage
- winrt::implements strukturvorlage
- Funktionsvorlage winrt::make
- winrt::make_self Funktionsvorlage
- winrt::Windows::Foundation::IUnknown::as-Funktion
- winrt::Windows::Foundation::IUnknown::try_as-Funktion
Zugehörige Themen
Windows developer