Umstellen von C++/CX auf C++/WinRT

Dies ist das erste Thema in einer Reihe, in der die Portierung des Quellcodes in Ihrem C++/CX-Projekt in entsprechenden C++/WinRT-Code beschrieben wird.

Wenn für den Projekt zudem Typen der C++-Vorlagenbibliothek für Windows-Runtime (WRL) verwendet werden, helfen dir die Informationen unter Wechsel zu C++/WinRT von WRL weiter.

Strategien zum Portieren

Sie sollten wissen, dass die Portierung von C++/CX nach C++/WinRT im Allgemeinen unkompliziert ist, mit der einzigen Ausnahme der Portierung von Parallel Patterns Library (PPL)-Aufgaben zu Co-Routinen. Die Modelle unterscheiden sich. Es gibt keine natürliche 1:1-Zuordnung von PPL-Aufgaben zu Co-Routinen, und es gibt keine einfache Methode, den Code, der für alle Fälle funktioniert, automatisch zu portieren. Hilfe zu diesem speziellen Aspekt der Portierung und Ihren Optionen für die Interoperabilität zwischen den beiden Modellen finden Sie unter Asynchronität und Interoperabilität zwischen C++/WinRT und C++/CX.

Entwicklungsteams berichten routinemäßig, dass, sobald sie die Hürde der Portierung ihres asynchronen Codes genommen haben, der Rest der Portierung weitgehend automatisch erfolgt.

Portieren in einem Durchgang

Wenn Sie Ihr gesamtes Projekt in einem Durchgang portieren können, brauchen Sie nur dieses Thema, um die benötigten Informationen zu erhalten (die nachfolgenden Themen zur Interoperabilität sind nicht erforderlich). Wir empfehlen, mit der Erstellung eines neuen Projekts in Visual Studio unter Verwendung einer der C++/WinRT-Projektvorlagen zu beginnen (siehe Visual Studio-Unterstützung für C++/WinRT). Verschieben Sie dann Ihre Quellcodedateien in das neue Projekt, und portieren Sie dabei den gesamten C++/CX-Quellcode nach C++/WinRT.

Wenn Sie die Portierung lieber in Ihrem bestehenden C++/CX-Projekt durchführen möchten, müssen Sie es um C++/WinRT-Unterstützung erweitern. Die Schritte, die Sie dazu ausführen müssen, sind in Erweitern eines bestehenden C++/CX-Projekts um C++/WinRT-Unterstützung beschrieben. Wenn Sie mit der Portierung fertig sind, haben Sie aus einem reinen C++/CX-Projekt ein reines C++/WinRT-Projekt gemacht.

Hinweis

Bei einem Windows-Runtime-Komponentenprojekt ist die Portierung in einem Durchgang Ihre einzige Option. Ein in C++ geschriebenes Windows-Runtime-Komponentenprojekt muss entweder den gesamten C++/CX-Quellcode oder den gesamten C++/WinRT-Quellcode enthalten. Es können in diesem Projekttyp nicht beide Quellcodes parallel vorhanden sein.

Schrittweise Portierung eines Projekts

Mit Ausnahme von Windows-Runtime-Komponentenprojekten, wie im vorigen Abschnitt erwähnt, benötigen Sie einen Portierungsprozess, bei dem C++/CX- und C++/WinRT-Code eine Zeit lang nebeneinander im selben Projekt existieren, wenn die Größe oder Komplexität Ihrer Codebasis eine schrittweise Portierung erforderlich macht. Schauen Sie sich zusätzlich zu diesem Thema auch die Themen Interoperabilität zwischen C++/WinRT und C++/CX sowie Asynchronität und Interoperabilität zwischen C++/WinRT und C++/CX an. Diese Themen enthalten Informationen und Codebeispiele zur Verdeutlichung der Interoperabilität zwischen den beiden Sprachprojektionen.

Um ein Projekt für einen schrittweisen Portierungsprozess vorzubereiten, besteht eine Möglichkeit darin, C++/WinRT-Unterstützung zu Ihrem C++/CX-Projekt hinzuzufügen. Die Schritte, die Sie dazu ausführen müssen, sind in Erweitern eines bestehenden C++/CX-Projekts um C++/WinRT-Unterstützung beschrieben. Von dort aus können Sie dann schrittweise portieren.

Eine weitere Möglichkeit besteht darin, ein neues Projekt in Visual Studio unter Verwendung einer der C++/WinRT-Projektvorlagen zu erstellen (siehe Visual Studio-Unterstützung für C++/WinRT), und dann C++/CX-Unterstützung zu diesem Projekt hinzuzufügen. Die Schritte, die Sie dazu ausführen müssen, sind in Erweitern eines bestehenden C++/WinRT-Projekts um C++/CX-Unterstützung beschrieben. Sie können dann damit beginnen, Ihren Quellcode dorthin zu verschieben und dabei einen Teil des C++/CX-Quellcodes nach C++/WinRT zu portieren.

In jedem Fall ist die Interoperabilität (in beiden Richtungen) zwischen Ihrem C++/WinRT-Code und dem restlichen C++/CX-Code, der noch nicht portiert wurde, gewährleistet.

Hinweis

Sowohl C++/CX als auch das Windows SDK deklarieren Typen im Stammnamespace Windows. Ein Windows-Typ, der in C++/WinRT projiziert wird, verfügt über den gleichen vollqualifizierten Namen wie der Windows-Typ, befindet sich aber im C++-winrt-Namespace. Diese unterschiedlichen Namespaces ermöglichen die Portierung von C++/CX zu C++/WinRT nach deinem eigenen Tempo.

Schrittweise Portierung eines XAML-Projekts

Wichtig

Für Projekt mit XAML müssen alle Ihre XAML-Seitentypen entweder vollständig in C++/CX oder vollständig in C++/WinRT vorliegen. Sie können weiterhin C++/CX und C++/WinRT außerhalb von XAML-Seitentypen innerhalb desselben Projekts mischen (in Ihren Modellen, ViewModels und anderswo).

Als Workflow für dieses Szenario wird empfohlen, ein neues C++ /WinRT-Projekt zu erstellen und Quellcode und Markup aus dem C++/CX-Projekt zu kopieren. Solange alle Ihre XAML-Seitentypen in C++/WinRT vorliegen, können Sie mit Projekt>Neues Element hinzufügen...>Visual C++>Leere Seite (C++/WinRT) neue XAML-Seiten hinzufügen.

Alternativ können Sie eine Windows-Runtime-Komponente (WRC) verwenden, um beim Portieren Code aus dem XAML-C++/CX-Projekt auszuklammern.

  • Sie könnten ein neues C++/CX WRC-Projekt erstellen, so viel C++/CX-Code wie möglich in dieses Projekt verschieben und dann das XAML-Projekt in C++/WinRT ändern.
  • Oder Sie könnten ein neues C++/WinRT WRC-Projekt erstellen, das XAML-Projekt als C++/CX belassen, mit der Portierung von C++/CX nach C++/WinRT beginnen und den resultierenden Code aus dem XAML-Projekt in das Komponentenprojekt verschieben.
  • Außerdem kannst du auch ein C++/CX-Komponentenprojekt neben einem C++/WinRT-Komponentenprojekt innerhalb derselben Lösung verwenden, auf beide aus deinem Anwendungsprojekt verweisen und nach und nach das Portieren von einem zum anderen durchführen. Unter Interoperabilität zwischen C++/WinRT und C++/CX finden Sie auch weitere Einzelheiten zur Verwendung der beiden Sprachprojektionen im selben Projekt.

Erste Schritte bei der Portierung eines C++/CX-Projekts zu C++/WinRT

Unabhängig von Ihrer Portierungsstrategie (Portierung in einem Durchgang oder schrittweise Portierung) besteht Ihr erster Schritt darin, Ihr Projekt auf die Portierung vorzubereiten. Hier finden Sie eine Zusammenfassung dessen, was wir in Strategien für die Portierung beschrieben haben, in Bezug auf die Art des Projekts, mit dem Sie beginnen, und dessen Einrichtung.

  • Portieren in einem Durchgang. Erstellen Sie ein neues Projekt in Visual Studio unter Verwendung einer der C++/WinRT-Projektvorlagen. Verschieben Sie die Dateien aus Ihrem C++/CX-Projekt in dieses neue Projekt, und portieren Sie den C++/CX-Quellcode.
  • Schrittweise Portierung eines Projekts (kein XAML-Projekt) . Sie können C++/WinRT-Unterstützung zu Ihrem C++/CX-Projekt hinzufügen (siehe Erweitern eines bestehenden C++/CX-Projekts um C++/WinRT-Unterstützung) und schrittweise portieren. Oder Sie können ein neues C++/WinRT-Projekt erstellen und C++/CX-Unterstützung hinzufügen (siehe Erweitern eines bestehenden C++/WinRT-Projekts um C++/CX-Unterstützung), Dateien in dieses verschieben, und schrittweise portieren.
  • Schrittweise Portierung eines XAML-Projekts. Erstellen Sie ein neues C++/WinRT-Projekt, verschieben Sie Dateien und portieren Sie nach und nach. Ihre XAML-Seitentypen müssen zu jedem beliebigen Zeitpunkt entweder alle in C++/WinRT oder alle in C++/CX vorliegen.

Der Rest dieses Themas gilt unabhängig davon, welche Portierungsstrategie Sie auswählen. Es enthält einen Katalog mit technischen Details, die bei der Portierung von Quellcode von C++/CX nach C++/WinRT wichtig sind. Wenn Sie eine schrittweise Portierung durchführen, sollten Sie auch Interoperabilität zwischen C++/WinRT und C++/CX und Asynchronität und Interoperabilität zwischen C++/WinRT und C++/CX lesen.

Konventionen für die Dateibenennung

XAML-Markupdateien

Dateiursprung C++/CX C++/WinRT
XAML-Entwicklerdateien MyPage.xaml
MyPage.xaml.h
MyPage.xaml.cpp
MyPage.xaml
MyPage.h
MyPage.cpp
MyPage.idl (siehe unten)
Generierte XAML-Dateien MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.g.h

Beachte, dass C++/WinRT die Zeichenfolge .xaml aus den Dateinamen *.h und *.cpp entfernt.

C++/WinRT fügt eine zusätzliche Entwicklerdatei hinzu: die Midl-Datei (.idl) . C++/CX generiert diese Datei automatisch intern und fügt sie zu jedem öffentlichen und geschützten Member hinzu. In C++/WinRT erstellst du die Datei selbst und fügst sie hinzu. Weitere Informationen, Codebeispiele und eine exemplarische Vorgehensweise zum Schreiben in IDL findest du unter XAML-Steuerelemente: Binden an eine C++/WinRT-Eigenschaft.

Siehe auch Einbeziehen von Laufzeitklassen in Midl-Dateien (.idl).

Laufzeitklassen

In C++/CX gibt es keine Einschränkungen in Bezug auf die Namen von Headerdateien; es ist gängige Praxis, mehrere Laufzeitklassendefinitionen in eine einzelne Headerdatei zu schreiben, insbesondere bei kleinen Klassen. C++/WinRT erfordert jedoch für jede Laufzeitklasse eine eigene Headerdatei, die nach dem Klassennamen benannt ist.

C++/CX C++/WinRT
Common.h
ref class A { ... }
ref class B { ... }
Common.idl
runtimeclass A { ... }
runtimeclass B { ... }
A.h
namespace implements {
  struct A { ... };
}
B.h
namespace implements {
  struct B { ... };
}

Weniger gängig, aber dennoch erlaubt ist in C++/CX die Verwendung von Headerdateien mit unterschiedlichen Namen für benutzerdefinierte XAML-Steuerelemente. Diese Headerdateien müssen umbenannt werden, damit sie dem Klassennamen entsprechen.

C++/CX C++/WinRT
A.xaml
<Page x:Class="LongNameForA" ...>
A.xaml
<Page x:Class="LongNameForA" ...>
A.h
partial ref class LongNameForA { ... }
LongNameForA.h
namespace implements {
  struct LongNameForA { ... };
}

Anforderungen an Headerdateien

In C++/CX musst du keine speziellen Headerdateien einschließen, weil Headerdateien automatisch aus .winmd-Dateien generiert werden. In C++/CX ist es gängige Praxis, using-Direktiven für Namespaces zu verwenden, die anhand des Namens genutzt werden.

using namespace Windows::Media::Playback;

String^ NameOfFirstVideoTrack(MediaPlaybackItem^ item)
{
    return item->VideoTracks->GetAt(0)->Name;
}

Die using namespace Windows::Media::Playback-Direktive ermöglicht es, MediaPlaybackItem ohne Namespacepräfix zu schreiben. Der Windows.Media.Core-Namespace ist ebenfalls beteiligt, weil item->VideoTracks->GetAt(0) ein Windows.Media.Core.VideoTrack-Element zurückgibt. Da der Name VideoTrack nirgendwo eingegeben werden muss, wird keine using Windows.Media.Core-Direktive benötigt.

In C++/WinRT musst du jedoch eine Headerdatei für jeden genutzten Namespace einschließen, auch wenn der Name nicht genannt wird.

#include <winrt/Windows.Media.Playback.h>
#include <winrt/Windows.Media.Core.h> // !!This is important!!

using namespace winrt;
using namespace Windows::Media::Playback;

winrt::hstring NameOfFirstVideoTrack(MediaPlaybackItem const& item)
{
    return item.VideoTracks().GetAt(0).Name();
}

Andererseits gilt auch: Obwohl das MediaPlaybackItem.AudioTracksChanged-Ereignis den Typ TypedEventHandler<MediaPlaybackItem, Windows.Foundation.Collections.IVectorChangedEventArgs> aufweist, müssen wir winrt/Windows.Foundation.Collections.h nicht einschließen, weil wir dieses Ereignis nicht verwendet haben.

In C++/WinRT musst du auch Headerdateien für Namespaces einschließen, die von XAML-Markup genutzt werden.

<!-- MainPage.xaml -->
<Rectangle Height="400"/>

Bei Verwendung der Rectangle-Klasse muss dieses Include hinzugefügt werden.

// MainPage.h
#include <winrt/Windows.UI.Xaml.Shapes.h>

Wenn du eine Headerdatei vergisst, funktioniert die Kompilierung ordnungsgemäß, aber du erhältst Linkerfehler, weil die consume_-Klassen fehlen.

Parameterübergabe

Beim Schreiben von C++/CX-Quellcode übergibst du C++/CX-Typen als Funktionsparameter in Form von Hütchenverweisen (^).

void LogPresenceRecord(PresenceRecord^ record);

In C++/WinRT solltest du für synchrone Funktionen standardmäßig const&-Parameter verwenden. Hierdurch werden Kopiervorgänge und unnötiger Zusatzaufwand vermieden. Für deine Coroutinen sollte aber Pass-by-Value verwendet werden, um sicherzustellen, dass sie nach Wert erfassen, und um Probleme mit der Lebensdauer zu vermeiden (weitere Informationen unter Parallelität und asynchrone Vorgänge mit C++/WinRT).

void LogPresenceRecord(PresenceRecord const& record);
IASyncAction LogPresenceRecordAsync(PresenceRecord const record);

Ein C++/WinRT-Objekt ist im Grunde genommen ein Wert, der einen Schnittstellenzeiger zum zugrunde liegenden Windows-Runtime-Objekt enthält. Beim Kopieren eines C++/WinRT-Objekts kopiert der Compiler den gekapselten Schnittstellenzeiger, wodurch sich sein Verweiszähler erhöht. Wenn die Kopie zerstört wird, wird der Verweiszähler verringert. Der mit einem Kopiervorgang verbundene Aufwand sollte also nur in Kauf genommen werden, wenn dies wirklich erforderlich ist.

Variablen und Feldverweise

Beim Schreiben von C++/CX-Quellcode verwendest du Hütchenvariablen (^) zum Verweisen auf Windows-Runtime-Objekte sowie den Pfeiloperator (->), um den Verweis einer Hütchenvariablen aufzuheben.

IVectorView<User^>^ userList = User::Users;

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList->Size; ++iUser)
    ...

Beim Portieren in den entsprechenden C++/WinRT-Code ist es sehr effektiv, die Hütchen zu entfernen und den Pfeiloperator (->) in den Punktoperator (.) zu ändern. Bei Typen mit C++/WinRT-Projektion handelt es sich um Werte, nicht um Zeiger.

IVectorView<User> userList = User::Users();

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList.Size(); ++iUser)
    ...

Der Standardkonstruktor für einen C++/CX-Handledeklaratorverweis (^) führt zu einer Initialisierung mit NULL. Hier ist ein C++/CX-Codebeispiel angegeben, in dem wir eine Variable bzw. ein Feld des richtigen Typs erstellen, die bzw. das aber nicht initialisiert ist. Es ist also anfänglich kein Verweis auf ein TextBlock-Element vorhanden. Wir weisen später einen Verweis zu.

TextBlock^ textBlock;

class MyClass
{
    TextBlock^ textBlock;
};

Informationen zur Entsprechung in C++/WinRT findest du unter Verzögerte Initialisierung.

Eigenschaften

Die C++/CX-Spracherweiterungen beinhalten das Konzept der „Eigenschaften“. Beim Schreiben von C++/CX-Quellcode kannst du so auf eine Eigenschaft zugreifen, als ob sie ein Feld wäre. Da der C++-Standardcode nicht über das Konzept einer Eigenschaft verfügt, rufst du in C++/WinRT Get- und Set-Funktionen auf.

In den folgenden Beispielen sind XboxUserId, UserState, PresenceDeviceRecords und Size jeweils Eigenschaften.

Abrufen eines Werts aus einer Eigenschaft

Hier ist beschrieben, wie du einen Eigenschaftswert in C++/CX erhältst.

void Sample::LogPresenceRecord(PresenceRecord^ record)
{
    auto id = record->XboxUserId;
    auto state = record->UserState;
    auto size = record->PresenceDeviceRecords->Size;
}

Der entsprechende C++/WinRT-Quellcode ruft eine Funktion mit dem gleichen Namen wie die Eigenschaft auf, aber ohne Parameter.

void Sample::LogPresenceRecord(PresenceRecord const& record)
{
    auto id = record.XboxUserId();
    auto state = record.UserState();
    auto size = record.PresenceDeviceRecords().Size();
}

Beachte hierbei, dass die Funktion PresenceDeviceRecords ein Windows-Runtime-Objekt zurückgibt, das über eine Size-Funktion verfügt. Da es sich beim zurückgegebenen Objekt ebenfalls um einen Typ mit C++/WinRT-Projektion handelt, führen wir das Dereferenzieren durch, indem wir den Punktoperator zum Aufrufen von Size verwenden.

Festlegen einer Eigenschaft auf einen neuen Wert

Beim Festlegen einer Eigenschaft auf einen neuen Wert wird ein ähnliches Muster angewandt. Zuerst in C++/CX.

record->UserState = newValue;

Um dasselbe in C++/WinRT durchzuführen, rufst du eine Funktion mit dem gleichen Namen wie die Eigenschaft auf und übergibst ein Argument.

record.UserState(newValue);

Erstellen einer Instanz einer Klasse

Hierfür verwendest du ein C++/ CX-Objekt über einen Handle, bekannt als Hütchenverweis (^). Du erstellst ein neues Objekt über das Schlüsselwort ref new, das wiederum RoActivateInstance aufruft, um eine neue Instanz der Laufzeitklasse zu aktivieren.

using namespace Windows::Storage::Streams;

class Sample
{
private:
    Buffer^ m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
};

Ein C++/WinRT-Objekt ist ein Wert. Folglich kannst du es im Stapel oder als Feld eines Objekts zuweisen. Du verwendest niemalsref new (oder new), um ein C++/WinRT-Objekt zuzuordnen. Im Hintergrund wird dennoch RoActivateInstance aufgerufen.

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
private:
    Buffer m_gamerPicBuffer{ MAX_IMAGE_SIZE };
};

Falls die Initialisierung einer Ressource aufwendig ist, wird dieser Vorgang meist verzögert, bis sie tatsächlich benötigt wird. Wie bereits erwähnt, führt der Standardkonstruktor für einen C++/CX-Handledeklaratorverweis (^) zu einer Initialisierung mit NULL.

using namespace Windows::Storage::Streams;

class Sample
{
public:
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer^ m_gamerPicBuffer;
};

Hier ist der gleiche Code nach dem Portieren zu C++/WinRT angegeben. Beachten Sie die Verwendung des std::nullptr_t-Konstruktors. Weitere Informationen zu diesem Konstruktor findest du unter Verzögerte Initialisierung.

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer m_gamerPicBuffer{ nullptr };
};

Auswirkungen des Standardkonstruktors auf Sammlungen

C++Sammlungstypen verwenden den Standardkonstruktor, was zu einer unbeabsichtigten Objektkonstruktion führen kann.

Szenario C++/CX C++/WinRT (falsch) C++/WinRT (richtig)
Lokale Variable, anfänglich leer TextBox^ textBox; TextBox textBox; // Creates a TextBox! TextBox textBox{ nullptr };
Membervariable, anfänglich leer class C {
  TextBox^ textBox;
};
class C {
  TextBox textBox; // Creates a TextBox!
};
class C {
  TextBox textbox{ nullptr };
};
Globale Variable, anfänglich leer TextBox^ g_textBox; TextBox g_textBox; // Creates a TextBox! TextBox g_textBox{ nullptr };
Vektor von leeren Verweisen std::vector<TextBox^> boxes(10); // Creates 10 TextBox objects!
std::vector<TextBox> boxes(10);
std::vector<TextBox> boxes(10, nullptr);
Festlegen eines Werts in einer Zuordnung std::map<int, TextBox^> boxes;
boxes[2] = value;
std::map<int, TextBox> boxes;
// Creates a TextBox at 2,
// then overwrites it!
boxes[2] = value;
std::map<int, TextBox> boxes;
boxes.insert_or_assign(2, value);
Array aus leeren Verweisen TextBox^ boxes[2]; // Creates 2 TextBox objects!
TextBox boxes[2];
TextBox boxes[2] = { nullptr, nullptr };
Koppeln std::pair<TextBox^, String^> p; // Creates a TextBox!
std::pair<TextBox, String> p;
std::pair<TextBox, String> p{ nullptr, nullptr };

Weitere Informationen zu Sammlungen leerer Verweise

Wenn du über ein Platform::Array^-Element in C++ verfügst (siehe Port Platform:: Array^), kannst du es in ein std::vector-Element in C++/WinRT portieren (tatsächlich in jeden beliebigen zusammenhängenden Container), anstatt es als Array zu belassen. Die Auswahl von std::vector bietet Vorteile.

Während es beispielsweise eine Kurzform für die Erstellung eines Vektors von leeren Verweisen mit fester Größe gibt (siehe Tabelle oben), gibt es keine solche Kurzform für die Erstellung eines Arrays von leeren Verweisen. Du musst nullptr für jedes Element im Array wiederholen. Wenn zu wenige „nullptr“ vorhanden sind, werden die zusätzlichen Elemente standardmäßig konstruiert.

Einen Vektor kannst du bei der Initialisierung mit leeren Verweisen füllen (wie in der obigen Tabelle), oder du kannst ihn nach der Initialisierung mit einem Code wie diesem mit leeren Verweisen füllen.

std::vector<TextBox> boxes(10); // 10 default-constructed TextBoxes.
boxes.resize(10, nullptr); // 10 empty references.

Weitere Informationen zum std::map-Beispiel

Der subscript-Operator [] für std::map weist folgendes Verhalten auf.

  • Wenn der Schlüssel in der Zuordnung gefunden wird, wird ein Verweis auf den vorhandenen Wert zurückgegeben (den du überschreiben kannst).
  • Wenn der Schlüssel in der Zuordnung nicht gefunden wird, wird ein neuer Eintrag in der Zuordnung erstellt, der den Schlüssel (verschoben, wenn verschiebbar) und einen standardmäßig konstruierten Wert enthält. Dann wird ein Verweis auf den Wert zurückgegeben (den du dann überschreiben kannst).

Anders gesagt: Der []-Operator erstellt immer einen Eintrag in der Zuordnung. Dieses Verhalten unterscheidet sich von C#, Java und JavaScript.

Konvertieren von einer Basis-Laufzeitklasse zu einer abgeleiteten Klasse

Üblicherweise wird ein Verweis auf die Basis verwendet, für die bekannt ist, dass auf ein Objekt mit einem abgeleiteten Typ verwiesen wird. In C++/CX verwendest du dynamic_cast, um „Verweis auf Basis“ in „Verweis auf abgeleitet“ umzuwandeln. dynamic_cast ist eigentlich nur ein versteckter Aufruf von QueryInterface. Hier ist ein typisches Beispiel angegeben: Du verarbeitest ein Ereignis für eine geänderte Abhängigkeitseigenschaft und möchtest die Umwandlung von DependencyObject zurück in den eigentlichen Typ durchführen, der der Besitzer der Abhängigkeitseigenschaft ist.

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject^ d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ e)
{
    BgLabelControl^ theControl{ dynamic_cast<BgLabelControl^>(d) };

    if (theControl != nullptr)
    {
        // succeeded ...
    }
}

Der entsprechende C++/WinRT-Code ersetzt dynamic_cast durch einen Aufruf der Funktion IUnknown::try_as, in die QueryInterface eingekapselt ist. Darüber hinaus hast du die Option, stattdessen IUnknown::as aufzurufen. Hierbei wird eine Ausnahme ausgelöst, wenn bei der Abfrage der erforderlichen Schnittstelle (Standardschnittstelle des angeforderten Typs) keine Rückgabe erfolgt. Hier ist ein C++/WinRT-Codebeispiel angegeben.

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
    {
        // succeeded ...
    }

    try
    {
        BgLabelControlApp::BgLabelControl theControl{ d.as<BgLabelControlApp::BgLabelControl>() };
        // succeeded ...
    }
    catch (winrt::hresult_no_interface const&)
    {
        // failed ...
    }
}

Abgeleitete Klassen

Um aus einer Laufzeitklasse abzuleiten, muss die Basisklasse zusammensetzbar sein. In C++/CX sind keine besonderen Schritte erforderlich, um Klassen zusammensetzbar zu machen, in C++/WinRT schon. Du verwendest das unsealed-Schlüsselwort, um anzugeben, dass die Klasse als Basisklasse verwendet werden kann.

unsealed runtimeclass BasePage : Windows.UI.Xaml.Controls.Page
{
    ...
}
runtimeclass DerivedPage : BasePage
{
    ...
}

In der Implementierungsheaderklasse musst du die Basisklassen-Headerdatei einschließen, bevor du den automatisch generierten Header für die abgeleitete Klasse einschließt. Andernfalls erhältst du Fehler wie „Ungültige Verwendung dieses Typs als Ausdruck“.

// DerivedPage.h
#include "BasePage.h"       // This comes first.
#include "DerivedPage.g.h"  // Otherwise this header file will produce an error.

namespace winrt::MyNamespace::implementation
{
    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        ...
    }
}

Ereignisbehandlung mit einem Delegaten

Hier ist ein typisches Beispiel für die Behandlung eines Ereignisses in C++/CX mit einer Lambda-Funktion als Delegat angegeben.

auto token = myButton->Click += ref new RoutedEventHandler([=](Platform::Object^ sender, RoutedEventArgs^ args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

Dies ist die Entsprechung in C++/WinRT.

auto token = myButton().Click([=](IInspectable const& sender, RoutedEventArgs const& args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

Anstelle einer Lambda-Funktion kannst du den Delegaten als freie Funktion oder als Pointer-to-Member-Funktion (Zeiger auf Member) implementieren. Weitere Informationen findest du unter Verarbeiten von Ereignissen über Delegaten in C++/WinRT.

Wenn du von einer C++/CX-Codebasis portierst, für die Ereignisse und Delegate intern verwendet werden (nicht über Binärdateien), hilft dir winrt::delegate beim Replizieren dieses Musters in C++/WinRT weiter. Siehe auch Parametrisierte Delegaten, einfache Signale und Rückrufe in einem Projekt.

Widerrufen eines Delegaten

In C++/CX verwendest du den Operator -=, um eine frühere Ereignisregistrierung zu widerrufen.

myButton->Click -= token;

Dies ist die Entsprechung in C++/WinRT.

myButton().Click(token);

Weitere Informationen und Optionen findest du unter Einen registrierten Delegaten widerrufen.

Boxing und Unboxing

C++/CX führt automatisch ein Boxing von Skalaren zu Objekten durch. In C++/WinRT musst du die Funktion winrt::box_value explizit aufrufen. In beiden Sprachen musst du ein Unboxing explizit angeben. Siehe Boxing und Unboxing mit C++/WinRT.

In der unten stehenden Tabelle werden folgende Definitionen verwendet.

C++/CX C++/WinRT
int i; int i;
String^ s; winrt::hstring s;
Object^ o; IInspectable o;
Vorgang C++/CX C++/WinRT
Boxing o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
Unboxing i = (int)o;
s = (String^)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

C++/CX und C# lösen Ausnahmen aus, wenn du versuchst, ein Unboxing eines NULL-Zeigers auf einen Werttyp auszuführen. C++/WinRT betrachtet dies als Programmierfehler und stürzt ab. Verwende in C++/WinRT die Funktion winrt::unbox_value_or, wenn ein Objekt nicht den von dir angenommenen Typ aufweist.

Szenario C++/CX C++/WinRT
Unboxing eines bekannten integer-Elements i = (int)o; i = unbox_value<int>(o);
Wenn „o“ NULL ist Platform::NullReferenceException Absturz
Wenn „o“ kein geboxter int-Wert ist Platform::InvalidCastException Absturz
Unboxing für „int“, Fallback bei NULL; Absturz in allen anderen Fällen i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Unboxing für „int“ (falls möglich); Fallback in allen anderen Fällen auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

Boxing und Unboxing von string-Elementen

Ein string-Element ist in einigen Fällen ein Werttyp, in anderen Fällen ein Verweistyp. C++/CX und C++/WinRT behandeln string-Elemente unterschiedlich.

Der ABI-Typ HSTRING ist ein Zeiger auf ein als Verweis gezähltes string-Element. Er wird jedoch nicht von IInspectable abgeleitet, ist also technisch gesehen kein Objekt. Darüber hinaus stellt ein HSTRING mit Wert NULL ein leeres string-Element dar. Das Boxing von Elementen, die nicht von IInspectable abgeleitet werden, erfolgt über den Einschluss in IReference<T>, und die Windows-Runtime stellt eine Standardimplementierung in Form des PropertyValue-Objekts bereit (benutzerdefinierte Typen werden als PropertyType::OtherType gemeldet).

C++/CX stellt ein string-Element der Windows-Runtime als Referenztyp dar, C++/WinRT dagegen als Werttyp. Das bedeutet, dass eine geboxte NULL-Zeichenfolge unterschiedliche Darstellungen aufweisen kann, je nachdem, wie du dorthin gelangt bist.

Darüber hinaus erlaubt C++/CX das Dereferenzieren eines String^ -Elements mit NULL-Wert, woraufhin dieses Element sich wie die Zeichenfolge "" verhält.

Verhalten C++/CX C++/WinRT
Deklarationen Object^ o;
String^ s;
IInspectable o;
hstring s;
Kategorie des string-Typs Verweistyp Werttyp
HSTRING mit NULL-Wert wird dargestellt als (String^)nullptr hstring{}
Sind NULL und "" identisch? Ja Ja
Gültigkeit von NULL s = nullptr;
s->Length == 0 (gültig)
s = hstring{};
s.size() == 0 (gültig)
Wenn Sie einem Objekt eine NULL-Zeichenfolge zuweisen o = (String^)nullptr;
o == nullptr
o = box_value(hstring{});
o != nullptr
Wenn Sie einem Objekt "" zuweisen o = "";
o == nullptr
o = box_value(hstring{L""});
o != nullptr

Grundlegendes Boxing und Unboxing

Vorgang C++/CX C++/WinRT
Boxing eines string-Elements o = s;
Eine leere Zeichenfolge wird zu einem nullptr.
o = box_value(s);
Eine leere Zeichenfolge wird zu einem Nicht-NULL-Objekt.
Unboxing eines bekannten string-Elements s = (String^)o;
Ein NULL-Objekt wird zu einer leeren Zeichenfolge.
InvalidCastException, falls keine Zeichenfolge.
s = unbox_value<hstring>(o);
Absturz eines NULL-Objekts.
Absturz, falls keine Zeichenfolge.
Unboxing einer möglichen Zeichenfolge s = dynamic_cast<String^>(o);
Ein NULL-Objekt oder eine Nicht-Zeichenfolge wird zu einer leeren Zeichenfolge.
s = unbox_value_or<hstring>(o, fallback);
NULL oder eine Nicht-Zeichenfolge wird zu einem Fallback.
Leere Zeichenfolge beibehalten.

Parallelität und asynchrone Vorgänge

Die Parallel Patterns Library (PPL) (beispielsweise concurrency::task) wurde aktualisiert und unterstützt jetzt C++/CX-Handledeklaratorverweise (^).

In C++/WinRT solltest du stattdessen Coroutinen und co_await verwenden. Weitere Informationen und Codebeispiele findest du unter Parallelität und asynchrone Vorgänge mit C++/WinRT.

Verwenden von Objekten aus XAML-Markup

In einem C++/CX-Projekt kannst du private Member und benannte Elemente aus XAML-Markup nutzen. In C++/WinRT dagegen müssen alle Entitäten, die über die XAML-{x:Bind}-Markuperweiterung genutzt werden, in IDL öffentlich verfügbar gemacht werden.

Durch Binden an einen booleschen Typ wird in C++/CX true oder false angezeigt, in C++/WinRT dagegen Windows.Foundation.IReference`1<Boolean>.

Weitere Informationen sowie Codebeispiele findest du unter Verwenden von Objekten aus Markup.

Zuordnen von C++/CX-Platform-Typen zu C++/WinRT-Typen

Unter C++/CX werden im Platform-Namespace verschiedene Datentypen bereitgestellt. Diese Typen gehören nicht zum C++-Standard. Daher kannst du sie nur verwenden, wenn du die Windows-Runtime-Spracherweiterungen aktivierst (Visual Studio-Projekteigenschaft C/C++>Allgemein>Windows-Runtime-Erweiterung verwenden>Ja (/ZW) ). In der Tabelle unten findest du Informationen zum Portieren von Platform-Typen in ihre Entsprechungen in C++/WinRT. Anschließend kannst du die Option /ZW deaktivieren, da C++/WinRT zum C++-Standard gehört.

C++/CX C++/WinRT
Platform::Agile^ winrt::agile_ref
Platform::Array^ Siehe Portieren von Platform::Array^
Platform::Exception^ winrt::hresult_error
Platform::InvalidArgumentException^ winrt::hresult_invalid_argument
Platform::Object^ winrt::Windows::Foundation::IInspectable
Platform::String^ winrt::hstring

Portieren von Platform::Agile^ zu winrt::agile_ref

Der Typ Platform::Agile^ in C++/CX stellt eine Windows-Runtime-Klasse dar, auf die über einen beliebigen Thread zugegriffen werden kann. Die C++/WinRT-Entsprechung lautet winrt::agile_ref.

In C++/CX:

Platform::Agile<Windows::UI::Core::CoreWindow> m_window;

In C++/WinRT:

winrt::agile_ref<Windows::UI::Core::CoreWindow> m_window;

Portieren von Platform::Array^

In Fällen, in denen C++/CX die Verwendung eines Arrays erfordert, erlaubt C++/WinRT die Verwendung eines beliebigen zusammenhängenden Containers. Unter Auswirkungen des Standardkonstruktors auf Sammlungen wird erläutert, warum std::vector eine gute Wahl ist.

Wenn du also ein Platform::Array^-Element in C++/CX verwendest, gehören die Verwendung einer Initialisiererliste, eines std::array- oder std::vector-Elements zu deinen Portierungsoptionen. Weitere Informationen und Codebeispiele findest du unter Standard-Initialisierungslisten und Standard-Arrays und -Vektoren.

Portieren von Platform::Exception^ zu winrt::hresult_error

Der Typ Platform::Exception^ wird in C++/CX erzeugt, wenn eine Windows-Runtime-API ein Nicht-S_OK HRESULT zurückgibt. Für C++/WinRT ist die Entsprechung winrt::hresult_error.

Zum Portieren zu C++/WinRT musst du den gesamten Code ändern, für den Platform::Exception^ verwendet wird, um stattdessen winrt::hresult_error zu nutzen.

In C++/CX:

catch (Platform::Exception^ ex)

In C++/WinRT:

catch (winrt::hresult_error const& ex)

C++/WinRT stellt die hier angegebenen Ausnahmeklassen bereit.

Ausnahmetyp Basisklasse HRESULT
winrt::hresult_error Aufruf von hresult_error::to_abi
winrt::hresult_access_denied winrt::hresult_error E_ACCESSDENIED
winrt::hresult_canceled winrt::hresult_error ERROR_CANCELLED
winrt::hresult_changed_state winrt::hresult_error E_CHANGED_STATE
winrt::hresult_class_not_available winrt::hresult_error CLASS_E_CLASSNOTAVAILABLE
winrt::hresult_illegal_delegate_assignment winrt::hresult_error E_ILLEGAL_DELEGATE_ASSIGNMENT
winrt::hresult_illegal_method_call winrt::hresult_error E_ILLEGAL_METHOD_CALL
winrt::hresult_illegal_state_change winrt::hresult_error E_ILLEGAL_STATE_CHANGE
winrt::hresult_invalid_argument winrt::hresult_error E_INVALIDARG
winrt::hresult_no_interface winrt::hresult_error E_NOINTERFACE
winrt::hresult_not_implemented winrt::hresult_error E_NOTIMPL
winrt::hresult_out_of_bounds winrt::hresult_error E_BOUNDS
winrt::hresult_wrong_thread winrt::hresult_error RPC_E_WRONG_THREAD

Beachte, dass jede Klasse (über die hresult_error-Basisklasse) eine to_abi-Funktion bereitstellt, die das HRESULT für den Fehler zurückgibt, sowie eine message-Funktion, die die Darstellung der Zeichenfolge dieses HRESULT zurückgibt.

Hier ist ein Beispiel für das Auslösen einer Ausnahme in C++/CX angegeben:

throw ref new Platform::InvalidArgumentException(L"A valid User is required");

Dies ist die Entsprechung in C++/WinRT:

throw winrt::hresult_invalid_argument{ L"A valid User is required" };

Portieren von Platform::Object^ zu winrt::Windows::Foundation::IInspectable

Wie alle C++/WinRT-Typen ist winrt::Windows::Foundation::IInspectable ein Werttyp. Hier erfährst du, wie du eine Variable dieses Typs auf null initialisierst.

winrt::Windows::Foundation::IInspectable var{ nullptr };

Portieren von Platform::String zu winrt::hstring

Platform::String^ ist das Äquivalent zum HSTRING-ABI-Typ der Windows-Runtime. Für C++/WinRT ist die Entsprechung winrt::hstring. Bei C++/WinRT kannst du aber Windows-Runtime-APIs mit Wide-String-Typen der C++-Standardbibliothek, z. B. std::wstring, bzw. Wide-String-Literale aufrufen. Weitere Informationen und Codebeispiele findest du unter String-Verarbeitung in C++/WinRT.

Mit C++/CX können Sie auf die Eigenschaft Platform::String::Data zugreifen, um die Zeichenfolge als const wchar_t*-Array im C-Stil abzurufen (z. B. zur Übergabe an std::wcout).

auto var{ titleRecord->TitleName->Data() };

Mit C++/WinRT erreichst du dies, indem du die Funktion hstring::c_str verwendest, um eine auf null beendete Zeichenfolgenversion im C-Stil abzurufen (wie über std::wstring).

auto var{ titleRecord.TitleName().c_str() };

Bei der Implementierung von APIs, die Zeichenfolgen übernehmen oder zurückgeben, änderst du in der Regel jeglichen C++/CX-Code, der Platform::String^ verwendet, um stattdessen winrt::hstring zu nutzen.

Hier ist ein Beispiel für eine C++/CX-API angegeben, für die eine Zeichenfolge verwendet wird:

void LogWrapLine(Platform::String^ str);

Für C++/WinRT kannst du diese API in MIDL 3.0 beispielsweise wie folgt deklarieren:

// LogType.idl
void LogWrapLine(String str);

Die C++/WinRT-Toolkette generiert dann Quellcode für dich, der wie folgt aussieht.

void LogWrapLine(winrt::hstring const& str);

ToString()

C++/CX-Typen stellen die Object::ToString-Methode bereit.

int i{ 2 };
auto s{ i.ToString() }; // s is a Platform::String^ with value L"2".

In C++/WinRT ist dies nicht direkt verfügbar, aber du kannst Alternativen nutzen.

int i{ 2 };
auto s{ std::to_wstring(i) }; // s is a std::wstring with value L"2".

C++/WinRT unterstützt auch winrt::to_hstring für eine begrenzte Anzahl von Typen. Du musst Überladungen für alle zusätzlichen Typen hinzufügen, für die du eine Stringification durchführen möchtest.

Language Stringification von „int“ Stringification von „enum“
C++/CX String^ result = "hello, " + intValue.ToString(); String^ result = "status: " + status.ToString();
C++/WinRT hstring result = L"hello, " + to_hstring(intValue); // must define overload (see below)
hstring result = L"status: " + to_hstring(status);

Bei der Stringification eines enum-Typs musst du die Implementierung von winrt::to_hstring bereitstellen.

namespace winrt
{
    hstring to_hstring(StatusEnum status)
    {
        switch (status)
        {
        case StatusEnum::Success: return L"Success";
        case StatusEnum::AccessDenied: return L"AccessDenied";
        case StatusEnum::DisabledByPolicy: return L"DisabledByPolicy";
        default: return to_hstring(static_cast<int>(status));
        }
    }
}

Diese Stringifications werden häufig implizit von der Datenbindung verwendet.

<TextBlock>
You have <Run Text="{Binding FlowerCount}"/> flowers.
</TextBlock>
<TextBlock>
Most recent status is <Run Text="{x:Bind LatestOperation.Status}"/>.
</TextBlock>

Diese Bindungen führen winrt::to_hstring der gebundenen Eigenschaft durch. Im zweiten Beispiel (StatusEnum) musst du eine eigene Überladung von winrt::to_hstring bereitstellen, andernfalls erhältst du einen Compilerfehler.

Erstellung von string-Elementen

C++/CX und C++/WinRT nutzen ein standardmäßiges std::wstringstream-Element für die Erstellung von string-Elementen.

Vorgang C++/CX C++/WinRT
Anfügen eines string-Elements, NULL-Werte beibehalten stream.print(s->Data(), s->Length); stream << std::wstring_view{ s };
Anfügen eines string-Elements, bei erstem NULL-Wert beenden stream << s->Data(); stream << s.c_str();
Ergebnisse extrahieren ws = stream.str(); ws = stream.str();

Weitere Beispiele anzeigen

In den unten stehenden Beispielen ist ws eine Variable des Typs std::wstring. C++/CX kann ein Platform::String-Element aus einer 8-Bit-Zeichenfolge erstellen, dies ist in C++/WinRT nicht möglich.

Vorgang C++/CX C++/WinRT
string-Element aus Literal erstellen String^ s = "hello";
String^ s = L"hello";
// winrt::hstring s{ "hello" }; // Doesn't compile
winrt::hstring s{ L"hello" };
Aus std::wstring konvertieren, NULL-Werte beibehalten String^ s = ref new String(ws.c_str(),
  (uint32_t)ws.size());
winrt::hstring s{ ws };
s = winrt::hstring(ws);
// s = ws; // Doesn't compile
Aus std::wstring konvertieren, bei erstem NULL-Wert beenden String^ s = ref new String(ws.c_str()); winrt::hstring s{ ws.c_str() };
s = winrt::hstring(ws.c_str());
// s = ws.c_str(); // Doesn't compile
Zu std::wstring konvertieren, NULL-Werte beibehalten std::wstring ws{ s->Data(), s->Length };
ws = std::wstring(s>Data(), s->Length);
std::wstring ws{ s };
ws = s;
Zu std::wstring konvertieren, bei erstem NULL-Wert beenden std::wstring ws{ s->Data() };
ws = s->Data();
std::wstring ws{ s.c_str() };
ws = s.c_str();
Literal an Methode übergeben Method("hello");
Method(L"hello");
// Method("hello"); // Doesn't compile
Method(L"hello");
std::wstring an Methode übergeben Method(ref new String(ws.c_str(),
  (uint32_t)ws.size()); // Stops on first null
Method(ws);
// param::winrt::hstring accepts std::wstring_view

Wichtige APIs