Udostępnij przez


Przechodzenie do języka C++/WinRT z języka C++/CX

Ten temat jest pierwszym z serii opisujących sposób przenoszenia kodu źródłowego w projekcie C++/CX do jego odpowiednika w języku C++/WinRT.

Jeśli projekt korzysta również z typów biblioteki szablonów języka C++ środowiska uruchomieniowego systemu Windows Windows Runtime (WRL), zobacz Move to C++/WinRT from WRL(Przenoszenie do języka C++/WinRT zbiblioteki WRL).

Strategie przenoszenia

Warto wiedzieć, że przenoszenie z języka C++/CX do języka C++/WinRT jest ogólnie proste, z jednym wyjątkiem przenoszenia z biblioteki Parallel Patterns Library (PPL) zadań do współroutines. Modele są różne. Nie ma naturalnego mapowania "jeden do jednego" z zadań PPL do koroutines i nie ma prostego sposobu na mechaniczne przenoszenie kodu, który działa we wszystkich przypadkach. Aby uzyskać pomoc dotyczącą tego konkretnego aspektu przenoszenia oraz opcji współdziałania między dwoma modelami, zobacz Asynchrony i współdziałanie między językami C++/WinRT i C++/CX.

Zespoły programistyczne rutynowo zgłaszają, że gdy pokonają kluczową przeszkodę przenoszenia kodu asynchronicznego, dalsza część pracy jest w dużej mierze mechaniczna.

Przenoszenie w jednym przebiegu

Jeśli jesteś w stanie przełączyć cały projekt w jednym przebiegu, będziesz potrzebować tylko tego tematu dla potrzebnych informacji (i nie będziesz potrzebować międzyoperacyjnego tematów, które są zgodne z tym tematem). Zalecamy rozpoczęcie od utworzenia nowego projektu w programie Visual Studio przy użyciu jednego z szablonów projektów C++/WinRT (zobacz Obsługa programu Visual Studio dla języka C++/WinRT). Następnie przenieś pliki kodu źródłowego do tego nowego projektu i przenieś cały kod źródłowy C++/CX do języka C++/WinRT, tak jak to robisz.

Alternatywnie, jeśli wolisz wykonać pracę z przenoszeniem w istniejącym projekcie C++/CX, musisz dodać do niego obsługę języka C++/WinRT. Kroki, które należy wykonać, aby to zrobić, zostały opisane w Przyjmowanie projektu C++/CX i dodawanie obsługi C++/WinRT. Po przeniesieniu twój projekt, który był czystym projektem C++/CX, stanie się czystym projektem C++/WinRT.

Uwaga / Notatka

Jeśli masz projekt komponentu Środowiska uruchomieniowego systemu Windows, przeniesienie za jednym razem jest jedyną opcją. Projekt składnika środowiska uruchomieniowego systemu Windows napisany w języku C++ musi zawierać cały kod źródłowy C++/CX lub cały kod źródłowy C++/WinRT. Nie mogą współistnieć w tym typie projektu.

Przenoszenie projektu stopniowo

Z wyjątkiem projektów składników środowiska uruchomieniowego systemu Windows, jak wspomniano w poprzedniej sekcji, jeśli rozmiar lub złożoność bazy kodu sprawia, że konieczne jest stopniowe przenoszenie projektu, potrzebny będzie proces przenoszenia, w którym dla czasu C++/CX i C++/WinRT kod istnieje obok siebie w tym samym projekcie. Oprócz przeczytania tego tematu, zobacz również interoperacyjność pomiędzy C++/WinRT i C++/CX oraz Asynchroniczność i interoperacyjność pomiędzy C++/WinRT i C++/CX. Te tematy zawierają informacje i przykłady kodu przedstawiające sposób współdziałania między dwoma projekcjami językowymi.

Aby przygotować projekt do stopniowego procesu przenoszenia, jedną z opcji jest dodanie obsługi języka C++/WinRT do projektu C++/CX. Kroki, które należy wykonać, aby to zrobić, zostały opisane w Przyjmowanie projektu C++/CX i dodawanie obsługi C++/WinRT. Następnie można stopniowo stamtąd przenosić.

Inną opcją jest utworzenie nowego projektu w programie Visual Studio przy użyciu jednego z szablonów projektów C++/WinRT (zobacz Obsługa programu Visual Studio dla języka C++/WinRT). Następnie dodaj obsługę języka C++/CX do tego projektu. Kroki, które należy wykonać, aby to zrobić, zostały opisane w Biorąc projekt C++/WinRT i dodając obsługę języka C++/CX. Następnie możesz rozpocząć przenoszenie kodu źródłowego do tego elementu i przenoszenie niektórych kodu źródłowego C++/CX do języka C++/WinRT, tak jak to robisz.

W obu przypadkach będziesz współpracować (na oba sposoby) między kodem C++/WinRT i dowolnym kodem C++/CX, który jeszcze nie został portowany.

Uwaga / Notatka

Zarówno C++/CX, jak i Windows SDK deklarują typy w głównej przestrzeni nazw Windows. Typ systemu Windows przewidywany w języku C++/WinRT ma taką samą w pełni kwalifikowaną nazwę jak typ systemu Windows, ale znajduje się w przestrzeni nazw winrt języka C++. Te odrębne przestrzenie nazw umożliwiają przenoszenie z języka C++/CX do języka C++/WinRT we własnym tempie.

Przenoszenie projektu XAML stopniowo

Ważne

W przypadku projektu, który używa języka XAML, w danym momencie wszystkie typy stron XAML muszą być całkowicie C++/CX lub całkowicie C++/WinRT. Nadal można mieszać C++/CX i C++/WinRT poza typami stron XAML w tym samym projekcie (w modelach, modelach widoków i gdzie indziej).

W tym scenariuszu zalecany przepływ pracy polega na utworzeniu nowego projektu C++/WinRT i skopiowaniu kodu źródłowego i narzutu z projektu C++/CX. Dopóki wszystkie typy stron XAML są typu C++/WinRT, możesz dodać nowe strony XAML z Project>Dodaj nowy element...>Visual C++>Pusta strona (C++/WinRT).

Alternatywnie możesz użyć składnika środowiska uruchomieniowego systemu Windows (WRC), aby uwzględnić kod poza projektem XAML C++/CX podczas jego przenoszenia.

  • Możesz utworzyć nowy projekt C++/CX WRC, przenieść tyle kodu C++/CX, jak to możliwe do tego projektu, a następnie zmienić projekt XAML na C++/WinRT.
  • Możesz też utworzyć nowy projekt C++/WinRT WRC, pozostawić projekt XAML jako C++/CX i rozpocząć przenoszenie kodu C++/CX do języka C++/WinRT i przeniesienie wynikowego kodu z projektu XAML i do projektu składnika.
  • Można również mieć projekt składników C++/CX wraz z projektem składników C++/WinRT w ramach tego samego rozwiązania, odwoływać się do obu z projektu aplikacji i stopniowo przenosić z jednego do drugiego. Ponownie zobacz dotyczący międzyoperacyjności języków C++/WinRT i C++/CX, aby uzyskać więcej informacji na temat używania dwóch projekcji językowych w tym samym projekcie.

Pierwsze kroki przenoszenia projektu C++/CX do języka C++/WinRT

Bez względu na to, jaka będzie strategia przenoszenia (przenoszenie w jednym kroku lub stopniowe przenoszenie), pierwszym krokiem jest przygotowanie projektu do tego procesu. Poniżej przedstawiono podsumowanie tego, co opisano w Strategies for porting w kontekście rodzaju projektu, od którego zaczniesz, oraz sposobu jego konfiguracji.

  • Portowanie w jednym przebiegu. Utwórz nowy projekt w programie Visual Studio przy użyciu jednego z szablonów projektów C++/WinRT. Przenieś pliki z projektu C++/CX do tego nowego projektu i przenieś kod źródłowy C++/CX.
  • Przenoszenie projektu innego niż XAML stopniowo. Możesz dodać obsługę języka C++/WinRT do projektu C++/CX (zobacz Przejmowanie projektu C++/CX i dodanie obsługi języka C++/WinRT), i stopniowo portować. Możesz też utworzyć nowy projekt C++/WinRT i dodać do niego obsługę C++/CX (zobacz Tworzenie projektu C++/WinRT i dodawanie obsługi C++/CX), przenieść pliki i stopniowo dokonywać portowania.
  • Stopniowe przenoszenie projektu XAML. Utwórz nowy projekt C++/WinRT, stopniowo przenoś pliki i port. W dowolnym momencie typy stron XAML muszą być wszystkie C++/WinRT lub wszystkich C++/CX.

Pozostała część tego tematu ma zastosowanie niezależnie od wybranej strategii portowania. Zawiera wykaz szczegółów technicznych związanych z przenoszeniem kodu źródłowego z języka C++/CX do języka C++/WinRT. Jeśli przenosisz stopniowo, prawdopodobnie zechcesz również zapoznać się z sekcją o współpracy między C++/WinRT i C++/CX oraz o asynchroniczności, i współpracy między C++/WinRT i C++/CX.

Konwencje nazewnictwa plików

Pliki znaczników XAML

Źródło pliku C++/CX C++/WinRT
Pliki XAML dla deweloperów MyPage.xaml
MyPage.xaml.h
MyPage.xaml.cpp
MyPage.xaml
MyPage.h
MyPage.cpp
MyPage.idl (patrz poniżej)
Wygenerowane pliki XAML MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.g.h

Zwróć uwagę, że C++/WinRT usuwa .xaml z nazw plików *.h i *.cpp.

C++/WinRT dodaje dodatkowy plik dewelopera, plik Midl (idl). C++/CX automatycznie generuje ten plik wewnętrznie, dodając do niego każdego publicznego i chronionego członka. W języku C++/WinRT dodajesz i samodzielnie tworzysz plik. Aby uzyskać więcej informacji, przykłady kodu i przewodnik tworzenia IDL, zobacz: kontrolki XAML oraz powiązanie z właściwością C++/WinRT.

Zobacz również Rozbijanie klas środowiska wykonawczego na pliki Midl (.idl)

Klasy środowiska uruchomieniowego

Język C++/CX nie nakłada ograniczeń na nazwy plików nagłówkowych; Często umieszcza się wiele definicji klas środowiska uruchomieniowego w jednym pliku nagłówka, szczególnie w przypadku małych klas. Jednak język C++/WinRT wymaga, aby każda klasa środowiska uruchomieniowego ma własny plik nagłówkowy o nazwie po nazwie klasy.

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 { ... };
}

Mniej typowe (ale nadal legalne) w języku C++/CX jest używanie inaczej nazwanych plików nagłówków dla kontrolek niestandardowych XAML. Musisz zmienić nazwę tego pliku nagłówka, aby był zgodny z nazwą klasy.

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 { ... };
}

Wymagania dotyczące pliku nagłówka

Język C++/CX nie wymaga dołączania żadnych specjalnych plików nagłówkowych, ponieważ wewnętrznie autogeneruje pliki nagłówkowe z plików .winmd. Często w języku C++/CX używane są dyrektywy using dla przestrzeni nazw używanych przez nazwę.

using namespace Windows::Media::Playback;

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

Dyrektywa using namespace Windows::Media::Playback umożliwia pisanie MediaPlaybackItem bez prefiksu przestrzeni nazw. Zajęliśmy się również przestrzenią nazw Windows.Media.Core, ponieważ item->VideoTracks->GetAt(0) zwraca Windows.Media.Core.VideoTrack. Ale nie musieliśmy wpisać nazwy VideoTrack nigdzie, więc nie potrzebujemy using Windows.Media.Core dyrektywy.

Jednak język C++/WinRT wymaga dołączenia pliku nagłówka odpowiadającego każdej używanej przestrzeni nazw, nawet jeśli nie zostanie ona nazwana.

#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();
}

Z drugiej strony, mimo że zdarzenie MediaPlaybackItem.AudioTracksChanged jest typu TypedEventHandler<MediaPlaybackItem, Windows.Foundation.Collections.IVectorChangedEventArgs>, nie musimy uwzględniać winrt/Windows.Foundation.Collections.h, ponieważ nie używaliśmy tego zdarzenia.

Język C++/WinRT wymaga również, żeby dołączyć pliki nagłówkowe dla przestrzeni nazw używanych przez znaczniki XAML.

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

Użycie klasy Prostokąt oznacza, że należy dodać tę wartość dołączaną.

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

Jeśli zapomnisz pliku nagłówkowego, wszystko się skompiluje poprawnie, ale otrzymasz błędy linkera, ponieważ brakuje klas consume_.

Przekazywanie parametrów

Podczas pisania kodu źródłowego C++/CX przekazujesz typy C++/CX jako parametry funkcji jako odwołania do hat (^).

void LogPresenceRecord(PresenceRecord^ record);

W języku C++/WinRT w przypadku funkcji synchronicznych należy domyślnie używać const& parametrów. Pozwoli to uniknąć kopii i zatłoczonego obciążenia. Jednake powinny używać wartości typu pass-by-value, aby upewnić się, że przechwytują według wartości i unikają problemów z okresem istnienia (aby uzyskać więcej informacji, zobacz współbieżność i asynchroniczne operacje w języku C++/WinRT).

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

Obiekt C++/WinRT jest zasadniczo wartością, która zawiera wskaźnik interfejsu do obiektu pomocniczego środowiska uruchomieniowego systemu Windows. Podczas kopiowania obiektu C++/WinRT kompilator kopiuje hermetyzowany wskaźnik interfejsu, zwiększając liczbę odwołań. Ostateczne zniszczenie kopii obejmuje dekrementację liczby odwołań. Dlatego tylko wtedy, gdy jest to konieczne, ponosimy obciążenie związane z utworzeniem kopii.

Odwołania do zmiennych i pól

Podczas pisania kodu źródłowego C++/CX należy używać zmiennych hat (^) do odwoływania się do obiektów środowiska uruchomieniowego systemu Windows oraz operatora strzałki (->), aby wyłuszczyć zmienną hat.

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

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

Podczas przenoszenia do równoważnego kodu C++/WinRT można przejść długą drogę, usuwając kapelusze i zmieniając operator strzałki (->) na operator kropki (.). Przewidywane typy C++/WinRT to wartości, a nie wskaźniki.

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

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

Domyślny konstruktor odwołania c++/CX inicjuje go do wartości null. Oto przykład kodu C++/CX, w którym tworzymy zmienną/pole poprawnego typu, ale niezainicjowaną. Innymi słowy, początkowo nie dotyczy TextBlock; zamierzamy przypisać referencję później.

TextBlock^ textBlock;

class MyClass
{
    TextBlock^ textBlock;
};

Aby uzyskać odpowiednik w języku C++/WinRT, zobacz Opóźnione inicjowanie.

Właściwości

Rozszerzenia języka C++/CX obejmują pojęcie właściwości. Podczas pisania kodu źródłowego C++/CX można uzyskać dostęp do właściwości tak, jakby to było pole. Standardowy C++ nie posiada pojęcia właściwości, dlatego w C++/WinRT wywołujesz funkcje get i set.

W poniższych przykładach XboxUserId, UserState, PresenceDeviceRecordsi Size są wszystkimi właściwościami.

Pobieranie wartości z właściwości

Poniżej przedstawiono sposób uzyskiwania wartości właściwości w języku C++/CX.

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

Równoważny kod źródłowy C++/WinRT wywołuje funkcję o takiej samej nazwie jak właściwość, ale bez parametrów.

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

Należy pamiętać, że funkcja PresenceDeviceRecords zwraca obiekt środowiska uruchomieniowego systemu Windows, który sam w sobie ma funkcję Size. Ponieważ zwracany obiekt jest również typem projekcji C++/WinRT, wykonujemy dereferencję przy użyciu operatora '.' w celu wywołania Size.

Ustawianie właściwości na nową wartość

Ustawienie właściwości na nową wartość jest zgodne z podobnym wzorcem. Najpierw w języku C++/CX.

record->UserState = newValue;

Aby wykonać odpowiednik w języku C++/WinRT, należy wywołać funkcję o takiej samej nazwie jak właściwość i przekazać argument.

record.UserState(newValue);

Tworzenie wystąpienia klasy

Pracujesz z obiektem C++/CX za pomocą uchwytu, powszechnie znanego jako odwołanie do kapelusza (^). Nowy obiekt tworzy się za pomocą słowa kluczowego ref new , co z kolei wywołuje klasę RoActivateInstance w celu aktywowania nowego wystąpienia klasy środowiska uruchomieniowego.

using namespace Windows::Storage::Streams;

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

Obiekt C++/WinRT jest wartością; można go przydzielić na stosie lub jako pole obiektu. Nigdy nigdy nie używać ref new (ani new) w celu przydzielenia obiektu C++/WinRT. W ukryciu nadal wywoływana jest RoActivateInstance.

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

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

Jeśli zasób jest kosztowny do zainicjowania, często opóźnia się inicjowanie, dopóki nie będzie on rzeczywiście potrzebny. Jak już wspomniano, domyślny konstruktor dla odwołania typu "hat" w C++/CX inicjuje go do wartości 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;
};

Ten sam kod został przekierowany do języka C++/WinRT. Zwróć uwagę na użycie konstruktora std::nullptr_t . Aby uzyskać więcej informacji na temat tego konstruktora, zobacz Opóźnione inicjowanie.

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 };
};

Jak domyślny konstruktor wpływa na kolekcje

Typy kolekcji języka C++ używają konstruktora domyślnego, co może spowodować niezamierzoną konstrukcję obiektu.

Scenariusz C++/CX C++/WinRT (niepoprawne) C++/WinRT (poprawna)
Zmienna lokalna, początkowo pusta TextBox^ textBox; TextBox textBox; // Creates a TextBox! TextBox textBox{ nullptr };
Zmienna składowa, początkowo pusta class C {
  TextBox^ textBox;
};
class C {
  TextBox textBox; // Creates a TextBox!
};
class C {
  TextBox textbox{ nullptr };
};
Zmienna globalna, początkowo pusta TextBox^ g_textBox; TextBox g_textBox; // Creates a TextBox! TextBox g_textBox{ nullptr };
Wektor pustych odwołań std::vector<TextBox^> boxes(10); // Creates 10 TextBox objects!
std::vector<TextBox> boxes(10);
std::vector<TextBox> boxes(10, nullptr);
Ustawianie wartości na mapie 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);
Tablica pustych odwołań TextBox^ boxes[2]; // Creates 2 TextBox objects!
TextBox boxes[2];
TextBox boxes[2] = { nullptr, nullptr };
Para std::pair<TextBox^, String^> p; // Creates a TextBox!
std::pair<TextBox, String> p;
std::pair<TextBox, String> p{ nullptr, nullptr };

Więcej informacji o kolekcjach pustych odwołań

Zawsze, gdy masz Platform::Array^ (zobacz Port Platform::Array^) w C++/CX, masz możliwość przeniesienia go do std::vector w C++/WinRT (faktycznie do dowolnego ciągłego kontenera), zamiast pozostawiać go jako tablicę. Istnieją zalety wyboru std::vector.

Na przykład, podczas gdy istnieje skrót do tworzenia wektora o stałym rozmiarze pustych odwołań (patrz tabela powyżej), nie ma skrótu na utworzenie tablicy pustych odwołań. Należy powtórzyć nullptr dla każdego elementu w tablicy. Jeśli masz za mało, dodatki będą tworzone domyślnie.

W przypadku wektora można wypełnić je pustymi odwołaniami podczas inicjowania (tak jak w powyższej tabeli) lub wypełnić je pustymi odwołaniami po zainicjowaniu za pomocą kodu takiego jak ten.

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

Więcej informacji na temat przykładu std::map

Operator indeksu dolnego [] dla std::map zachowuje się następująco.

  • Jeśli klucz zostanie znaleziony na mapie, zwróć odwołanie do istniejącej wartości (którą można zastąpić).
  • Jeśli klucz nie zostanie znaleziony w mapie, utwórz nowy wpis w mapie, który będzie się składał z klucza (przeniesionego, jeśli to możliwe) i wartości domyślnie skonstruowanej, i zwróć referencję do wartości (którą można następnie zastąpić).

Innymi słowy, [] operator zawsze tworzy wpis na mapie. Różni się to od języków C#, Java i JavaScript.

Konwertowanie z podstawowej klasy czasu wykonania na klasę pochodną

Często zdarza się, że istnieje odwołanie do bazy, o której wiadomo, że odnosi się do obiektu typu pochodnego. W języku C++/CX użyjesz dynamic_cast, aby rzutować odwołanie do podstawy do pochodnego odwołania. dynamic_cast to naprawdę tylko ukryte wywołanie QueryInterface. Oto typowy przykład — obsługujesz zdarzenie zmiany właściwości zależności i chcesz rzutować obiekt DependencyObject z powrotem na rzeczywisty typ, który jest właścicielem właściwości zależności.

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

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

Równoważny kod C++/WinRT zastępuje dynamic_cast wywołaniem funkcji IUnknown::try_as, która hermetyzuje QueryInterface. Istnieje również możliwość wywołania IUnknown::as, co zamiast tego spowoduje zgłoszenie wyjątku, jeśli zapytanie dotyczące wymaganego interfejsu (domyślnego interfejsu żądanego typu) nie zostanie zwrócone. Oto przykład kodu C++/WinRT.

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 ...
    }
}

Klasy pochodne

Aby dziedziczyć z klasy środowiska uruchomieniowego, klasa bazowa musi być komponowalna. Język C++/CX nie wymaga wykonania żadnych specjalnych kroków w celu skomponowania klas, ale robi to C++/WinRT. Aby wskazać, że klasa ma być używana jako klasa bazowa, należy użyć słowa kluczowego unsealed.

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

W klasie nagłówka implementacji należy dołączyć plik nagłówka klasy bazowej przed dołączeniem automatycznie wygenerowanego nagłówka dla klasy pochodnej. W przeciwnym razie zostaną wyświetlone błędy, takie jak "Niedozwolone użycie tego typu jako wyrażenie".

// 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>
    {
        ...
    }
}

Obsługa zdarzeń za pomocą delegata

Oto typowy przykład obsługi zdarzenia w języku C++/CX przy użyciu funkcji lambda jako delegata w tym przypadku.

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

Jest to odpowiednik języka 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.
});

Zamiast funkcji lambda można zaimplementować delegata jako funkcję wolną lub jako wskaźnik do funkcji członkowskiej. Aby uzyskać więcej informacji, zobacz Obsługa zdarzeń za pomocą delegatów w C++/WinRT.

Jeśli przenosisz z bazy kodu C++/CX, w której zdarzenia i delegaty są używane wewnętrznie (a nie między plikami binarnymi), winrt::delegate ułatwi replikowanie tego wzorca w języku C++/WinRT. Zobacz również Delegaty sparametryzowane, proste sygnały i wywołania zwrotne w projekcie.

Odwołowanie delegata

W języku C++/CX używasz operatora -=, aby odwołać wcześniejszą rejestrację zdarzenia.

myButton->Click -= token;

Jest to odpowiednik języka C++/WinRT.

myButton().Click(token);

Aby uzyskać więcej informacji i opcji, zobacz Odwoływanie zarejestrowanego delegata.

Opakowywanie i rozpakowywanie

Język C++/CX automatycznie opakowuje liczby skalarne w obiekty. Język C++/WinRT wymaga jawnego wywołania funkcji winrt::box_value. Oba języki wymagają jawnego rozpakowania (unboxingu). Zobacz boksowanie i odboksowanie w C++/WinRT.

W poniższych tabelach użyjemy tych definicji.

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

C++/CX i C# zgłaszają wyjątki, jeśli spróbujesz rozpakować wskaźnik null do typu wartości. C++/WinRT uważa to za błąd programowania i ulega awarii. W języku C++/WinRT użyj funkcji winrt::unbox_value_or , jeśli chcesz obsłużyć przypadek, w którym obiekt nie jest typem, który był uważany za.

Scenariusz C++/CX C++/WinRT
Rozpakuj znaną liczbę całkowitą i = (int)o; i = unbox_value<int>(o);
Jeśli o jest null Platform::NullReferenceException Wypadek
Jeśli o nie jest opakowanym intem Platform::InvalidCastException Wypadek
Rozpakuj int, użyj wartości domyślnej, jeśli jest null; zakończ działanie w przypadku innych wartości i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Rozpakuj "int", jeśli to możliwe; użyj alternatywy dla innych przypadków. auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

Boxing i unboxing ciągu znaków

Ciąg jest w jakiś sposób typem wartości i w inny sposób typem odwołania. C++/CX i C++/WinRT traktują ciągi inaczej.

Typ ABI HSTRING jest wskaźnikiem do ciągu znaków zarządzanego referencyjnie. Ale nie pochodzi z IInspectable, więc nie jest to technicznie obiekt . Ponadto, null HSTRING reprezentuje pusty ciąg. Boxowanie elementów, które nie pochodzą z IInspectable, odbywa się poprzez opakowywanie ich wewnątrz IReference<T>, a środowisko wykonawcze Windows Runtime zapewnia standardową implementację w postaci obiektu PropertyValue (typy niestandardowe są zgłaszane jako PropertyType::OtherType).

C++/CX reprezentuje ciąg środowiska uruchomieniowego systemu Windows jako typ odwołania; podczas gdy C++/WinRT projektuje ciąg jako typ wartości. Oznacza to, że opakowany ciąg znaków null może mieć różne reprezentacje w zależności od tego, jak do tego doszło.

Ponadto język C++/CX umożliwia dereferencję wartości null String^, w takim przypadku zachowuje się jak łańcuch "".

Zachowanie C++/CX C++/WinRT
Deklaracje Object^ o;
String^ s;
IInspectable o;
hstring s;
Kategoria typu ciągu Typ odwołania Typ wartości
null HSTRING projekty jako (String^)nullptr hstring{}
Czy null i "" są identyczne? Tak Tak
Ważność wartości null s = nullptr;
s->Length == 0 (prawidłowe)
s = hstring{};
s.size() == 0 (prawidłowe)
W przypadku przypisania ciągu null do obiektu o = (String^)nullptr;
o == nullptr
o = box_value(hstring{});
o != nullptr
Jeśli przypiszesz "" do obiektu o = "";
o == nullptr
o = box_value(hstring{L""});
o != nullptr

Podstawy boksowania i rozboksowywania.

Operacja C++/CX C++/WinRT
Osadzanie ciągu w ramce o = s;
Pusty ciąg staje się nullptr.
o = box_value(s);
Pusty ciąg staje się obiektem niebędącym wartością null.
Rozpakuj znany ciąg s = (String^)o;
Obiekt o wartości null staje się pustym ciągiem.
InvalidCastException, jeśli nie jest ciągiem.
s = unbox_value<hstring>(o);
Obiekt o wartości null ulega awarii.
Zawieszenie, jeśli nie jest ciągiem.
Rozpakuj możliwy ciąg s = dynamic_cast<String^>(o);
Obiekt o wartości null lub ciąg inny niż ciąg staje się pustym ciągiem.
s = unbox_value_or<hstring>(o, fallback);
Wartość null lub wartość niebędąca ciągiem staje się wartością domyślną.
Zachowany pusty ciąg.

Współbieżność i operacje asynchroniczne

Biblioteka Wzorców Równoległych (PPL) (współbieżności::task, na przykład) została zaktualizowana w celu obsługi odwołań do wskaźników C++/CX.

W przypadku języka C++/WinRT należy użyć kohroutines i co_await zamiast tego. Aby dowiedzieć się więcej oraz zobaczyć przykłady kodu, zobacz Współbieżność i operacje asynchroniczne za pomocą języka C++/WinRT.

Korzystanie z obiektów z znaczników XAML

W projekcie C++/CX można wykorzystywać prywatne elementy członkowskie i nazwane elementy ze znaczników XAML. Jednak w języku C++/WinRT wszystkie elementy używane przy użyciu rozszerzenia znaczników XAML {x:Bind} muszą być dostępne publicznie w IDL.

Ponadto powiązanie z wartością logiczną powoduje wyświetlenie true lub false w języku C++/CX, natomiast w C++/WinRT wyświetla się Windows.Foundation.IReference`1<Boolean>.

Aby uzyskać więcej informacji oraz przykłady kodu, zobacz Korzystanie z obiektów z markupu.

Mapowanie typów platformy C++/CX na typy C++/WinRT

Język C++/CX udostępnia kilka typów danych w przestrzeni nazw platformy . Te typy nie są standardowymi językami C++, więc można ich używać tylko podczas włączania rozszerzeń języka środowiska uruchomieniowego systemu Windows (właściwość projektu programu Visual Studio C/C++>Ogólne>korzystanie z rozszerzenia środowiska uruchomieniowego systemu Windows>Tak (/ZW)). Poniższa tabela ułatwia przenoszenie typów z platformy do ich odpowiedników w języku C++/WinRT. Po wykonaniu tej czynności, ponieważ język C++/WinRT jest standardowym językiem C++, możesz wyłączyć /ZW tę opcję.

C++/CX C++/WinRT
Platform::Agile^ winrt::agile_ref
Platform::Array^ Zobacz Port Platform::Array^
Platform::Exception^ winrt::hresult_error
Platform::NieprawidłowyArgument^ winrt::hresult_invalid_argument
Platform::Object^ winrt::Windows::Foundation::IInspectable
Platform::String^ winrt::hstring

Port Platform::Agile^ do winrt::agile_ref

Typ Platform::Agile^ w języku C++/CX reprezentuje klasę środowiska uruchomieniowego systemu Windows, do których można uzyskać dostęp z dowolnego wątku. Odpowiednik C++/WinRT to winrt::agile_ref.

W języku C++/CX.

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

W języku C++/WinRT.

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

Platform::Array^

W przypadkach, gdy język C++/CX wymaga użycia tablicy, C++/WinRT umożliwia korzystanie z dowolnego ciągłego kontenera. Zobacz Jak konstruktor domyślny wpływa na kolekcje z przyczyny, dla której std::vector jest dobrym wyborem.

Dlatego zawsze, gdy masz Platform::Array^ w języku C++/CX, opcje przenoszenia obejmują użycie listy inicjatora, std::arraylub std::vector. Aby uzyskać więcej informacji i przykłady kodu, zobacz standardowe listy inicjalizatorów i standardowe tablice i wektory.

Port Platform::Exception^ do winrt::hresult_error

Typ Platform::Exception^ jest generowany w C++/CX, gdy Windows Runtime API zwraca HRESULT inny niż S_OK. Odpowiednik C++/WinRT jest winrt::hresult_error.

Aby użyć portu do języka C++/WinRT, zmień cały kod, który używa platformy ::Exception^, aby użyć winrt::hresult_error.

W języku C++/CX.

catch (Platform::Exception^ ex)

W języku C++/WinRT.

catch (winrt::hresult_error const& ex)

Język C++/WinRT udostępnia te klasy wyjątków.

Typ wyjątku Klasa bazowa HRESULT
winrt::hresult_error wywołaj hresult_error::to_abi
winrt::hresult_access_denied winrt::hresult_error E_ACCESSDENIED (odmowa dostępu)
winrt::hresult_canceled winrt::hresult_error BŁĄD_ODWOŁANO
winrt::hresult_changed_state winrt::hresult_error E_CHANGED_STATE
winrt::hresult_class_not_available winrt::hresult_error CLASS_E_CLASSNOTAVAILABLE (Klasa nie jest dostępna)
winrt::hresult_illegal_delegate_assignment winrt::hresult_error NIELEGALNE_PRZYPISANIE_DELEGATA
winrt::hresult_illegal_method_call winrt::hresult_error E_ILLEGAL_METHOD_CALL
winrt::hresult_illegal_state_change winrt::hresult_error E_ILLEGAL_STATE_CHANGE (Niedozwolona zmiana stanu)
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 Błąd: RPC_E_WRONG_THREAD (Nieprawidłowy wątek)

Należy pamiętać, że każda klasa (przez klasę bazową hresult_error) udostępnia funkcję to_abi, która zwraca HRESULT błędu, oraz funkcję message, która zwraca reprezentację ciągu tego HRESULT.

Oto przykład zgłaszania wyjątku w języku C++/CX.

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

I odpowiednik w języku C++/WinRT.

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

Port Platform::Object^ na winrt::Windows::Foundation::IInspectable

Podobnie jak wszystkie typy C++/WinRT, winrt::Windows::Foundation::IInspectable jest typem wartości. Poniżej przedstawiono sposób inicjowania zmiennej tego typu na wartość null.

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

Port Platform::String^ do winrt::hstring

Platform::String^ jest odpowiednikiem typu ABI HSTRING środowiska uruchomieniowego systemu Windows. W przypadku języka C++/WinRT odpowiednik to winrt::hstring. Jednak za pomocą języka C++/WinRT można wywoływać interfejsy API środowiska uruchomieniowego systemu Windows przy użyciu szerokich typów ciągów biblioteki C++, takich jak std::wstring i/lub szerokie literały ciągów. Aby uzyskać więcej szczegółów i przykłady kodu, zobacz Obsługa ciągów w języku C++/WinRT.

Za pomocą języka C++/CX można uzyskać dostęp do właściwości Platform::String::Data w celu pobrania ciągu jako const wchar_t* tablicy (na przykład w celu przekazania jej do std::wcout).

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

Aby zrobić to samo w języku C++/WinRT, możesz użyć funkcji hstring::c_str , aby uzyskać wersję ciągu w stylu C zakończoną wartością null, podobnie jak w przypadku ciągu std::wstring.

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

Jeśli chodzi o implementowanie interfejsów API, które przyjmują lub zwracają ciągi znaków, zazwyczaj zmienia się kod napisany w C++/CX, który używa Platform::String^, na kod używający winrt::hstring.

Oto przykład interfejsu API języka C++/CX, który przyjmuje ciąg.

void LogWrapLine(Platform::String^ str);

W przypadku języka C++/WinRT możesz zadeklarować ten interfejs API w wersji MIDL 3.0 w następujący sposób.

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

Łańcuch narzędzi C++/WinRT wygeneruje kod źródłowy, który wygląda następująco.

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

ToString()

Typy C++/CX zapewniają metodę Object::ToString .

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

Język C++/WinRT nie zapewnia bezpośrednio tej funkcji, ale możesz zwrócić się do alternatyw.

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

Język C++/WinRT obsługuje również winrt::to_hstring dla ograniczonej liczby typów. Należy dodać przeciążenia dla dodatkowych typów, które chcesz konwertować na ciągi znaków.

Język Konwertuj int na ciąg znaków Konwertuj wyliczenie na ciąg
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);

W przypadku zamiany wyliczenia na ciąg znaków należy podać implementację winrt::to_hstring.

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));
        }
    }
}

Te stringifikacje są często wykorzystywane niejawnie przez powiązanie danych.

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

Te powiązania będą wykonywać winrt::to_hstring właściwości powiązanej. W przypadku drugiego przykładu (StatusEnum), musisz dostarczyć własną wersję przeciążenia funkcji winrt::to_hstring, w przeciwnym razie zostanie wyświetlony błąd kompilatora.

Tworzenie ciągów

C++/CX i C++/WinRT wykorzystują standardowy std::wstringstream do tworzenia ciągów znaków.

Operacja C++/CX C++/WinRT
Dołączanie ciągu, zachowywanie wartości null stream.print(s->Data(), s->Length); stream << std::wstring_view{ s };
Dołączanie ciągu, zatrzymywanie na pierwszej wartości null stream << s->Data(); stream << s.c_str();
Wyodrębnij wynik ws = stream.str(); ws = stream.str();

Więcej przykładów

W poniższych przykładach ws jest zmienną typu std::wstring. Ponadto, podczas gdy język C++/CX może skonstruować ciąg Platform::String z ciągu 8-bitowego, język C++/WinRT tego nie robi.

Operacja C++/CX C++/WinRT
Konstruowanie ciągu z literału String^ s = "hello";
String^ s = L"hello";
// winrt::hstring s{ "hello" }; // Doesn't compile
winrt::hstring s{ L"hello" };
Przekonwertuj z std::wstringzachowując wartości null 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
Konwertuj z std::wstring, zatrzymaj się na pierwszym znaku null 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
Przekonwertuj na std::wstring, zachowując wartości null std::wstring ws{ s->Data(), s->Length };
ws = std::wstring(s>Data(), s->Length);
std::wstring ws{ s };
ws = s;
Przekonwertuj na std::wstring, zatrzymaj przy pierwszej wartości null std::wstring ws{ s->Data() };
ws = s->Data();
std::wstring ws{ s.c_str() };
ws = s.c_str();
Przekaż literał do metody Method("hello");
Method(L"hello");
// Method("hello"); // Doesn't compile
Method(L"hello");
Przekaż std::wstring do metody Method(ref new String(ws.c_str(),
  (uint32_t)ws.size()); // Stops on first null
Method(ws);
// param::winrt::hstring accepts std::wstring_view

Ważne interfejsy API