Udostępnij za pomocą


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

Wskazówka

Jeśli czytałeś ten temat wcześniej i wracasz do niego z zamiarem wykonania konkretnego zadania, możesz przejść do sekcji Znajdź zawartość na podstawie zadania, które wykonujesz w tym wątku.

Ten temat kompleksowo zawiera wykaz szczegółów technicznych związanych z przenoszeniem kodu źródłowego w projekcie języka C# do jego odpowiednika w C++/WinRT.

Aby zapoznać się z analizą przypadku przenoszenia jednej z przykładów aplikacji platformy uniwersalnej systemu Windows (UWP), zobacz temat towarzyszący Przenoszenie przykładu Schowka do języka C++/WinRT zjęzyka C#. Możesz zdobyć praktykę i doświadczenie w przenoszeniu, postępując zgodnie z tym przewodnikiem i przenosząc przykład samodzielnie w miarę postępów.

Jak przygotować się i czego się spodziewać

Analiza przypadku Przenoszenie przykładu Schowka do języka C++/WinRT z języka C# ilustruje przykłady rodzajów decyzji projektowych oprogramowania, które należy podjąć podczas przenoszenia projektu do języka C++/WinRT. Dlatego warto przygotować się do przenoszenia, zdobywając solidne zrozumienie sposobu działania istniejącego kodu. W ten sposób uzyskasz dobry przegląd funkcjonalności aplikacji i struktury kodu, a następnie decyzje, które podejmujesz, zawsze będą podejmowane do przodu i w odpowiednim kierunku.

Jeśli chodzi o to, jakich zmian w przenoszeniu można się spodziewać, można je zgrupować w cztery kategorie.

  • Przenieś projekcję języka. Środowisko uruchomieniowe systemu Windows (WinRT) jest przewidywane do różnych języków programowania. Każda z tych projekcji języka została zaprojektowana tak, aby czuła się idiomatyczna dla danego języka programowania. W przypadku języka C# niektóre typy środowiska uruchomieniowego systemu Windows są przewidywane jako typy platformy .NET. Na przykład przetłumaczysz element System.Collections.Generic.IReadOnlyList<T> z powrotem do elementu Windows.Foundation.Collections.IVectorView<T>. Ponadto w języku C# niektóre operacje środowiska uruchomieniowego systemu Windows są przewidywane jako wygodne funkcje języka C#. Na przykład w języku C# używa się składni operatora += do zarejestrowania delegata obsługującego zdarzenie. Dlatego będziesz tłumaczyć funkcje języka, takie jak ta, do podstawowej operacji, która jest wykonywana (rejestracja zdarzeń, w tym przykładzie).
  • Składnia języka portów. Wiele z tych zmian to proste przekształcenia mechaniczne, zastępując jeden symbol dla innego. Na przykład zmiana kropki (.) na dwukropek (::).
  • Procedura języka portów. Niektóre z nich mogą być prostymi, powtarzalnymi zmianami (takimi jak myObject.MyPropertymyObject.MyProperty()). Inne wymagają głębszych zmian (na przykład przeniesienie procedury, która wykorzystuje System.Text.StringBuilder na taką, która wykorzystuje std::wostringstream).
  • zadania związane z przenoszeniem specyficzne dla języka C++/WinRT. Niektóre szczegóły środowiska uruchomieniowego systemu Windows są obsługiwane niejawnie przez język C#, w tle. Szczegóły te są realizowane jawnie w C++/WinRT. Przykładem jest użycie .idl pliku do zdefiniowania klas środowiska uruchomieniowego.

Po następującym indeksie opartym na zadaniach pozostałe sekcje w tym temacie są ustrukturyzowane zgodnie z taksonomią powyżej.

Znajdowanie zawartości na podstawie wykonywanego zadania

Zadanie Zawartość
Tworzenie składnika środowiska uruchomieniowego systemu Windows (WRC) Niektóre funkcje można osiągnąć (lub niektóre wywoływane interfejsy API) tylko w języku C++. Możesz uwzględnić te funkcje w języku C++/WinRT WRC, a następnie korzystać z WRC z (na przykład) aplikacji języka C#. Zobacz składniki środowiska uruchomieniowego systemu Windows w języku C++/WinRT i Jeśli tworzysz klasę środowiska uruchomieniowego w składniku środowiska uruchomieniowego systemu Windows.
Przenieś metodę asynchroniczną Dobrym pomysłem jest, aby pierwszy wiersz metody asynchronicznej w klasie środowiska uruchomieniowego C++/WinRT był auto lifetime = get_strong(); (zobacz Bezpieczne uzyskiwanie dostępu do wskaźnika this w korutynie składowej klasy).

Przenoszenie z Task, zobacz akcję asynchroniczną.
Przenoszenie z Task<T>— zobacz operację asynchroniczną.
Przenoszenie z async void, metoda Fire-and-forget, patrz .
Portowanie klasy Najpierw określ, czy klasa musi być klasą środowiska uruchomieniowego, czy też może być zwykłą klasą. Aby ułatwić podjęcie decyzji, zobacz początek interfejsów API tworzenia przy użyciu języka C++/WinRT. Następnie zapoznaj się z poniższymi trzema kolejnymi wierszami.
Przenoszenie klasy środowiska uruchomieniowego Klasa, która udostępnia funkcje poza aplikacją języka C++ lub klasę używaną w powiązaniu danych XAML. Zobacz , jeśli tworzysz klasę uruchomieniową w składniku Windows Runtime, lub , jeśli klasa ma być referencją w interfejsie użytkownika XAML.

Te linki opisują to bardziej szczegółowo, ale klasa środowiska uruchomieniowego musi być zadeklarowana w języku IDL. Jeśli projekt zawiera już plik IDL (na przykład Project.idl), zalecamy zadeklarowanie dowolnej nowej klasy środowiska uruchomieniowego w tym pliku. W języku IDL zadeklaruj wszystkie metody i składowe danych, które będą używane poza aplikacją lub będą używane w języku XAML. Po zaktualizowaniu pliku IDL, przebuduj projekt i sprawdź wygenerowane pliki szkieletowe (.h i .cpp) w folderze Generated Files projektu (w Eksploratorze rozwiązań , z wybranym węzłem projektu, upewnij się, że opcja Pokaż wszystkie pliki jest włączona). Porównaj pliki wycinkowe z plikami już w projekcie, dodając pliki lub dodając/aktualizując podpisy funkcji w razie potrzeby. Składnia pliku wycinkowego jest zawsze poprawna, dlatego zalecamy użycie jej w celu zminimalizowania błędów kompilacji. Gdy szablony w projekcie będą zgodne z elementami w plikach szablonów, możesz je wdrożyć, przenosząc kod języka C#.
Przenoszenie zwykłej klasy Zobacz , jeśli nie tworzysz klasy środowiska uruchomieniowego.
Autor IDL Wprowadzenie do języka Microsoft Interface Definition Language 3.0
Jeśli tworzysz klasę uruchomieniową do użycia w UI XAML
Konsumowanie obiektów ze znaczników XAML
Definiowanie klas środowiska uruchomieniowego w języku IDL
Przenoszenie kolekcji Kolekcje z C++/WinRT
Umożliwienie dostępu do źródła danych w znacznikach XAML
kontener asocjacyjny
Dostęp do elementu wektora
Portowanie zdarzenia delegowanie programu obsługi zdarzeń jako składowej klasy
Odwołaj delegata obsługi zdarzeń
Portowanie metody W języku C#: private async void SampleButton_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { ... }
Do pliku .h C++/WinRT: fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&);
Do pliku .cpp C++/WinRT: fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...}
Łańcuchy portów Obsługa stringów w C++/WinRT
ToString
tworzenie ciągów
Boksowanie i rozpakowywanie ciągu znaków
Konwersja typów (rzutowanie typów) C#: o.ToString()
C++/WinRT: to_hstring(static_cast<int>(o))
Patrz również ToString.

C#: (Value)o
C++/WinRT: unbox_value<Value>(o)
Rzuca wyjątek, jeśli rozpakowywanie się nie powiedzie. Zobacz również Boxing i rozpakowywanie.

C#: o as Value? ?? fallback
C++/WinRT: unbox_value_or<Value>(o, fallback)
Zwraca wartość domyślną, jeśli rozpakowanie się nie powiedzie. Zobacz również Boxing i rozpakowywanie.

C#: (Class)o
C++/WinRT: o.as<Class>()
Zgłasza błąd w przypadku niepowodzenia konwersji.

C#: o as Class
C++/WinRT: o.try_as<Class>()
Zwraca wartość null, jeśli konwersja nie powiedzie się.

Zmiany, które obejmują projekcję języka

Kategoria C# C++/WinRT Zobacz także
Nietypowany obiekt objectlub System.Object Windows::Foundation::IInspectable Przenoszenie metody EnableClipboardContentChangedNotifications
Przestrzenie nazw projekcji using System; using namespace Windows::Foundation;
using System.Collections.Generic; using namespace Windows::Foundation::Collections;
Rozmiar kolekcji collection.Count collection.Size() Przenoszenie metody BuildClipboardFormatsOutputString
Typowy typ kolekcji IList<T> i Dodaj aby dodać element. IVector<T>i Append w celu dodania elementu. Jeśli używasz std::vector w dowolnym miejscu, użyj push_back, aby dodać element.
Typ kolekcji tylko do odczytu IReadOnlyList<T> IVectorView<T> Przenoszenie metody BuildClipboardFormatsOutputString
delegat programu obsługi zdarzeń jako element klasy myObject.EventName += Handler; token = myObject.EventName({ get_weak(), &Class::Handler }); Przenoszenie metody EnableClipboardContentChangedNotifications
odwołać delegata procedury obsługi zdarzeń myObject.EventName -= Handler; myObject.EventName(token); Przenoszenie metody EnableClipboardContentChangedNotifications
kontener asocjacyjny ISłownik<K, V> IMap<K, V>
dostęp do elementu członkowskiego Vector x = v[i];
v[i] = x;
x = v.GetAt(i);
v.SetAt(i, x);

Rejestrowanie/odwoływanie obsługi zdarzeń

W języku C++/WinRT masz kilka opcji składniowych umożliwiających rejestrowanie/odwoływanie delegata procedury obsługi zdarzeń zgodnie z opisem w Obsługa zdarzeń przy użyciu delegatów w języku C++/WinRT. Zobacz również Przenoszenie metody EnableClipboardContentChangedNotifications metody.

Czasami, gdy adresat zdarzenia (obiekt obsługujący zdarzenie) ma zostać zniszczony, należy odwołać procedurę obsługi zdarzeń, aby źródło zdarzeń (obiekt wywołujący zdarzenie) nie wywołuje zniszczonego obiektu. Sprawdź Odwołanie zarejestrowanego pełnomocnika. W takich przypadkach utwórz zmienną event_token składową dla programów obsługi zdarzeń. Aby zapoznać się z przykładem, zobacz Przenoszenie metody EnableClipboardContentChangedNotifications.

Można również zarejestrować procedurę obsługi zdarzeń w adiustacji XAML.

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

W języku C# metoda OpenButton_Click może być prywatna, a kod XAML nadal będzie mógł połączyć się z ButtonBase.Click zdarzeniem wywoływanym przez OpenButton.

W C++/WinRT metoda OpenButton_Click musi być publiczna w typie implementacji, jeśli chcesz ją zarejestrować w znacznikach XAML. Jeśli zarejestrujesz program obsługi zdarzeń tylko w kodzie imperatywnym, program obsługi zdarzeń nie musi być publiczny.

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

Alternatywnie możesz ustawić stronę rejestracji XAML jako przyjaciela typu implementacji i OpenButton_Click prywatną.

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

Ostatnim scenariuszem jest miejsce, w którym projekt języka C#, który przenosi wiąże z programem obsługi zdarzeń z znaczników (aby uzyskać więcej informacji na temat tego scenariusza, zobacz Functions w pliku x:Bind).

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

Możesz po prostu zmienić ten znacznik na bardziej prosty Click="OpenButton_Click". Jeśli wolisz, możesz zachować ten znacznik tak, jak to jest. Wszystko, co musisz zrobić, aby go obsługiwać, to zadeklarować program obsługi zdarzeń w języku IDL.

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

Uwaga / Notatka

Zadeklaruj funkcję jako void, nawet jeśli zaimplementujesz ją jako "Fire and forget".

Zmiany, które obejmują składnię języka

Kategoria C# C++/WinRT Zobacz także
Modyfikatory dostępu public \<member\> public:
    \<member\>
Przenoszenie metody Button_Click
Uzyskiwanie dostępu do elementu członkowskiego danych this.variable this->variable  
akcja asynchroniczna async Task ... IAsyncAction ... interfejs IAsyncAction, współbieżność i operacje asynchroniczne za pomocą języka C++/WinRT
operacja asynchroniczna async Task<T> ... IAsyncOperation<T> ... interfejs IAsyncOperation, współbieżność i operacje asynchroniczne w języku C++/WinRT
metoda "fire-and-forget" (oznacza metodę asynchroniczną) async void ... winrt::fire_and_forget ... Portowanie metody CopyButton_Click, Fire and forget
Uzyskiwanie dostępu do wyliczonej stałej E.Value E::Value przenoszenie metody DisplayChangedFormats
Wspólne oczekiwanie await ... co_await ... Przenoszenie metody CopyButton_Click
Kolekcja projektowanych typów jako prywatne pole private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); std::vector
<MyNamespace::MyRuntimeClass>
m_myRuntimeClasses;
Konstrukcja identyfikatora GUID 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} };
Separator przestrzeni nazw A.B.T A::B::T
Zero null nullptr Przeniesienie metody UpdateStatus
Uzyskiwanie obiektu typu typeof(MyType) winrt::xaml_typename<MyType>() Przenoszenie właściwości Scenarios
Deklaracja parametru dla metody MyType MyType const& przekazywanie parametrów
Deklaracja parametru dla metody asynchronicznej MyType MyType przekazywanie parametrów
Wywoływanie metody statycznej T.Method() T::Method()
Ciągi string, lub System.String winrt::hstring Obsługa stringów w C++/WinRT
Literał ciągu "a string literal" L"a string literal" Przenoszenie konstruktora Currenti FEATURE_NAME
Typ wywnioskowany (lub wyprowadzony) var auto Przenoszenie metody BuildClipboardFormatsOutputString
Korzystanie z dyrektywy using A.B.C; using namespace A::B::C; Przenoszenie konstruktora Currenti FEATURE_NAME
Literał dosłownego łańcucha znaków/surowego łańcucha znaków @"verbatim string literal" LR"(raw string literal)" Przenoszenie DisplayToast metody

Uwaga / Notatka

Jeśli plik nagłówka nie zawiera dyrektywy using namespace dla danej przestrzeni nazw, musisz w pełni zakwalifikować wszystkie nazwy typów dla tej przestrzeni nazw lub przynajmniej aby były wystarczająco kwalifikowane, aby kompilator je znalazł. Aby zapoznać się z przykładem, zobacz Przenoszenie metody DisplayToast.

Przenoszenie klas i elementów

Musisz zdecydować, czy dla każdego typu C# należy go przenieść do typu Windows Runtime, czy też do zwykłej klasy/struktury/wyliczenia C++. Aby uzyskać więcej informacji i szczegółowe przykłady ilustrujące sposób podejmowania tych decyzji, zobacz Przeniesienie przykładu Schowka z języka C# do C++/WinRT.

Właściwość języka C# zwykle staje się funkcją dostępu, funkcją mutatora oraz polem danych. Aby uzyskać więcej informacji i przykład, zobacz Przenoszenie właściwości IsClipboardContentChangedEnabled.

W przypadku pól niestatycznych, zrób z nich składowe danych typu implementacji .

Pole statyczne języka C# staje się statycznym akcesorem C++/WinRT i/lub funkcją mutatora. Aby uzyskać więcej informacji i przykład, zobacz Przenoszenie konstruktora, Obecnyi FEATURE_NAME.

W przypadku funkcji składowych należy ponownie zdecydować dla każdej z osobna, czy powinna należeć do IDL, czy jest funkcją publiczną lub prywatną w typie implementacji. Aby uzyskać więcej informacji i przykłady sposobu podejmowania decyzji, zobacz IDL dla MainPage typu.

Przenoszenie znaczników XAML i plików zasobów

W przypadku Przenoszenie przykładu Schowka do języka C++/WinRT zjęzyka C# mogliśmy użyć tego samego znaczników XAML (w tym zasobów) i plików elementów zawartości w języku C# i projekcie C++/WinRT. W niektórych przypadkach konieczne będzie wprowadzenie zmian w znacznikach. Zapoznaj się z , skopiuj XAML oraz style niezbędne do zakończenia migracji MainPage.

Zmiany obejmujące procedury w języku

Kategoria C# C++/WinRT Zobacz także
Zarządzanie cyklem życia w metodzie asynchronicznej N/A auto lifetime{ get_strong() }; lub
auto lifetime = get_strong();
Przenoszenie metody CopyButton_Click
Utylizacja using (var t = v) auto t{ v };
t.Close(); // or let wrapper destructor do the work
Portowanie metody CopyImage
Konstruowanie obiektu new MyType(args) MyType{ args } lub
MyType(args)
Przenoszenie właściwości Scenarios
Tworzenie niezainicjowanej referencji MyType myObject; MyType myObject{ nullptr }; lub
MyType myObject = nullptr;
Przenoszenie konstruktora Currenti FEATURE_NAME
Budowa obiektu jako zmiennej z args var myObject = new MyType(args); auto myObject{ MyType{ args } }; lub
auto myObject{ MyType(args) }; lub
auto myObject = MyType{ args }; lub
auto myObject = MyType(args); lub
MyType myObject{ args }; lub
MyType myObject(args);
Portowanie metody Footer_Click
Konstruowanie obiektu w zmiennej bez args var myObject = new T(); MyType myObject; Przenoszenie metody BuildClipboardFormatsOutputString
Skrót inicjowania obiektu var p = new FileOpenPicker{
    ViewMode = PickerViewMode.List
};
FileOpenPicker p;
p.ViewMode(PickerViewMode::List);
Operacje wektorowe zbiorcze var p = new FileOpenPicker{
    FileTypeFilter = { ".png", ".jpg", ".gif" }
};
FileOpenPicker p;
p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" });
Przenoszenie metody CopyButton_Click
Iterowanie w kolekcji foreach (var v in c) for (auto&& v : c) Przenoszenie metody BuildClipboardFormatsOutputString
Przechwyć wyjątek catch (Exception ex) catch (winrt::hresult_error const& ex) Przenoszenie metody PasteButton_Click
Szczegóły wyjątku ex.Message ex.message() Przenoszenie metody PasteButton_Click
Pobierz wartość właściwości myObject.MyProperty myObject.MyProperty() przenoszenie metody NotifyUser
Ustaw wartość właściwości myObject.MyProperty = value; myObject.MyProperty(value);
Zwiększanie wartości właściwości myObject.MyProperty += v; myObject.MyProperty(thing.Property() + v);
W przypadku ciągów użyj obiektu Builder
ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()
Ciąg języka do ciągu środowiska uruchomieniowego systemu Windows N/A winrt::hstring{ s }
Tworzenie ciągów StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
tworzenie ciągów
Interpolacja ciągów $"{i++}) {s.Title}" winrt::to_hstringi/lub winrt::hstring::operator+ Przenoszenie metody OnNavigatedTo
Pusty ciąg do porównania System.String.Empty winrt::hstring::empty Przeniesienie metody UpdateStatus
Tworzenie pustego ciągu var myEmptyString = String.Empty; winrt::hstring myEmptyString{ L"" };
Operacje słownika 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)
Konwersja typu (zgłaszanie błędu) (MyType)v v.as<MyType>() Portowanie metody Footer_Click
Konwersja typu (wartość null w przypadku niepowodzenia) v as MyType v.try_as<MyType>() Przenoszenie metody PasteButton_Click
Elementy XAML z właściwościami x:Name MyNamedElement MyNamedElement() Przenoszenie konstruktora Currenti FEATURE_NAME
Przełącz się do wątku interfejsu użytkownika CoreDispatcher.RunAsync CoreDispatcher.RunAsynclub winrt::resume_foreground Przenoszenie metody NotifyUseri Przenoszenie metody HistoryAndRoaming
Konstrukcja elementu interfejsu użytkownika w kodzie imperatywnego na stronie XAML Zobacz konstruowanie elementów interfejsu użytkownika Zobacz konstruowanie elementów interfejsu użytkownika

Poniższe sekcje zawierają bardziej szczegółowe informacje dotyczące niektórych elementów w tabeli.

Konstrukcja elementu interfejsu użytkownika

Te przykłady kodu pokazują konstrukcję elementu interfejsu użytkownika w kodzie imperatywnej strony XAML.

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()

Typy języka C# udostępniają metodę Object.ToString .

int i = 2;
var s = i.ToString(); // s is a System.String with value "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# 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);

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.

Zobacz również Przenoszenie metody Footer_Click.

Tworzenie ciągów

W przypadku kompilowania ciągów język C# ma wbudowany typ StringBuilder .

Kategoria C# C++/WinRT
Tworzenie ciągów StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Dołącz ciąg Windows Runtime z zachowaniem wartości null builder.Append(s); builder << std::wstring_view{ s };
Dodawanie nowego wiersza builder.Append(Environment.NewLine); builder << std::endl;
Uzyskiwanie dostępu do wyniku s = builder.ToString(); ws = builder.str();

Zobacz również Przenoszenie metody BuildClipboardFormatsOutputStringi Przenoszenie metody DisplayChangedFormats.

Uruchamianie kodu w głównym wątku interfejsu użytkownika

Został zaczerpnięty z przykładu skanera kodów kreskowych .

Jeśli chcesz pracować nad głównym wątkiem interfejsu użytkownika w projekcie języka C#, zazwyczaj używasz metody CoreDispatcher.RunAsync , takiej jak ta.

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

Znacznie łatwiej jest wyrazić to w języku C++/WinRT. Zwróć uwagę, że przyjmujemy parametry według wartości przy założeniu, że chcemy uzyskać do nich dostęp po pierwszym punkcie zawieszenia ( co_awaitw tym przypadku ). Aby uzyskać więcej informacji, zobacz przekazywanie parametrów.

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

Jeśli musisz wykonać pracę z priorytetem innym niż domyślna, zobacz funkcję winrt::resume_foreground, która ma przeciążenie, które ma priorytet. Aby zobaczyć przykłady kodu pokazujące, jak oczekiwać na wywołanie winrt::resume_foreground, zobacz Programowanie z koligacją wątków.

Definiowanie klas środowiska uruchomieniowego w języku IDL

Zobacz IDL dla typu MainPagei Konsoliduj pliki .

Uwzględnij potrzebne pliki nagłówkowe przestrzeni nazw systemu Windows C++/WinRT

W języku C++/WinRT zawsze, gdy chcesz użyć typu z przestrzeni nazw systemu Windows, musisz dołączyć odpowiedni plik nagłówka przestrzeni nazw systemu Windows C++/WinRT. Aby zapoznać się z przykładem, zobacz Przenoszenie metody NotifyUser.

Opakowywanie i rozpakowywanie

Język C# automatycznie opakowuje typy wartości 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# C++/WinRT
int i; int i;
string s; winrt::hstring s;
object o; IInspectable o;
Operacja C# 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# C++/WinRT
Rozpakuj znaną liczbę całkowitą i = (int)o; i = unbox_value<int>(o);
Jeśli o jest null System.NullReferenceException Wypadek
Jeśli o nie jest opakowanym intem System.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 != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Rozpakuj "int", jeśli to możliwe; użyj alternatywy dla innych przypadków. i = as int? ?? fallback; i = unbox_value_or<int>(o, fallback);

Aby zapoznać się z przykładem, zobacz Przenoszenie metody OnNavigatedToi Przenoszenie metody Footer_Click.

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. Języki C# 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).

Język C# 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.

Zachowanie C# 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 "" hstring{}
Czy null i "" są identyczne? Nie. Tak
Ważność wartości null s = null;
s.Length zgłasza wyjątek NullReferenceException
s = hstring{};
s.size() == 0 (prawidłowe)
W przypadku przypisania ciągu null do obiektu o = (string)null;
o == null
o = box_value(hstring{});
o != nullptr
Jeśli przypiszesz "" do obiektu o = "";
o != null
o = box_value(hstring{L""});
o != nullptr

Podstawy boksowania i rozboksowywania.

Operacja C# C++/WinRT
Osadzanie ciągu w ramce o = s;
Pusty ciąg staje się obiektem niebędącym wartością null.
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ę ciągiem null.
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 = o as string;
Obiekt o wartości null lub nie będący ciągiem staje się ciągiem null.

LUB

s = o as string ?? fallback;
Wartość null lub wartość niebędąca ciągiem staje się wartością domyślną.
Zachowany pusty ciąg.
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.

Udostępnianie klasy rozszerzeniu znaczników {Binding}

Jeśli zamierzasz użyć rozszerzenia znaczników {Binding} do wprowadzenia powiązań danych z Twoim typem danych, zobacz Obiekt powiązania zadeklarowany przy użyciu {Binding}.

Korzystanie z obiektów z znaczników XAML

W projekcie języka C# można używać prywatnych elementów członkowskich i nazwanych elementów z 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 wiązanie z wartością typu Boolean wyświetla true lub false w języku C#, ale pokazuje Windows.Foundation.IReference`1<Boolean> w języku C++/WinRT.

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

Udostępnianie źródła danych dla znaczników XAML

W C++/WinRT, począwszy od wersji 2.0.190530.8, winrt::single_threaded_observable_vector tworzy obserwowalny wektor, który obsługuje zarówno IObservableVector<T>, jak i IObservableVector<IInspectable>. Aby zapoznać się z przykładem, zobacz Przenoszenie właściwości Scenarios.

Możesz utworzyć plik Midl (.idl) w następujący sposób (zobacz również podział klas środowiska uruchomieniowego na pliki Midl (.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; };
    }
}

I zaimplementuj w ten sposób.

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

Aby uzyskać więcej informacji, zobacz kontrolki elementów XAML; wiązanie z kolekcją C++/WinRTi kolekcjami za pomocą języka C++/WinRT.

Udostępnianie źródła danych dla znaczników XAML (przed C++/WinRT 2.0.190530.8)

Powiązanie danych XAML wymaga, aby źródło elementów implementowało IIterable<IInspectable>, a także jedną z następujących kombinacji interfejsów.

  • IObservableVector<IInspectable>
  • IBindableVector i INotifyCollectionChanged
  • IBindableVector i IBindableObservableVector
  • IBindableVector sam w sobie (nie będzie reagować na zmiany)
  • IVector<IInspectable>
  • IBindableIterable (będzie iterować i zapisywać elementy w prywatnej kolekcji)

Nie można wykryć interfejsu ogólnego, takiego jak IVector<T>, w czasie wykonywania. Każdy IVector<T> ma inny identyfikator interfejsu (IID), który jest funkcją T. Każdy deweloper może rozszerzyć kolekcję T dowolnie, więc oczywiście kod powiązania XAML nigdy nie może znać pełnego zestawu, o który można zapytać. To ograniczenie nie jest problemem dla języka C#, ponieważ każdy obiekt CLR implementujący IEnumerable T< automatycznie implementuje IEnumerable>. Na poziomie ABI oznacza to, że każdy obiekt, który to implementuje IObservableVector<T>, automatycznie implementuje IObservableVector<IInspectable>.

Język C++/WinRT nie oferuje tej gwarancji. Jeśli klasa środowiska uruchomieniowego C++/WinRT implementuje IObservableVector<T>, nie możemy założyć, że implementacja IObservableVector<IInspectable> jest również w jakiś sposób dostępna.

W związku z tym poniżej przedstawiono sposób, w jaki poprzedni przykład będzie musiał wyglądać.

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

I implementacja.

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

Jeśli musisz uzyskać dostęp do obiektów w m_bookSkus, musisz przekonwertować je z powrotem na Bookstore::BookSku.

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

Klasy pochodne

Aby dziedziczyć z klasy środowiska uruchomieniowego, klasa bazowa musi być komponowalna. Język C# nie wymaga wykonania żadnych specjalnych kroków w celu skomponowania klas, ale język C++/WinRT to wymaga. 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 pliku nagłówkowym dla typu 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>
    {
        ...
    }
}

Ważne interfejsy API