Freigeben über


Von C++/CX zu C++/WinRT wechseln.

Dieses Thema ist der erste in einer Reihe, in der beschrieben wird, wie Sie den Quellcode in Ihrem C++/CX- Projekt zu seiner Entsprechung in C++/WinRT-portieren können.

Wenn Ihr Projekt auch Windows-Runtime-C++-Vorlagenbibliothek (WRL) Typen verwendet, lesen Sie Wechseln zu C++/WinRT aus WRL-.

Strategien für die Portierung

Es ist wichtig zu wissen, dass das Portieren von C++/CX zu C++/WinRT im Allgemeinen einfach ist, mit der einzigen Ausnahme des Wechsels von Parallel Patterns Library (PPL) Tasks zu Coroutinen. Die Modelle unterscheiden sich. Es gibt keine natürliche 1:1-Zuordnung von PPL-Aufgaben zu Coroutinen, und es gibt keine einfache Möglichkeit, den Code, der für alle Fälle funktioniert, mechanisch zu portieren. Hilfe zu diesem spezifischen Aspekt der Portierung sowie der Optionen für die Interoperabilität zwischen den beiden Modellen sehen Sie unter Asynchrony und Interoperabilität zwischen C++/WinRT und C++/CX.

Entwicklungsteams melden regelmäßig, dass der Rest der Portierungsarbeiten weitgehend mechanisch ist, sobald sie die Hürde der Portierung ihres asynchronen Codes überschreiten.

Portierung in einem Schritt

Wenn Sie in der Lage sind, Ihr gesamtes Projekt in einem Durchgang zu portieren, benötigen Sie nur dieses Thema für die benötigten Informationen (und Sie brauchen die Interoperabilität Themen, die auf dieses folgen, nicht). Es wird empfohlen, zunächst ein neues Projekt in Visual Studio mithilfe einer der C++/WinRT-Projektvorlagen zu erstellen (siehe Visual Studio-Unterstützung für C++/WinRT). Verschieben Sie dann Ihre Quellcodedateien in das neue Projekt, und portieren Sie währenddessen den gesamten C++/CX-Quellcode auf C++/WinRT.

Wenn Sie es vorziehen, die Portierung in Ihrem vorhandenen C++/CX-Projekt zu erledigen, dann müssen Sie diesem auch C++/WinRT-Unterstützung hinzufügen. Die Schritte, die Sie ausführen, werden in Erstellung eines C++/CX-Projekts und Hinzufügung von C++/WinRT-Unterstützungbeschrieben. Wenn Sie mit der Portierung fertig sind, haben Sie das, was ein reines C++/CX-Projekt war, in ein reines C++/WinRT-Projekt umgewandelt.

Hinweis

Wenn Sie über ein Komponentenprojekt für Windows-Runtime verfügen, ist das Portieren in einem Durchlauf Ihre einzige Option. Ein in C++ geschriebenes Windows-Runtime-Komponentenprojekt muss entweder den gesamten C++/CX-Quellcode oder den gesamten C++/WinRT-Quellcode enthalten. Sie können in diesem Projekttyp nicht koexistieren.

Schrittweises Portieren eines Projekts

Mit Ausnahme von Windows-Runtime-Komponentenprojekten, wie im vorherigen Abschnitt erwähnt, benötigen Sie einen Portierungsprozess, wenn die Größe oder Komplexität Ihrer Codebasis es notwendig macht, Ihr Projekt schrittweise zu portieren. In diesem Fall müssen C++/CX- und C++/WinRT-Code für eine Zeit lang parallel im selben Projekt existieren. Zusätzlich zur Lektüre dieses Themas sollten Sie auch Interoperabilität zwischen C++/WinRT und C++/CX sowie Asynchronie und Interoperabilität zwischen C++/WinRT und C++/CXbetrachten. Diese Themen enthalten Informationen und Codebeispiele, die zeigen, wie zwischen den beiden Sprachprojektionen interoperiert wird.

Um ein Projekt für einen schrittweisen Portierungsprozess vorzubereiten, besteht eine Option darin, Ihrem C++/CX-Projekt C++/WinRT-Unterstützung hinzuzufügen. Die Schritte, die Sie ausführen, werden in Erstellung eines C++/CX-Projekts und Hinzufügung von C++/WinRT-Unterstützungbeschrieben. Sie können dann von dort aus schrittweise migrieren.

Eine weitere Option besteht darin, ein neues Projekt in Visual Studio mit einer der C++/WinRT-Projektvorlagen zu erstellen (siehe Visual Studio-Unterstützung für C++/WinRT). Fügen Sie dann dem Projekt C++/CX-Unterstützung hinzu. Die Schritte, die Sie ausführen, werden in Erstellen eines C++/WinRT-Projekts und das Hinzufügen von C++/CX-Unterstützungbeschrieben. Anschließend können Sie mit dem Verschieben des Quellcodes zu diesem Code beginnen und einige des C++/CX-Quellcodes in C++/WinRT portieren.

In beiden Fällen wird Ihr C++/WinRT-Code in beide Richtungen mit jedem C++/CX-Code zusammenarbeiten, den Sie noch nicht portiert haben.

Hinweis

Sowohl C++/CX- als auch das Windows SDK deklarieren Typen im Stammnamespace Windows. Ein in C++/WinRT projizierter Windows-Typ hat den gleichen vollqualifizierten Namen wie der Windows-Typ, wird jedoch im C++-winrt Namespace platziert. Mit diesen unterschiedlichen Namespaces können Sie aus C++/CX zu C++/WinRT in Ihrem eigenen Tempo portieren.

Schrittweises Portieren eines XAML-Projekts

Von Bedeutung

Für ein Projekt, das XAML verwendet, müssen zu jedem Zeitpunkt alle XAML-Seitentypen entweder vollständig C++/CX oder vollständig C++/WinRT sein. Sie können weiterhin C++/CX und C++/WinRT außerhalb von XAML-Seitentypen innerhalb desselben Projekts mischen, zum Beispiel in Ihren Modellen, Ansichtsmodellen und anderswo.

Für dieses Szenario empfiehlt es sich, ein neues C++/WinRT-Projekt zu erstellen und Quellcode und Markup aus dem C++/CX-Projekt zu kopieren. Solange alle XAML-Seitentypen C++/WinRT sind, können Sie neue XAML-Seiten mit Project>Add New Item...>Visual C++>Blank Page (C++/WinRT)hinzufügen.

Alternativ können Sie eine Windows-Runtime-Komponente (WRC) verwenden, um Code aus dem XAML-C++/CX-Projekt auszugliedern, während Sie es portieren.

  • Sie können 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.
  • Alternativ können Sie ein neues C++/WinRT WRC-Projekt erstellen, das XAML-Projekt als C++/CX belassen und mit der Portierung von C++/CX zu C++/WinRT beginnen und den resultierenden Code aus dem XAML-Projekt und in das Komponentenprojekt verschieben.
  • Es ist auch möglich, ein C++/CX-Komponentenprojekt neben einem C++/WinRT-Komponentenprojekt in derselben Lösung zu haben, beide von Ihrem Anwendungsprojekt zu referenzieren und schrittweise von einem zum anderen zu migrieren. Weitere Informationen zur Verwendung der beiden Sprachprojektionen im selben Projekt finden Sie unter Interoperabilität zwischen C++/WinRT und C++/CX.

Erste Schritte beim Portieren eines C++/CX-Projekts zu C++/WinRT

Unabhängig davon, was Ihre Portierungsstrategie sein wird (Portieren in einem Durchgang oder schrittweise Portierung), besteht der erste Schritt darin, Ihr Projekt für die Portierung vorzubereiten. Hier ist eine Zusammenfassung dessen, was wir in Strategien zum Portieren von in Bezug auf die Art des Projekts, mit dem Sie beginnen werden, und wie man es einrichtet, beschrieben haben.

  • Portierung in einem Durchgang. Erstellen Sie ein neues Projekt in Visual Studio mit einer der C++/WinRT-Projektvorlagen. Verschieben Sie die Dateien aus Ihrem C++/CX-Projekt in das neue Projekt, und portieren Sie den C++/CX-Quellcode.
  • Portieren eines Nicht-XAML-Projekts schrittweise. Sie können Ihrem C++/CX-Projekt die C++/WinRT-Unterstützung hinzufügen (siehe Erstellen eines C++/CX-Projekts und Hinzufügen der C++/WinRT-Unterstützung), und schrittweise migrieren. Sie können auch ein neues C++/WinRT-Projekt erstellen und diesem C++/CX-Unterstützung hinzufügen (siehe Erstellen eines C++/WinRT-Projekts und Hinzufügen der C++/CX-Unterstützung), Dateien verschieben und nach und nach portieren.
  • Schrittweises Portieren eines XAML-Projekts. Erstellen Sie ein neues C++/WinRT-Projekt, verschieben Sie Dateien nach und nach, und portieren Sie sie schrittweise. Zu einem bestimmten Zeitpunkt müssen Die XAML-Seitentypen entweder alle C++/WinRT-oder alle C++/CX werden.

Der Rest dieses Themas gilt unabhängig davon, welche Portierungsstrategie Sie auswählen. Sie enthält einen Katalog mit technischen Details, die beim Portieren von Quellcode von C++/CX zu C++/WinRT erforderlich sind. Wenn Sie die Portierung schrittweise ausführen, sollten Sie wahrscheinlich auch Interop zwischen C++/WinRT und C++/CX und Asynchronie sowie die Interoperabilität zwischen C++/WinRT und C++/CXsehen.

Dateibenennungskonventionen

XAML-Markup-Dateien

Dateiursprung C++/CX C++/WinRT
XAML-Dateien für Entwickler 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

Beachten Sie, dass C++/WinRT den .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 intern automatisch und fügt sie jedem öffentlichen und geschützten Mitglied hinzu. In C++/WinRT fügen Sie die Datei selbst hinzu und erstellen sie. Weitere Details, Codebeispiele und eine Schritt-für-Schritt-Anleitung zur Erstellung von IDL finden Sie unter XAML-Steuerelemente; Bindung an eine C++/WinRT-Eigenschaft.

Siehe auch Faktorisierung von Laufzeit-Klassen in zu Midl-Dateien (.idl)

Laufzeitklassen

C++/CX erzwingt keine Einschränkungen für die Namen Ihrer Headerdateien; Es ist üblich, dass mehrere Laufzeitklassendefinitionen in eine einzelne Headerdatei eingefügt werden, insbesondere für kleine Klassen. C++/WinRT erfordert jedoch, dass jede Laufzeitklasse über eine eigene Headerdatei verfügt, 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 häufig (aber immer noch legal) in C++/CX ist die Verwendung unterschiedlich benannter Headerdateien für benutzerdefinierte XAML-Steuerelemente. Sie müssen diese Headerdatei umbenennen, um dem Klassennamen zu 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 für Header-Dateien

C++/CX erfordert keine speziellen Headerdateien, da es intern Headerdateien aus .winmd files automatisch generiert. Es ist üblich, dass in C++/CX using Direktiven für Namespaces verwendet werden, die Sie nach Namen verwenden.

using namespace Windows::Media::Playback;

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

Mit der Richtlinie using namespace Windows::Media::Playback können wir MediaPlaybackItem ohne Namespace-Präfix schreiben. Wir haben auch den Windows.Media.Core-Namespace bearbeitet, da item->VideoTracks->GetAt(0) einen Windows.Media.Core.VideoTrackzurückgibt. Aber wir mussten den Namen VideoTrack nirgendwo eingeben, also brauchten wir keine using Windows.Media.Core Direktive.

C++/WinRT erfordert jedoch, dass Sie eine Headerdatei einschließen, die jedem von Ihnen verbrauchten Namespace entspricht, auch wenn Sie ihn nicht benennen.

#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 müssen wir nicht einschließen, auch wenn das < Ereignis vom Typ >ist, da wir dieses Ereignis nicht verwendet haben.

C++/WinRT erfordert außerdem, dass Sie Headerdateien für Namespaces einschließen, die vom XAML-Markup verwendet werden.

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

Die Verwendung der Rechteck--Klasse bedeutet, dass Sie dieses Include hinzufügen müssen.

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

Wenn Sie eine Headerdatei vergessen, wird alles in Ordnung kompiliert, aber Sie erhalten Linkerfehler, da die consume_ Klassen fehlen.

Parameterübergabe

Beim Schreiben von C++/CX-Quellcode übergeben Sie C++/CX-Typen als Hat-Verweise (^) in Funktionsparameter.

void LogPresenceRecord(PresenceRecord^ record);

In C++/WinRT sollten Sie für synchrone Funktionen standardmäßig const& Parameter verwenden. Dadurch werden Kopien und miteinander verbundenen Overhead vermieden. Ihre Coroutinen sollten jedoch pass-by-Value verwenden, um sicherzustellen, dass sie nach Wert erfasst werden und Lebensdauerprobleme vermeiden (weitere Details finden Sie 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 Wesentlichen ein Wert, der einen Schnittstellenzeiger auf das zugrunde liegende Windows-Runtime-Objekt enthält. Wenn Sie ein C++/WinRT-Objekt kopieren, kopiert der Compiler den gekapselten Schnittstellenzeiger und erhöht die Referenzanzahl. Die endgültige Vernichtung der Kopie umfasst die Dekrementierung der Verweisanzahl. Daher entsteht nur bei Bedarf der Aufwand für eine Kopie.

Variablen- und Feldverweise

Beim Schreiben von C++/CX-Quellcode verwenden Sie Hassvariablen (^), um auf Windows-Runtime-Objekte zu verweisen, und den Pfeiloperator (->), um eine Hassvariable abzuleiten.

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

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

Beim Portieren zum entsprechenden C++/WinRT-Code können Sie viel erreichen, indem Sie die Carets entfernen und den Pfeiloperator (->) in den Punktoperator (.) ändern. Projizierte C++/WinRT-Typen sind Werte und keine Zeiger.

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

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

Der Standardkonstruktor für einen C++/CX-Hut-Pointer initialisiert ihn auf null. Hier ist ein C++/CX-Codebeispiel, in dem wir eine Variable/ein Feld des richtigen Typs erstellen, aber eine, die nicht initialisiert ist. Anders ausgedrückt: Es verweist nicht zunächst auf einen TextBlock-; wir beabsichtigen, später einen Bezug herzustellen.

TextBlock^ textBlock;

class MyClass
{
    TextBlock^ textBlock;
};

Informationen zum Äquivalent in C++/WinRT finden Sie unter Verzögerte Initialisierung.

Eigenschaften

Die C++/CX-Spracherweiterungen enthalten das Konzept von Eigenschaften. Beim Schreiben von C++/CX-Quellcode können Sie auf eine Eigenschaft zugreifen, als wäre es ein Feld. Standard C++ verfügt nicht über das Konzept einer Eigenschaft, sodass Sie in C++/WinRT Abruffunktionen und Setzfunktionen aufrufen.

In den folgenden Beispielen sind XboxUserId, UserState, PresenceDeviceRecordsund Size Eigenschaften.

Abrufen eines Werts aus einer Eigenschaft

Hier erfahren Sie, wie Sie einen Eigenschaftswert in C++/CX abrufen.

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 demselben Namen wie die Eigenschaft auf, jedoch ohne Parameter.

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

Beachten Sie, dass die Funktion PresenceDeviceRecords ein Windows-Runtime-Objekt zurückgibt, das selbst eine Size-Funktion enthält. Da das zurückgegebene Objekt auch ein projizierter C++/WinRT-Typ ist, dereferenzieren wir es mithilfe des Punktoperators, um Sizeaufzurufen.

Festlegen einer Eigenschaft auf einen neuen Wert

Das Festlegen einer Eigenschaft auf einen neuen Wert folgt einem ähnlichen Muster. Zuerst in C++/CX.

record->UserState = newValue;

Zum Ausführen der Entsprechung in C++/WinRT rufen Sie eine Funktion mit demselben Namen wie die Eigenschaft auf und übergeben ein Argument.

record.UserState(newValue);

Erstellen einer Instanz einer Klasse

Sie arbeiten mit einem C++/CX-Objekt über ein Handle daran, das häufig als Hat-Referenz (^) bezeichnet wird. Sie erstellen 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; sodass Sie es auf dem Stapel platzieren oder als Feld eines Objekts verwenden können. Sie sollten niemalsref new (noch new) verwenden, um ein C++/WinRT-Objekt zuzuweisen. Hinter den Kulissen wird RoActivateInstance weiterhin aufgerufen.

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

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

Wenn eine Ressource teuer zu initialisieren ist, ist es üblich, die Initialisierung zu verzögern, bis sie tatsächlich benötigt wird. Wie bereits erwähnt, initialisiert der Standardkonstruktor für eine C++/CX-Referenz ihn 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;
};

Derselbe Code, der zu C++/WinRT portiert wurde. Beachten Sie die Verwendung des std::nullptr_t-Konstruktors. Weitere Informationen zu diesem Konstruktor finden Sie 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 };
};

Wie der Standardkonstruktor Sammlungen beeinflusst

C++-Auflistungstypen verwenden den Standardkonstruktor, der zu unbeabsichtigter Objekterstellung führen kann.

Szenario C++/CX C++/WinRT (falsch) C++/WinRT ist korrekt
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 leerer Bezüge 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 Karte 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 leerer Verweise TextBox^ boxes[2]; // Creates 2 TextBox objects!
TextBox boxes[2];
TextBox boxes[2] = { nullptr, nullptr };
Paar 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 Referenzen

Wenn Sie über eine Platform::Array^- verfügen (siehe Port Platform::Array^) in C++/CX haben Sie die Möglichkeit, dies in einen std::vector in C++/WinRT (in der Tat alle zusammenhängenden Container) zu portieren, anstatt sie als Array zu belassen. Es gibt Vorteile, wenn man sich für std::vectorentscheidet.

Zum Beispiel gibt es eine Kurzform zum Erstellen eines Vektors mit leeren Bezügen fester Größe (siehe Tabelle oben), aber keine solche Kurzform zum Erstellen eines Arrays leerer Bezüge. Sie müssen nullptr für jedes Element in einem Array wiederholen. Wenn Sie zu wenige haben, werden die Extras standardmäßig konstruiert.

Bei einem Vektor können Sie ihn bei der Initialisierung mit leeren Bezügen (wie in der obigen Tabelle) ausfüllen, oder Sie können ihn mit leeren Bezügen nach der Initialisierung mit Code wie diesem ausfüllen.

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

Weitere Informationen zum beispiel std::map

Der [] Subskript-Operator für std::map verhält sich wie folgt.

  • Wenn der Schlüssel in der Karte gefunden wird, geben Sie einen Verweis auf den vorhandenen Wert zurück (den Sie überschreiben können).
  • Wenn der Schlüssel nicht in der Karte gefunden wird, erstellen Sie einen neuen Eintrag in der Karte, der aus dem Schlüssel (verschoben, wenn verschiebbar) besteht, und einen standardwertigen Wert, und geben Sie einen Verweis auf den Wert zurück (den Sie dann überschreiben können).

Mit anderen Worten, der [] Operator erstellt immer einen Eintrag in der Karte. Dies unterscheidet sich von C#, Java und JavaScript.

Konvertieren von einer Basislaufzeitklasse in eine abgeleitete Klasse

Es ist üblich, eine Referenz auf eine Basisklasse zu haben, von der Sie wissen, dass sie sich auf ein Objekt eines abgeleiteten Typs bezieht. In C++/CX verwenden Sie dynamic_cast, um die Referenz-zu-Basis in einen abgeleiteten Verweis zu. Die dynamic_cast ist eigentlich nur ein versteckter Aufruf von "QueryInterface". Hier ist ein typisches Beispiel: Sie verarbeiten ein Ereignis einer geänderten Abhängigkeitseigenschaft und möchten von DependencyObject zurück zum tatsächlichen Typ umwandeln, der die Abhängigkeitseigenschaft besitzt.

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 die dynamic_cast durch einen Aufruf der IUnknown::try_as-Funktion, die QueryInterface-kapselt. Sie haben stattdessen die Möglichkeit, IUnknown::asaufzurufen, was eine Ausnahme auslöst, falls die Abfrage der erforderlichen Schnittstelle (die Standardschnittstelle des angeforderten Typs) nicht zurückgegeben wird. Hier ist ein C++/WinRT-Codebeispiel.

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 von einer Laufzeitklasse abzuleiten, muss die Basisklasse komponierbarsein. C++/CX erfordert nicht, dass Sie spezielle Schritte ausführen, um Ihre Klassen komponierbar zu machen, aber C++/WinRT führt dies aus. Sie verwenden das nicht versiegelten Schlüsselworts, um anzugeben, dass Ihre Klasse als Basisklasse verwendet werden soll.

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

In Ihrer Implementierungsheader-Datei müssen Sie die Headerdatei der Basisklasse einschließen, bevor Sie den automatisch generierten Header für die abgeleitete Klasse einschließen. Andernfalls erhalten Sie Fehler wie "Unzulässige 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

Im Folgenden finden Sie ein typisches Beispiel für die Behandlung eines Ereignisses in C++/CX, wobei in diesem Fall eine Lambda-Funktion als Stellvertretung verwendet wird.

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 das Äquivalent 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.
});

Statt einer Lambda-Funktion können Sie ihren Delegaten als freie Funktion oder als Zeiger-zu-Member-Funktion implementieren. Weitere Informationen finden Sie unter Behandeln von Ereignissen mithilfe von Delegaten in C++/WinRT.

Wenn Sie von einer C++/CX-Codebasis portieren, in der Ereignisse und Delegaten intern (nicht über Binärdateien) verwendet werden, hilft Ihnen winrt::delegate, dieses Muster in C++/WinRT zu replizieren. Siehe auch Parametrisierte Stellvertretungen, einfache Signale und Rückrufe innerhalb eines Projekts.

Widerrufen eines Bevollmächtigten

In C++/CX verwenden Sie den -=-Operator, um eine vorherige Ereignisregistrierung zu widerrufen.

myButton->Click -= token;

Dies ist das Äquivalent in C++/WinRT.

myButton().Click(token);

Weitere Informationen und Optionen finden Sie unter Widerruf eines registrierten Delegierten.

Boxen und Entboxen

C++/CX ordnet automatisch Skalare in Objekte ein. C++/WinRT erfordert, dass Sie die winrt::box_value Funktion explizit aufrufen. Beide Sprachen erfordern, dass Sie explizit entpacken. Weitere Informationen finden Sie unter: Boxen und Entpacken mit C++/WinRT.

In den folgenden Tabellen verwenden wir diese Definitionen.

C++/CX C++/WinRT
int i; int i;
String^ s; winrt::hstring s;
Object^ o; IInspectable o;
Vorgang C++/CX C++/WinRT
Boxen o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
Auspacken 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 Sie versuchen, einen Nullzeiger auf einen Werttyp zu entpacken. C++/WinRT betrachtet dies als Programmierfehler und stürzt ab. Verwenden Sie in C++/WinRT die winrt::unbox_value_or-Funktion, wenn Sie den Fall behandeln möchten, in dem das Objekt nicht dem Typ entspricht, den Sie erwartet haben.

Szenario C++/CX C++/WinRT
Einen bekannten ganzzahligen Wert auspacken i = (int)o; i = unbox_value<int>(o);
Wenn o null ist Platform::NullReferenceException Absturz
Wenn o kein verpacktes Integer ist Platform::InvalidCastException Absturz
Int auspacken, Fallback verwenden, wenn Null; bei allen anderen Fällen abstürzen. i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Wenn möglich, int entsperren; Fallback für alles andere verwenden auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

Umwandeln und Entpacken einer Zeichenfolge

Eine Zeichenfolge ist auf verschiedene Weise ein Werttyp und auf andere Weise ein Verweistyp. C++/CX und C++/WinRT behandeln Zeichenfolgen unterschiedlich.

Der ABI-Typ HSTRING ist ein Zeiger auf eine referenzgezählte Zeichenfolge. Aber es ist nicht von IInspectableabgeleitet, daher ist es technisch kein Objekt. Darüber hinaus repräsentiert ein null HSTRING die leere Zeichenfolge. Das Boxen von Elementen, die nicht von IInspectable abgeleitet werden, erfolgt, indem sie in eine IReference<T>umschlossen werden, und die Windows-Runtime stellt eine Standardimplementierung in Form eines PropertyValue--Objekts bereit (benutzerdefinierte Typen werden als PropertyType::OtherTypegemeldet).

C++/CX stellt eine Windows-Runtime-Zeichenfolge als Referenztyp dar; während C++/WinRT eine Zeichenfolge als Werttyp projiziert. Dies bedeutet, dass eine eingekapselte null-Zeichenfolge, abhängig davon, wie Sie dorthin gelangt sind, unterschiedliche Darstellungen aufweisen kann.

Darüber hinaus können Sie mit C++/CX eine null-Zeichenfolge String^dereferenzieren, dabei verhält es sich wie die Zeichenfolge "".

Verhalten C++/CX C++/WinRT
Deklarationen Object^ o;
String^ s;
IInspectable o;
hstring s;
Zeichenfolgen-Typkategorie Bezugstyp Werttyp
null HSTRING- Projekte 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

Einfaches Boxing und Unboxing.

Vorgang C++/CX C++/WinRT
Verpacke eine Zeichenfolge o = s;
Leerer String wird zu nullptr.
o = box_value(s);
Leere Zeichenfolge wird zu einem Nicht-Null-Objekt.
Ein bekannte Zeichenfolge auspacken s = (String^)o;
Das nulles Objekt wird zu einem leeren String.
InvalidCastException, wenn kein String vorliegt.
s = unbox_value<hstring>(o);
Null-Objekt stürzt ab.
Absturz, wenn kein String vorhanden ist.
Eine mögliche Zeichenfolge entpacken s = dynamic_cast<String^>(o);
Null-Objekt oder eine nicht-Zeichenfolge wird zu einem leeren String.
s = unbox_value_or<hstring>(o, fallback);
Null oder eine Nicht-Zeichenfolge wird zum Standardwert.
Leerer Text wird beibehalten.

Parallelität und asynchrone Vorgänge

Die Parallel Patterns Library (PPL) (z. B.concurrency::task) wurde aktualisiert, um C++/CX-hat-Referenzen zu unterstützen.

Für C++/WinRT sollten Sie stattdessen Coroutinen und co_await verwenden. Weitere Informationen und Codebeispiele finden Sie unter Parallelität und asynchrone Vorgänge mit C++/WinRT.

Konsumieren von Objekten aus XAML-Markup

In einem C++/CX-Projekt können Sie private Member und benannte Elemente aus XAML-Markup nutzen. In C++/WinRT müssen jedoch alle Entitäten, die durch die XAML-{x:Bind}-Markuperweiterung konsumiert werden, öffentlich in IDL verfügbar gemacht werden.

Außerdem zeigt die Bindung an einen booleschen Wert in C++/CX true oder false an, in C++/WinRT hingegen zeigt sie Windows.Foundation.IReference`1<Boolean> an.

Weitere Informationen und Codebeispiele finden Sie unter Verwenden von Objekten aus Markup-.

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

C++/CX stellt mehrere Datentypen im Platform Namespace bereit. Diese Typen sind kein C++-Standard, sodass Sie sie nur verwenden können, wenn Sie Windows-Runtime-Spracherweiterungen aktivieren (Visual Studio-Projekteigenschaft C/C++>Allgemein>Windows-Runtime-Erweiterung verwenden>Ja (/ZW)). Die folgende Tabelle hilft Ihnen beim Portieren von Platform Typen zu ihren Entsprechungen in C++/WinRT. Nachdem Sie dies getan haben, können Sie die option /ZW deaktivieren, da C++/WinRT standard C++ ist.

C++/CX C++/WinRT
Platform::Agile^ winrt::agile_ref
Platform::Array^ Siehe Port Platform::Array^
Platform::Exception^ winrt::hresult_error
Platform::InvalidArgumentException^ winrt::hresult_invalid_argument
Platform::Object^ winrt::Windows::Foundation::IInspectable
Plattform::Zeichenfolge^ winrt::hstring

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

Der Platform::Agile^ Typ in C++/CX stellt eine Windows-Runtime-Klasse dar, auf die über einen beliebigen Thread zugegriffen werden kann. Das C++/WinRT-Äquivalent ist 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;

Portplattform::Array^

In Fällen, in denen C++/CX erfordert, dass Sie ein Array verwenden, können Sie mit C++/WinRT einen beliebigen zusammenhängenden Container verwenden. Lesen Sie , wie der Standardkonstruktor Kollektionen beeinflusst, um zu verstehen, warum std::vector eine gute Wahl ist.

Wenn Sie also über eine Platform::Array^ in C++/CX verfügen, umfassen Ihre Portierungsoptionen eine Initialisierungsliste, ein std::arrayoder ein std::vector. Weitere Informationen und Codebeispiele finden Sie unter Standardinitializerlisten und Standardarrays und Vektoren.

Übertragen Platform::Exception^ zu winrt::hresult_error

Der Platform::Exception^ Typ wird in C++/CX erstellt, wenn eine Windows-Runtime-API einen HRESULT ungleich S_OK zurückgibt. Das C++/WinRT-Äquivalent ist winrt::hresult_error.

Um zu C++/WinRT zu portieren, ändern Sie den gesamten Code, der Platform::Exception^ verwendet, um winrt::hresult_errorzu verwenden.

In C++/CX.

catch (Platform::Exception^ ex)

In C++/WinRT.

catch (winrt::hresult_error const& ex)

C++/WinRT stellt diese Ausnahmeklassen bereit.

Ausnahmetyp Basisklasse HRESULT
winrt::hresult_error hresult_error::to_abi aufrufen
winrt::hresult_access_denied winrt::hresult_error E_ACCESSDENIED (Zugriff verweigert)
winrt::hresult_canceled winrt::hresult_error FEHLER_ABGEBROCHEN
winrt::hresult_changed_state winrt::hresult_error STATUS_GEÄNDERT
winrt::hresult_class_not_available winrt::hresult_error KLASSE_E_KLASSE_NICHT_VERFÜGBAR
winrt::hresult_illegal_delegate_assignment winrt::hresult_error E_ILLEGAL_DELEGATE_ASSIGNMENT
winrt::hresult_illegal_method_call winrt::hresult_error ILLEGAL_METHODE_AUFRUF
winrt::hresult_illegal_state_change winrt::hresult_error E_UNGÜLTIGER_ZUSTANDSWECHSEL
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

Beachten Sie, dass jede Klasse (über die hresult_error Basisklasse) eine to_abi Funktion bereitstellt, die das HRESULT des Fehlers zurückgibt, und eine Nachricht Funktion, die die Zeichenfolgendarstellung dieses HRESULT zurückgibt.

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

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

Und das Äquivalent in C++/WinRT.

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

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

Wie alle C++/WinRT-Typen ist winrt::Windows::Foundation::IInspectable ein Werttyp. Hier erfahren Sie, wie Sie eine Variable dieses Typs auf NULL initialisieren.

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

Konvertieren Platform::String^ in winrt::hstring

Platform::String^ entspricht dem Windows-Runtime-HSTRING-ABI-Typ. Für C++/WinRT ist die Entsprechung winrt::hstring. Mit C++/WinRT können Sie jedoch Windows-Runtime-APIs mit breiten Zeichenfolgentypen der C++-Standardbibliothek wie std::wstringund/oder breiten Zeichenfolgenliteralen aufrufen. Weitere Details und Codebeispiele finden Sie unter Zeichenfolgenbehandlung in C++/WinRT.

Mit C++/CX können Sie auf die Platform::String::Data-Eigenschaft zugreifen, um die Zeichenfolge als C-Stil const wchar_t* Array abzurufen (z. B. um sie an std::wcoutzu übergeben).

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

Um dasselbe mit C++/WinRT zu tun, können Sie die hstring::c_str-Funktion verwenden, um eine nullterminierte C-Standard-Zeichenfolgenversion zu erhalten, genauso wie bei std::wstring.

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

Bei der Implementierung von APIs, die Zeichenfolgen annehmen oder zurückgeben, ändern Sie in der Regel jeden C++/CX-Code, der Platform::String^ verwendet, um stattdessen winrt::hstring zu verwenden.

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

void LogWrapLine(Platform::String^ str);

Bei C++/WinRT können Sie diese API in MIDL 3.0- wie folgt deklarieren.

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

Die C++/WinRT-Toolkette generiert dann Quellcode für Sie, 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".

C++/WinRT stellt diese Funktion nicht direkt bereit, aber Sie können auf Alternativen zurückgreifen.

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. Sie müssen Überladungen für alle zusätzlichen Typen hinzufügen, die Sie in eine Zeichenfolge konvertieren möchten.

Sprache int zu String konvertieren Stringdarstellung der Aufzählung
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 Umwandlung in eine Zeichenfolge eines Enums müssen Sie die Implementierung von winrt::to_hstringbereitstellen.

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 Zeichenfolgen werden häufig implizit von der Datenbindung verarbeitet.

<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 aus. Im zweiten Beispiel (der StatusEnum) müssen Sie eine eigene Überladung von winrt::to_hstringbereitstellen, sonst erhalten Sie einen Compilerfehler.

String-Erstellung

C++/CX und C++/WinRT verwenden den Standard std::wstringstream zum Erstellen von Zeichenfolgen.

Vorgang C++/CX C++/WinRT
Anfügen von Zeichenfolgen, Beibehalten von NULL-Werten stream.print(s->Data(), s->Length); stream << std::wstring_view{ s };
Zeichenfolge anhängen, bei erstem Nullwert stoppen stream << s->Data(); stream << s.c_str();
Ergebnis extrahieren ws = stream.str(); ws = stream.str();

Weitere Beispiele

In den folgenden Beispielen ist ws eine Variable vom Typ std::wstring. Während C++/CX eine Platform::String aus einer 8-Bit-Zeichenfolge erstellen kann, tut C++/WinRT dies nicht.

Vorgang C++/CX C++/WinRT
Zeichenfolge aus Literal erstellen String^ s = "hello";
String^ s = L"hello";
// winrt::hstring s{ "hello" }; // Doesn't compile
winrt::hstring s{ L"hello" };
Konvertiere von std::wstring, unter Beibehaltung von Nullen 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
Umwandeln von std::wstring, Stopp beim ersten Nullzeichen 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
In std::wstringkonvertieren, wobei Nullwerte erhalten bleiben std::wstring ws{ s->Data(), s->Length };
ws = std::wstring(s>Data(), s->Length);
std::wstring ws{ s };
ws = s;
In std::wstringkonvertieren, beim ersten Nullzeichen stoppen. std::wstring ws{ s->Data() };
ws = s->Data();
std::wstring ws{ s.c_str() };
ws = s.c_str();
Ein Literal an eine Methode übergeben Method("hello");
Method(L"hello");
// Method("hello"); // Doesn't compile
Method(L"hello");
Übergeben Sie std::wstring an die Methode 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