Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
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
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.href class A { ... }ref class B { ... } |
common.idlruntimeclass A { ... }runtimeclass B { ... } |
A.hnamespace implements {struct A { ... };} |
|
B.hnamespace 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.hpartial ref class LongNameForA { ... } |
LongNameForA.hnamespace 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 compilewinrt::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 compileMethod(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
- szablon struktury winrt::delegate
- winrt::hresult_error, struktura
- struktura winrt::hstring
- przestrzeń nazw winrt
Tematy pokrewne
- C++/CX
- Zdarzenia autora w C++/WinRT
- Współbieżność i operacje asynchroniczne w języku C++/WinRT
- Korzystaj z interfejsów API w języku C++/WinRT
- Obsługa zdarzeń przy użyciu delegatów w języku C++/WinRT
- Interoperacyjność między C++/WinRT a C++/CX
- Asynchroniczność i współpraca między platformami C++/WinRT i C++/CX
- Microsoft Interface Definition Language 3.0 — dokumentacja
- Przejście do C++/WinRT z WRL
- Obsługa stringów w C++/WinRT