Umstellen von C# auf C++/WinRT

Tipp

Wenn Sie dieses Thema schon einmal gelesen haben und wegen einer bestimmten Aufgabe zu ihm zurückkehren, können Sie zum Abschnitt Inhalt zu einer Aufgabe finden, die Sie ausführen möchten dieses Themas springen.

Dieses Thema enthält einen umfassenden Katalog der technischen Details der Portierung des Quellcodes in einem C#-Projekt zum entsprechenden Äquivalent in C++/WinRT-.

Eine Fallstudie zum Portieren eines der App-Beispiele für die universelle Windows-Plattform (UWP) finden Sie im Begleitthema Portieren des Beispiels „Zwischenablage“ (Clipboard) von C# zu C++/WinRT. Sie können Praxis und Erfahrung mit dem Portieren sammeln, indem Sie der exemplarischen Vorgehensweise folgen und in deren Verlauf das Beispiel für sich selbst portieren.

Wie Sie sich vorbereiten und was Sie erwarten dürfen

Die Fallstudie Portieren des Beispiels „Zwischenablage“ (Clipboard) von C# zu C++/WinRT veranschaulicht Beispiele der Arten von Softwareentwurfsentscheidungen, die Sie beim Portieren eines Projekts zu C++/WinRT treffen. Es ist also eine gute Idee, sich auf die Portierung vorzubereiten, indem man sich ein solides Verständnis davon verschafft, wie der vorhandene Code funktioniert. Auf diese Weise erhalten Sie einen guten Überblick über die Funktionen der App und die Struktur des Codes, und die Entscheidungen, die Sie treffen, werden Sie stets vorwärts und in die richtige Richtung bringen.

Die Arten der zu erwartenden Portierungsänderungen lassen sich in vier Kategorien gruppieren.

  • Portieren der Sprachprojektion. Die Windows-Runtime (WinRT) wird in verschiedene Programmiersprachen projiziert. Jede dieser Sprachprojektionen ist so konzipiert, dass sie idiomatisch an die betreffende Programmiersprache angepasst ist. Für C# werden einige Windows-Runtime-Typen als .NET-Typen projiziert. So wird System.Collections.Generic.IReadOnlyList<T> beispielsweise zurück zu Windows.Foundation.Collections.IVectorView<T> übersetzt. Ebenfalls werden in C# einige Windows-Runtime-Operationen als praktische C#-Sprachfunktionen projiziert. In C# verwenden Sie beispielsweise die +=-Operatorsyntax zum Registrieren eines Ereignisbehandlungsdelegaten. Sie übersetzen also Sprachfeatures wie diese zurück in die grundlegende Operation, die gerade ausgeführt wird (in diesem Beispiel die Ereignisregistrierung).
  • Portieren der Sprachsyntax. Viele dieser Änderungen sind einfache mechanische Umwandlungen, bei denen ein Symbol durch ein anderes ersetzt wird. Zum Beispiel wird ein Punkt (.) in einen Doppelpunkt (::) geändert.
  • Portieren von Sprachprozeduren. Einige davon können einfache, sich wiederholende Änderungen sein (wie z. B. myObject.MyProperty in myObject.MyProperty()). Andere benötigen tiefer gehende Änderungen (z. B. die Portierung einer Prozedur, die die Verwendung von System.Text.StringBuilder beinhaltet, in eine Prozedur, die die Verwendung von std::wostringstream beinhaltet).
  • Portieren zugehöriger Tasks, die spezifisch für C++/WinRT sind. Bestimmte Details der Windows-Runtime werden von C# implizit im Hintergrund erledigt. Diese Details werden explizit in C++/WinRT durchgeführt. Ein Beispiel ist die Verwendung einer .idl-Datei, um Ihre Laufzeitklassen zu definieren.

Nach dem folgenden aufgabenbasierten Index sind die restlichen Abschnitte in diesem Thema entsprechend der obigen Taxonomie aufgebaut.

Finden Sie Inhalte basierend auf der Aufgabe, die Sie gerade ausführen

Aufgabe Content
Erstellen einer Komponente für Windows-Runtime (WCR) Bestimmte Funktionalität kann nur mit C++ (oder durch Aufrufen bestimmter APIs) erreicht werden. Sie können diese Funktionalität in eine C++/WinRT-WRC einbauen und dann die WRC von (z. B.) einer C#-App verwenden. Weitere Informationen finden Sie unter Windows-Runtime-Komponenten mit C++/WinRT und Wenn Sie eine Laufzeitklasse in einer Komponente für Windows-Runtime erstellen.
Portieren einer asynchronen Methode Es ist sinnvoll, dass die erste Zeile einer asynchronen Methode in einer C++/WinRT-Laufzeitklasse auto lifetime = get_strong(); lautet (siehe Sicherer Zugriff auf den this-Zeiger in einer Klassenmember-Coroutine).

Weitere Informationen zum Portieren aus Task finden Sie unter Asynchrone Aktion.
Weitere Informationen zum Portieren aus Task<T> finden Sie unter Asynchroner Vorgang.
Weitere Informationen zum Portieren aus async void finden Sie unter Fire and Forget-Methode.
Portieren einer Klasse Bestimmen Sie zunächst, ob die Klasse eine Laufzeitklasse sein muss oder ob sie eine normale Klasse sein kann. Informationen, die Sie bei der Entscheidungsfindung unterstützen, finden Sie am Anfang von Erstellen von APIs mit C++/WinRT. Schauen Sie sich anschließend die nächsten drei Zeilen unten an.
Portieren einer Laufzeitklasse Eine Klasse, die Funktionalität außerhalb der C++-App teilt, oder eine Klasse, die in der XAML-Datenbindung verwendet wird. Weitere Informationen finden Sie unter Wenn Sie eine Laufzeitklasse in einer Komponente für Windows-Runtime erstellen oder Wenn Sie eine Laufzeitklasse erstellen, die in Ihrer XAML-Benutzeroberfläche referenziert werden soll.

Die Links beschreiben dies genauer, aber eine Laufzeitklasse muss in IDL deklariert werden. Wenn Ihr Projekt bereits eine IDL-Datei enthält (z. B. Project.idl), dann empfehlen wir, dass Sie jede neue Laufzeitklasse in dieser Datei deklarieren. Deklarieren Sie in der IDL alle Methoden und Datenmember, die außerhalb Ihrer App oder in XAML verwendet werden. Nachdem Sie die IDL-Datei aktualisiert haben, erstellen Sie sie neu, und sehen Sie sich die generierten Stubdateien (.h und .cpp) im Ordner Generated Files Ihres Projekts an (stellen Sie sicher, dass im Projektmappen-Explorer der Projektknoten ausgewählt und Alle Dateien anzeigen aktiviert ist). Vergleichen Sie die Stubdateien mit den Dateien, die sich bereits in Ihrem Projekt befinden, und fügen Sie bei Bedarf Dateien hinzu oder ergänzen/aktualisieren Sie Funktionssignaturen. Die Syntax der Stubdatei ist immer korrekt, daher empfehlen wir Ihnen, diese zu verwenden, um Buildfehler zu minimieren. Sobald die Stubs in Ihrem Projekt mit denen in den Stubdateien übereinstimmen, können Sie fortfahren und sie implementieren, indem Sie den C#-Code hinüber portieren.
Portieren einer gewöhnlichen Klasse Siehe Wenn Sie keine Laufzeitklasse erstellen.
Erstellen von IDL Einführung in Microsoft Interface Definition Language 3.0
Wenn Sie eine Laufzeitklasse erstellen, die in Ihrer XAML-Benutzeroberfläche referenziert werden soll, gehen Sie wie folgt vor
Verwenden von Objekten aus XAML-Markup
Definieren Ihrer Laufzeitklassen in IDL
Portieren einer Sammlung Sammlungen mit C++/WinRT
Verfügbarmachen einer Datenquelle für XAML-Markup
Assoziativer Container
Vektormemberzugriff
Portieren eines Ereignisses Ereignishandlerdelegat als Klassenmember
Ereignishandlerdelegat widerrufen
Hinzufügen einer Methode Von C#: private async void SampleButton_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { ... }
In die C++/WinRT .h-Datei: fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&);
In die C++/WinRT .cpp-Datei: fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...}
Portieren von Zeichenfolgen Verarbeitung von Zeichenfolgen in C++/WinRT
ToString
Erstellung von string-Elementen
Boxing und Unboxing von string-Elementen
Typkonvertierung (Typumwandlung) C#: o.ToString()
C++/WinRT: to_hstring(static_cast<int>(o))
Weitere Informationen finden Sie unter ToString.

C#: (Value)o
C++/WinRT: unbox_value<Value>(o)
Wird ausgelöst, wenn das Unboxing fehlschlägt. Weitere Informationen finden Sie unter Boxing und Unboxing.

C#: o as Value? ?? fallback
C++/WinRT: unbox_value_or<Value>(o, fallback)
Gibt ein Fallback zurück, wenn das Unboxing fehlschlägt. Weitere Informationen finden Sie unter Boxing und Unboxing.

C#: (Class)o
C++/WinRT: o.as<Class>()
Wird ausgelöst, wenn die Konvertierung fehlschlägt.

C#: o as Class
C++/WinRT: o.try_as<Class>()
Gibt NULL zurück, wenn die Konvertierung fehlschlägt.

Änderungen, die die Sprachprojektion betreffen

Category C# C++/WinRT Siehe auch
Nicht typisiertes Objekt object oder System.Object Windows::Foundation::IInspectable Portieren der EnableClipboardContentChangedNotifications-Methode
Projektionsnamespaces using System; using namespace Windows::Foundation;
using System.Collections.Generic; using namespace Windows::Foundation::Collections;
Größe einer Sammlung collection.Count collection.Size() Portieren der BuildClipboardFormatsOutputString-Methode
Typischer Sammlungstyp IList<T> und Hinzufügen, um ein Element hinzuzufügen. IVector<T> und Anfügen, um ein Element hinzuzufügen. Wenn Sie an einer beliebigen Stelle einen std::vector verwenden, führen Sie ein push_back aus, um ein Element hinzuzufügen.
Schreibgeschützter Sammlungstyp IReadOnlyList<T> IVectorView<T> Portieren der BuildClipboardFormatsOutputString-Methode
Ereignishandlerdelegat als Klassenmember myObject.EventName += Handler; token = myObject.EventName({ get_weak(), &Class::Handler }); Portieren der EnableClipboardContentChangedNotifications-Methode
Ereignishandlerdelegat widerrufen myObject.EventName -= Handler; myObject.EventName(token); Portieren der EnableClipboardContentChangedNotifications-Methode
Assoziativer Container IDictionary<K, V> IMap<K, V>
Vektormemberzugriff x = v[i];
v[i] = x;
x = v.GetAt(i);
v.SetAt(i, x);

Registrieren/Widerrufen eines Ereignishandlers

In C++/WinRT haben Sie mehrere syntaktische Optionen, um einen Ereignishandlerdelegaten zu registrieren/widerrufen, wie in Behandeln von Ereignissen mithilfe von Delegaten in C++/WinRT beschrieben. Siehe auch Portieren der EnableClipboardContentChangedNotifications-Methode.

Manchmal, z. B. wenn ein Ereignisempfänger (ein Objekt, das ein Ereignis behandelt) kurz vor der Zerstörung steht, möchten Sie einen Ereignishandler vielleicht widerrufen, damit die Ereignisquelle (das Objekt, das das Ereignis auslöst) kein zerstörtes Objekt aufruft. Mehr dazu erfahren Sie unter Einen registrierten Delegaten widerrufen. Erstellen Sie in solchen Fällen eine event_token-Membervariable für Ihre Ereignishandler. Siehe z. B. Portieren der EnableClipboardContentChangedNotifications-Methode.

Ein Ereignishandler kann auch in XAML-Markup registriert werden.

<Button x:Name="OpenButton" Click="OpenButton_Click" />

In C# kann deine OpenButton_Click-Methode privat sein, und XAML kann sie trotzdem mit dem ButtonBase.Click-Ereignis verbinden, das von OpenButton ausgelöst wird.

In C++/WinRT muss deine OpenButton_Click-Methode in deinem Implementierungstyp öffentlich sein, wenn du sie in XAML-Markup registrieren möchtest. Wenn du einen Ereignisbehandler nur in imperativem Code registrierst, dann muss der Ereignishandler nicht öffentlich sein.

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
        void OpenButton_Click(
            winrt::Windows:Foundation::IInspectable const& sender,
            winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
    }
};

Alternativ dazu kannst du die registrierende XAML-Seite als Friend deines Implementierungstyps und OpenButton_Click als privat festlegen.

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
    private:
        friend MyPageT;
        void OpenButton_Click(
            winrt::Windows:Foundation::IInspectable const& sender,
            winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
    }
};

Ein letztes Szenario ist eines, in dem das C#-Projekt, das Sie portieren, vom Markup aus an den Ereignishandler gebunden wird (mehr Hintergrund zu diesem Szenario finden Sie unter Funktionen in x:Bind).

<Button x:Name="OpenButton" Click="{x:Bind OpenButton_Click}" />

Sie können dieses Markup einfach in das einfachere Click="OpenButton_Click" ändern. Oder wenn Sie es bevorzugen, können Sie dieses Markup unverändert verwenden. Um dieses Verfahren zu unterstützen, brauchen Sie lediglich den Ereignisbehandler in IDL zu deklarieren.

void OpenButton_Click(Object sender, Windows.UI.Xaml.RoutedEventArgs e);

Hinweis

Deklarieren Sie die Funktion als void, auch wenn Sie sie als Fire and Forget (Auslösen und Vergessen) implementieren.

Änderungen, die die Sprachsyntax betreffen

Category C# C++/WinRT Siehe auch
Zugriffsmodifizierer public \<member\> public:
    \<member\>
Portieren der Button_Click-Methode
Auf ein Datenmember zugreifen this.variable this->variable  
Asynchrone Aktion async Task ... IAsyncAction ... IAsyncAction-Schnittstelle, Parallelität und asynchrone Vorgänge mit C++/WinRT
Asynchroner Vorgang async Task<T> ... IAsyncOperation<T> ... IAsyncOperation-Schnittstelle, Parallelität und asynchrone Vorgänge mit C++/WinRT
Fire-and-Forget-Methode (impliziert „Asynchron“) async void ... winrt::fire_and_forget ... Portieren der CopyButton_Click-Methode, Fire and Forget (Auslösen und Vergessen)
Zugriff auf eine Enumerationskonstante E.Value E::Value Portieren der DisplayChangedFormats-Methode
Kooperatives Wait await ... co_await ... Portieren der CopyButton_Click-Methode
Sammlung von projizierten Typen als privates Feld private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); std::vector
<MyNamespace::MyRuntimeClass>
m_myRuntimeClasses;
GUID-Konstruktion private static readonly Guid myGuid = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1"); winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} };
Namespacetrennzeichen A.B.T A::B::T
Null null nullptr Portieren der UpdateStatus-Methode
Abrufen eines Typobjekts typeof(MyType) winrt::xaml_typename<MyType>() Portieren der Scenarios-Eigenschaft
Parameterdeklaration für eine Methode MyType MyType const& Parameterübergabe
Parameterdeklaration für eine asynchrone Methode MyType MyType Parameterübergabe
Statische Methode aufrufen T.Method() T::Method()
Zeichenfolgen string oder System.String winrt::hstring Verarbeitung von Zeichenfolgen in C++/WinRT
Zeichenfolgenliteral "a string literal" L"a string literal" Portieren des Konstruktors, Current und FEATURE_NAME
Hergeleiteter (oder abgeleiteter) Typ var auto Portieren der BuildClipboardFormatsOutputString-Methode
Using-directive using A.B.C; using namespace A::B::C; Portieren des Konstruktors, Current und FEATURE_NAME
Ausführliche Zeichenfolgeliterale/Rohzeichenfolgenliterale @"verbatim string literal" LR"(raw string literal)" Portieren der DisplayToast-Methode

Hinweis

Wenn eine Header Datei keine using namespace-Direktive für einen bestimmten Namespace enthält, müssen Sie alle Typnamen für diesen Namespace vollständig qualifizieren oder zumindest ausreichend qualifizieren, damit der Compiler Sie finden kann. Ein Beispiel finden Sie unter Portieren der DisplayToast-Methode.

Portieren von Klassen und Membern

Sie müssen für jeden C#-Typ entscheiden, ob er in einen Windows-Runtime-Typ oder eine reguläre C++-Klasse/-Struktur/-Enumeration portiert werden soll. Weitere Informationen und ausführliche Beispiele, die veranschaulichen, wie Sie diese Entscheidungen treffen, finden Sie unter Portieren des Beispiels „Zwischenablage“ (Clipboard) von C# zu C++/WinRT.

Eine C#-Eigenschaft wird in der Regel zu einer Accessorfunktion, einer Mutatorfunktion und einem Unterstützungsdatenmember. Weitere Informationen und ein Beispiel finden Sie unter Portieren der IsClipboardContentChangedEnabled- -Eigenschaft.

Legen Sie nicht statische Felder als Datenmember Ihres Implementierungstyps fest.

Ein statisches C#-Feld wird zu einer statischen C++/WinRT-Accessor- und/oder Mutatorfunktion. Weitere Informationen und ein Beispiel finden Sie unter Portieren des Konstruktors, Current und FEATURE_NAME.

Auch für jede einzelne Memberfunktion müssen Sie sich entscheiden, ob sie in die IDL gehört, oder ob es sich um eine öffentliche oder private Memberfunktion Ihres Implementierungstyps handelt. Weitere Informationen und Beispiele zur Entscheidungsfindung finden Sie unter IDL für den MainPage-Typ.

Portieren von XAML-Markup- und Ressourcendateien

Im Falle von Portieren des Beispiels „Zwischenablage“ (Clipboard) von C# zu C++/WinRT können wir über das C#- und das C++/WinRT-Projekt hinweg das gleiche XAML-Markup (einschließlich der Ressourcen) und die gleichen Ressourcendateien verwenden. In einigen Fällen sind Änderungen an Markup erforderlich, um dies zu erreichen. Siehe Kopieren von XAML und Stilen, die notwendig sind, um das Portieren von MainPage abzuschließen.

Änderungen, die Prozeduren in der Sprache betreffen

Category C# C++/WinRT Siehe auch
Verwaltung der Lebensdauer in einer asynchronen Methode NICHT ZUTREFFEND auto lifetime{ get_strong() }; oder
auto lifetime = get_strong();
Portieren der CopyButton_Click-Methode
Verwerfen using (var t = v) auto t{ v };
t.Close(); // or let wrapper destructor do the work
Portieren der CopyImage-Methode
Objekt erstellen new MyType(args) MyType{ args } oder
MyType(args)
Portieren der Scenarios-Eigenschaft
Nicht initialisierten Verweis erstellen MyType myObject; MyType myObject{ nullptr }; oder
MyType myObject = nullptr;
Portieren des Konstruktors, Current und FEATURE_NAME
Objekt in Variable mit Argumenten erstellen var myObject = new MyType(args); auto myObject{ MyType{ args } }; oder
auto myObject{ MyType(args) }; oder
auto myObject = MyType{ args }; oder
auto myObject = MyType(args); oder
MyType myObject{ args }; oder
MyType myObject(args);
Portieren der Footer_Click-Methode
Objekt in Variable ohne Argumente erstellen var myObject = new T(); MyType myObject; Portieren der BuildClipboardFormatsOutputString-Methode
Objektinitialisierung (kompakt) var p = new FileOpenPicker{
    ViewMode = PickerViewMode.List
};
FileOpenPicker p;
p.ViewMode(PickerViewMode::List);
Massenvektorvorgang var p = new FileOpenPicker{
    FileTypeFilter = { ".png", ".jpg", ".gif" }
};
FileOpenPicker p;
p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" });
Portieren der CopyButton_Click-Methode
Sammlung durchlaufen foreach (var v in c) for (auto&& v : c) Portieren der BuildClipboardFormatsOutputString-Methode
Ausnahme abfangen catch (Exception ex) catch (winrt::hresult_error const& ex) Portieren der PasteButton_Click-Methode
Ausnahmedetails ex.Message ex.message() Portieren der PasteButton_Click-Methode
Eigenschaftswert abrufen myObject.MyProperty myObject.MyProperty() Portieren der notifyuser-Methode
Eigenschaftswert festlegen myObject.MyProperty = value; myObject.MyProperty(value);
Eigenschaftswert schrittweise erhöhen myObject.MyProperty += v; myObject.MyProperty(thing.Property() + v);
Für Zeichenfolgen zu einem Generator wechseln
ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()
Sprachzeichenfolge in Windows-Runtime-Zeichenfolge NICHT ZUTREFFEND winrt::hstring{ s }
Erstellung von string-Elementen StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Erstellung von string-Elementen
Zeichenfolgeninterpolierung $"{i++}) {s.Title}" winrt::to_hstring und/oder winrt::hstring::operator+ Portieren der OnNavigatedTo-Methode
Leere Zeichenfolge für den Vergleich System.String.Empty winrt::hstring::empty Portieren der UpdateStatus-Methode
Erstellen einer leeren Zeichenfolge var myEmptyString = String.Empty; winrt::hstring myEmptyString{ L"" };
Wörterbuchvorgänge map[k] = v; // replaces any existing
v = map[k]; // throws if not present
map.ContainsKey(k)
map.Insert(k, v); // replaces any existing
v = map.Lookup(k); // throws if not present
map.HasKey(k)
Typkonvertierung (bei Fehler auslösen) (MyType)v v.as<MyType>() Portieren der Footer_Click-Methode
Typkonvertierung (bei Fehler auf „Null“ festlegen) v as MyType v.try_as<MyType>() Portieren der PasteButton_Click-Methode
XAML-Elemente mit „x:Name“ sind Eigenschaften. MyNamedElement MyNamedElement() Portieren des Konstruktors, Current und FEATURE_NAME
Zum UI-Thread wechseln CoreDispatcher.RunAsync CoreDispatcher.RunAsync oder winrt::resume_foreground Portieren der NotifyUser-Methode und Portieren der HistoryAndRoaming-Methode
Erstellung von Benutzeroberflächenelementen in imperativem Code auf einer XAML-Seite Siehe Erstellen von Benutzeroberflächenelementen Siehe Erstellen von Benutzeroberflächenelementen

In den folgenden Abschnitten finden Sie ausführliche Informationen zu einigen Elementen in der Tabelle.

Erstellen von Benutzeroberflächenelementen

Diese Codebeispiele veranschaulichen die Erstellung eines Benutzeroberflächenelements im imperativen Code einer XAML-Seite.

var myTextBlock = new TextBlock()
{
    Text = "Text",
    Style = (Windows.UI.Xaml.Style)this.Resources["MyTextBlockStyle"]
};
TextBlock myTextBlock;
myTextBlock.Text(L"Text");
myTextBlock.Style(
    winrt::unbox_value<Windows::UI::Xaml::Style>(
        Resources().Lookup(
            winrt::box_value(L"MyTextBlockStyle")
        )
    )
);

ToString()

C#-Typen stellen die Object.ToString-Methode bereit.

int i = 2;
var s = i.ToString(); // s is a System.String with value "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# string result = "hello, " + intValue.ToString();
string result = $"hello, {intValue}";
string result = "status: " + status.ToString();
string result = $"status: {status}";
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.

Siehe auch Portieren der Footer_Click-Methode.

Erstellung von string-Elementen

Für die Erstellung von string-Elementen verfügt C# über einen integrierten StringBuilder-Typ.

Category C# C++/WinRT
Erstellung von string-Elementen StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Anfügen einer Windows-Runtime-Zeichenfolge unter Beibehalten von NULL-Werten builder.Append(s); builder << std::wstring_view{ s };
Neue Zeile hinzufügen builder.Append(Environment.NewLine); builder << std::endl;
Auf das Ergebnis zugreifen s = builder.ToString(); ws = builder.str();

Weitere Informationen finden Sie auch unter Portieren der BuildClipboardFormatsOutputString-Methode und Portieren der DisplayChangedFormats-Methode.

Ausführen von Code im Haupt-UI-Thread

Dieses Beispiel stammt aus dem Barcodescanner-Beispiel.

Wenn Sie am Haupt-UI-Thread in einem C#-Projekt arbeiten möchten, verwenden Sie normalerweise die Methode CoreDispatcher.RunAsync wie hier.

private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        // Do work on the main UI thread here.
    });
}

Es ist viel einfacher, das in C++/WinRT auszudrücken. Beachten Sie, dass Parameter nach Wert akzeptiert werden, unter der Annahme, dass nach dem ersten Anhaltepunkt (in diesem Fall dem co_await) darauf zugegriffen werden soll. Weitere Informationen finden Sie unter Parameterübergabe.

winrt::fire_and_forget Watcher_Added(DeviceWatcher sender, winrt::DeviceInformation args)
{
    co_await Dispatcher();
    // Do work on the main UI thread here.
}

Wenn Sie die Arbeit mit einer anderen Priorität als der Standardpriorität ausführen müssen, sehen Sie sich die Funktion winrt::resume_foreground an, die eine Überladung aufweist, die Priorität erfordert. Codebeispiele, die zeigen, wie ein Aufruf von winrt::resume_foreground abgewartet werden kann, finden Sie unter Programmieren mit Threadaffinität.

Definieren Ihrer Laufzeitklassen in IDL

Weitere Informationen finden Sie unter IDL für den MainPage-Typ und Konsolidieren Ihrer .idl-Dateien.

Schließen Sie die erforderlichen C++/WinRT-Headerdateien des Windows-Namespace ein.

Wann immer Sie in C++/WinRT einen Typ aus einem Windows-Namespace verwenden möchten, müssen Sie die entsprechende C++/WinRT-Windows-Namespace-Headerdatei einschließen. Ein Beispiel finden Sie unter Portieren der NotifyUser-Methode.

Boxing und Unboxing

C# 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# C++/WinRT
int i; int i;
string s; winrt::hstring s;
object o; IInspectable o;
Vorgang C# 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# C++/WinRT
Unboxing eines bekannten integer-Elements i = (int)o; i = unbox_value<int>(o);
Wenn „o“ NULL ist System.NullReferenceException Absturz
Wenn „o“ kein geboxter int-Wert ist System.InvalidCastException Absturz
Unboxing für „int“, Fallback bei NULL; Absturz in allen anderen Fällen i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Unboxing für „int“ (falls möglich); Fallback in allen anderen Fällen i = as int? ?? fallback; i = unbox_value_or<int>(o, fallback);

Ein Beispiel finden Sie unter Portieren der OnNavigatedTo-Methode und Portieren der Footer_Click-Methode.

Boxing und Unboxing von string-Elementen

Ein string-Element ist in einigen Fällen ein Werttyp, in anderen Fällen ein Verweistyp. C# 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# 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.

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

Grundlegendes Boxing und Unboxing

Vorgang C# C++/WinRT
Boxing eines string-Elements o = s;
Eine leere Zeichenfolge wird zu einem Nicht-NULL-Objekt.
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 NULL-Zeichenfolge.
InvalidCastException, falls keine Zeichenfolge.
s = unbox_value<hstring>(o);
Absturz eines NULL-Objekts.
Absturz, falls keine Zeichenfolge.
Unboxing einer möglichen Zeichenfolge s = o as string;
Ein NULL-Objekt oder eine Nicht-Zeichenfolge wird zu einer NULL Zeichenfolge.

oder

s = o as string ?? fallback;
NULL oder eine Nicht-Zeichenfolge wird zu einem Fallback.
Leere Zeichenfolge beibehalten.
s = unbox_value_or<hstring>(o, fallback);
NULL oder eine Nicht-Zeichenfolge wird zu einem Fallback.
Leere Zeichenfolge beibehalten.

Verfügbarmachen einer Klasse für die {Binding}-Markuperweiterung

Wenn du die {Binding}-Markuperweiterung zum Binden von Daten an deinen Datentyp verwenden möchtest, findest du Informationen dazu unter Mit {Binding} deklariertes Bindungsobjekt.

Verwenden von Objekten aus XAML-Markup

In einem C#-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# 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.

Verfügbarmachen einer Datenquelle für XAML-Markup

In C++/WinRT, Version 2.0.190530.8 und höher erstellt winrt::single_threaded_observable_vector einen Observable-Vektor, der sowohl IObservableVector<T> als auch IObservableVector<IInspectable> unterstützt. Ein Beispiel finden Sie unter Portieren der Scenarios-Eigenschaft.

Du kannst deine Midl-Datei (.idl) folgendermaßen erstellen (siehe auch Einbeziehen von Laufzeitklassen in Midl-Dateien (.idl)).

namespace Bookstore
{
    runtimeclass BookSku { ... }

    runtimeclass BookstoreViewModel
    {
        Windows.Foundation.Collections.IObservableVector<BookSku> BookSkus{ get; };
    }

    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

Die Implementierung sieht dann so aus.

// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
    BookstoreViewModel()
    {
        m_bookSkus = winrt::single_threaded_observable_vector<Bookstore::BookSku>();
        m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
    }
    
	Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> BookSkus();
    {
        return m_bookSkus;
    }

private:
    Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> m_bookSkus;
};
...

Weitere Informationen finden Sie unter XAML-Elementsteuerelemente: Binden an eine C++/WinRT-Sammlung und Sammlungen mit C++/WinRT.

Verfügbarmachen einer Datenquelle für XAML-Markup (vor C++/WinRT 2.0.190530.8)

Die XAML-Datenbindung erfordert, dass eine Elementquelle IIterable<IInspectable> sowie eine der folgenden Schnittstellenkombinationen implementiert.

  • IObservableVector<IInspectable>
  • IBindableVector und INotifyCollectionChanged
  • IBindableVector und IBindableObservableVector
  • IBindableVector allein (antwortet nicht auf Änderungen)
  • IVector<IInspectable>
  • IBindableIterable (Iteration und Speicherung von Elementen erfolgt in einer privaten Sammlung)

Eine generische Schnittstelle wie IVector<T> wird zur Laufzeit nicht erkannt. Jede IVector<T>-Schnittstelle weist eine andere IID (Schnittstellenbezeichner) auf, die eine Funktion von T ist. Jeder Entwickler kann den T-Satz nach Belieben erweitern, daher kennt der XAML-Bindungscode nie den vollständigen Satz, der abgefragt werden muss. Diese Einschränkung ist kein Problem für C#, weil jedes CLR-Objekt, das IEnumerable<T> implementiert, automatisch auch IEnumerable implementiert. Auf ABI-Ebene bedeutet dies, dass jedes Objekt, das IObservableVector<T> implementiert, automatisch auch IObservableVector<IInspectable> implementiert.

C++/WinRT bietet diese Garantie nicht. Wenn eine C++/WinRT-Laufzeitklasse IObservableVector<T> implementiert, kann nicht davon ausgegangen werden, dass auch eine Implementierung von IObservableVector<IInspectable> bereitgestellt wird.

Daher muss das vorherige Beispiel folgendermaßen aussehen.

...
runtimeclass BookstoreViewModel
{
    // This is really an observable vector of BookSku.
    Windows.Foundation.Collections.IObservableVector<Object> BookSkus{ get; };
}

Die Implementierung sieht dann so aus.

// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
    BookstoreViewModel()
    {
        m_bookSkus = winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>();
        m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
    }
    
    // This is really an observable vector of BookSku.
	Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> BookSkus();
    {
        return m_bookSkus;
    }

private:
    Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> m_bookSkus;
};
...

Wenn du auf Objekte in m_bookSkus zugreifen musst, musst du einen QI-Vorgang zurück zu Bookstore::BookSku durchführen.

Widget MyPage::BookstoreViewModel(winrt::hstring title)
{
    for (auto&& obj : m_bookSkus)
    {
        auto bookSku = obj.as<Bookstore::BookSku>();
        if (bookSku.Title() == title) return bookSku;
    }
    return nullptr;
}

Abgeleitete Klassen

Um aus einer Laufzeitklasse abzuleiten, muss die Basisklasse zusammensetzbar sein. In C# 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 Headerdatei deines Implementierungstyps 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>
    {
        ...
    }
}

Wichtige APIs