Переход на C++/WinRT с C#

Подсказка

Если вы прочитали этот раздел раньше, и вы вернелись к нему с определенной задачей, то вы можете перейти к содержимому поиска на основе задачи, которую вы выполняете в этом разделе.

В этом разделе подробно описаны технические сведения, связанные с переносом исходного кода в проекте C# в эквивалент C++/WinRT.

Пример переноса одного из примеров приложений Universal Windows Platform (UWP) см. в сопутствующем разделе Перенос примера буфера обмена с C# на C++/WinRT. Вы можете получить практику и опыт по переносу, следуя этому пошаговому руководству и самостоятельно перенося пример по ходу дела.

Как подготовиться и что ожидать

Пример переноса примера буфера обмена на C++/WinRT из C# иллюстрирует примеры типов решений по проектированию программного обеспечения, которые вы будете принимать при переносе проекта на C++/WinRT. Поэтому рекомендуется подготовиться к переносу, получив четкое представление о том, как работает существующий код. Таким образом вы получите хороший обзор функциональных возможностей приложения и структуры кода, а затем решения, которые вы принимаете, всегда будут принимать вас вперед и в правильном направлении.

С точки зрения того, какие типы переноса изменений следует ожидать, можно сгруппировать их в четыре категории.

  • Перенести языковую проекцию. среда выполнения Windows (WinRT) проецируется на различные языки программирования. Каждая из этих языковых проекций разработана так, чтобы выглядеть естественно для соответствующего языка программирования. Для C# некоторые типы среда выполнения Windows проецируются как .NET типы. Например, вы будете переводить System.Collections.Generic.IReadOnlyList<T> обратно в Windows. Foundation.Collections.IVectorView<T>. Кроме того, в C#некоторые операции среда выполнения Windows проецируются как удобные функции языка C#. Примером является то, что в C# используется += синтаксис оператора для регистрации делегата обработки событий. Таким образом, вы будете сводить подобные языковые конструкции к базовой выполняемой операции (в данном примере — регистрации события).
  • Синтаксис языка порта. Многие из этих изменений являются простыми механическими преобразованиями, заменяя один символ другим. Например, замена точки (.) на двойное двоеточие (::).
  • Процедура языка порта. Некоторые из них могут быть простыми, повторяющимися изменениями (например, замена myObject.MyProperty на myObject.MyProperty()). Другие нуждаются в более глубоких изменениях (например, перенос процедуры, которая включает использование System.Text.StringBuilder в ту, которая включает использование std::wostringstream).
  • Задачи, связанные с переносом, специфичные для C++/WinRT. Некоторые детали среда выполнения Windows неявно обрабатываются C#, за кулисами. Эти детали реализованы явным образом в C++/WinRT. Примером является использование .idl файла для определения классов среды выполнения.

После следующего индекса на основе задач остальные разделы в этом разделе структурированы в соответствии с таксономией выше.

Поиск содержимого на основе выполняемой задачи

задачи Content
Создание компонента среда выполнения Windows (WRC) Некоторые функциональные возможности можно достичь (или некоторые вызовы API) только с помощью C++. Эту функцию можно учитывать в WRC C++/WinRT, а затем использовать WRC из (например) приложения C#. См. Компоненты среда выполнения Windows с C++/WinRT и Создание класса времени выполнения в компоненте среда выполнения Windows.
Перенос асинхронного метода Рекомендуется, чтобы первой строкой асинхронного метода в классе среды выполнения C++/WinRT было auto lifetime = get_strong(); (см. Безопасный доступ к указателю this в корутине-члене класса).

Перенос с Task, см. Асинхронное действие.
Перенос с Task<T>, см. Асинхронная операция.
При переносе из async void см. метод «отправил и забыл».
Перенос класса Сначала определите, должен ли класс быть классом среды выполнения или может ли он быть обычным классом. Чтобы помочь вам принять решение, см. самое начало статьи Создание API с помощью C++/WinRT. Затем см. следующие три строки ниже.
Перенос класса среды выполнения Класс, который использует функциональные возможности за пределами приложения C++ или класса, используемого в привязке данных XAML. См. статью "Если вы создаете класс среды выполнения в компоненте среда выполнения Windows" или "Если вы создаете класс среды выполнения для ссылки в пользовательском интерфейсе XAML".

Эти ссылки подробно описывают это, но класс среды выполнения должен быть объявлен в IDL. Если project уже содержит IDL-файл (например, Project.idl), рекомендуется объявить любой новый класс среды выполнения в этом файле. В IDL объявите все методы и элементы данных, которые будут использоваться вне приложения или будут использоваться в XAML. После обновления IDL-файла перестройте проект и просмотрите сгенерированные файлы-заглушки (.h и .cpp) в папке Generated Files вашего проекта (в окне Обозреватель решений, выбрав узел проекта, убедитесь, что включен параметр Show All Files). Сравните stub-файлы с файлами, уже имеющимися в вашем проекте, и при необходимости добавьте недостающие файлы или добавьте либо обновите сигнатуры функций. Синтаксис файла-заглушки всегда корректен, поэтому мы рекомендуем использовать его, чтобы свести к минимуму ошибки сборки. Как только заглушки в вашем проекте совпадут с заглушками в файлах заглушек, можно переходить к их реализации, перенеся код C#.
Перенести обычный класс См. статью "Если вы не создаете класс среды выполнения".
Автор IDL Общие сведения о языке определения интерфейса (Майкрософт) 3.0
Если вы создаете класс среды выполнения, на который можно ссылаться в пользовательском интерфейсе XAML
Использование объектов из разметки XAML
Определение классов среды выполнения в IDL
Перенос коллекции Коллекции с помощью C++/WinRT
Предоставление доступа к источнику данных в разметке XAML
Ассоциативный контейнер
Доступ к элементу вектора
Перенос события Делегат обработчика событий как член класса
Отзыв делегата обработчика событий
Перенести метод Из C#: private async void SampleButton_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e) { ... }
В файл C++/WinRT .h : fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&);
В файл C++/WinRT .cpp : fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...}
Строковые значения портов Обработка строк в C++/WinRT
ToString
Строковое построение
Упаковка и распаковка строки
Преобразование типов (приведение типов) C#: o.ToString()
C++/WinRT: to_hstring(static_cast<int>(o))
См. также статью ToString.

C#: (Value)o
C++/WinRT: unbox_value<Value>(o)
Вызывается при сбое распаковки. Также см. Упаковка и распаковка.

C#: o as Value? ?? fallback
C++/WinRT: unbox_value_or<Value>(o, fallback)
Возвращает резервный вариант, если распаковка завершается ошибкой. См. также Упаковка и распаковка.

C#: (Class)o
C++/WinRT: o.as<Class>()
Вызывается, если преобразование завершается ошибкой.

C#: o as Class
C++/WinRT: o.try_as<Class>()
Возвращает значение NULL, если преобразование завершается ошибкой.

Изменения, связанные с проекцией языка

Category C# C++/WinRT См. также
Нетипизированный объект object, или System.Object Windows::Foundation::IInspectable Перенос метода EnableClipboardContentChangedNotifications
Пространства имен проекции using System; using namespace Windows::Foundation;
using System.Collections.Generic; using namespace Windows::Foundation::Collections;
Размер коллекции collection.Count collection.Size() Перенос метода BuildClipboardFormatsOutputString
Типичный тип коллекции IList<T>, и Add для добавления элемента. IVector<T> и Append для добавления элемента. Если вы где-либо используете std::vector, то используйте push_back, чтобы добавить элемент.
Тип коллекции только для чтения IReadOnlyList<T> IVectorView<T> Перенос метода BuildClipboardFormatsOutputString
Делегат обработчика событий как член класса myObject.EventName += Handler; token = myObject.EventName({ get_weak(), &Class::Handler }); Перенос метода EnableClipboardContentChangedNotifications
Отзыв делегата обработчика событий myObject.EventName -= Handler; myObject.EventName(token); Перенос метода EnableClipboardContentChangedNotifications
Ассоциативный контейнер IDictionary<K, V> IMap<K, V>
Доступ к элементу вектора x = v[i];
v[i] = x;
x = v.GetAt(i);
v.SetAt(i, x);

Регистрация и отзыв обработчика событий

В C++/WinRT есть несколько синтаксических параметров для регистрации и отзыва делегата обработчика событий, как описано в разделе "Обработка событий с помощью делегатов в C++/WinRT". См. также раздел "Перенос метода EnableClipboardContentChangedNotifications".

Иногда, например, когда получатель события (объект, обрабатывающий событие) вот-вот будет уничтожен, необходимо отменить регистрацию обработчика события, чтобы источник события (объект, инициирующий событие) не обращался к уничтоженному объекту. См. раздел "Отзыв зарегистрированного делегата". В таких случаях создайте переменную-член event_token для обработчиков событий. Пример см. в разделе "Перенос метода EnableClipboardContentChangedNotifications".

Вы также можете зарегистрировать обработчик событий в разметке XAML.

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

В C#метод OpenButton_Click может быть частным, и XAML по-прежнему сможет подключить его к событию ButtonBase.Click , созданному OpenButton.

В C++/WinRT метод OpenButton_Click должен быть общедоступным в типе реализации, если вы хотите зарегистрировать его в разметке XAML. Если вы регистрируете обработчик событий только в императивном коде, обработчик событий не должен быть общедоступным.

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

Кроме того, вы можете сделать XAML-страницу, в которой выполняется регистрация, дружественной вашему типу реализации, а OpenButton_Click — закрытым.

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

Еще один заключительный сценарий — это когда проект C#, который вы переносите, привязывает обработчик событий из разметки (дополнительные сведения об этом сценарии см. в разделе Функции в x:Bind).

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

Вы можете просто изменить эту разметку на более простой Click="OpenButton_Click". Или, если хотите, можете сохранить эту разметку как есть. Все, что необходимо сделать для поддержки, заключается в объявлении обработчика событий в IDL.

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

Note

Объявите функцию как void, даже если вы реализуете её как запустил и забыл.

Изменения, связанные с синтаксисом языка

Category C# C++/WinRT См. также
Модификаторы доступа public \<member\> public:
    \<member\>
Перенос метода Button_Click
Доступ к элементу данных this.variable this->variable  
Асинхронное действие async Task ... IAsyncAction ... Интерфейс IAsyncAction, Параллелизм и асинхронные операции с C++/WinRT
Асинхронная операция async Task<T> ... IAsyncOperation<T> ... Интерфейс IAsyncOperation, Параллелизм и асинхронные операции с C++/WinRT
Метод «запустил и забыл» (подразумевает асинхронность) async void ... winrt::fire_and_forget ... Перенос метода CopyButton_Click, Запустил и забыл
Доступ к перечисленной константе E.Value E::Value Перенос метода DisplayChangedFormats
Ожидать совместно await ... co_await ... Перенос метода CopyButton_Click
Коллекция проецируемых типов в виде частного поля private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); std::vector
<MyNamespace::MyRuntimeClass>
m_myRuntimeClasses;
Создание 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} };
Разделитель пространства имен A.B.T A::B::T
Null null nullptr Перенос метода UpdateStatus
Получение объекта типа typeof(MyType) winrt::xaml_typename<MyType>() Перенос свойства Scenarios
Объявление параметров для метода MyType MyType const& Передача параметров
Объявление параметров для асинхронного метода MyType MyType Передача параметров
Вызов статического метода T.Method() T::Method()
Строки string, или System.String winrt::hstring Обработка строк в C++/WinRT
Строковый литерал "a string literal" L"a string literal" Перенос конструктора, Current и FEATURE_NAME
Выведенный (или дедуцированный) тип var auto Перенос метода BuildClipboardFormatsOutputString
Директива Using- using A.B.C; using namespace A::B::C; Перенос кода конструктора, Current и FEATURE_NAME
Дословный или необработанный строковый литерал @"verbatim string literal" LR"(raw string literal)" Перенос метода DisplayToast

Note

Если файл заголовка не содержит директиву using namespace для заданного пространства имен, вам придется полностью указать все имена типов для этого пространства имен; или, по крайней мере, достаточно квалифицировать их для компилятора, чтобы найти их. Пример см. в разделе "Перенос метода DisplayToast".

Перенос классов и их членов

Для каждого типа C# необходимо решить, следует ли перенести его в тип среда выполнения Windows или в обычный класс C++, структуру или перечисление. Дополнительные сведения и подробные примеры, демонстрирующие принятие этих решений, см. в статье "Перенос примера буфера обмена на C++/WinRT" из C#.

Свойство C# обычно становится функцией доступа, функцией мутатора и элементом резервных данных. Дополнительные сведения и пример см. в разделе "Перенос свойства IsClipboardContentChangedEnabled".

Для нестатических полей сделайте их элементами данных типа реализации.

Статическое поле C# преобразуется в статическую функцию доступа и/или статическую функцию-мутатор C++/WinRT. Дополнительные сведения и пример см. в статье "Перенос конструктора", "Current" и FEATURE_NAME.

Для функций-членов, опять же, необходимо для каждой из них решить, следует ли включать её в IDL или же она является открытым либо закрытым методом вашего типа реализации. Дополнительные сведения и примеры решения см. в IDL для типа MainPage.

Перенос разметки XAML и файлов ресурсов

В случае переноса примера буфера обмена на C++/WinRT из C#, мы смогли использовать ту же разметку XAML (включая ресурсы) и файлы ресурсов в C# и проекте C++/WinRT. В некоторых случаях изменения разметки потребуются для этого. См. Скопируйте XAML и стили, необходимые для завершения переноса MainPage.

Изменения, затрагивающие процедуры в языке

Category C# C++/WinRT См. также
Управление временем существования в асинхронном методе N/A auto lifetime{ get_strong() }; или
auto lifetime = get_strong();
Перенос метода CopyButton_Click
Выбытие using (var t = v) auto t{ v };
t.Close(); // or let wrapper destructor do the work
Перенос метода CopyImage
Создать объект new MyType(args) MyType{ args } или
MyType(args)
Перенос свойства Scenarios
Создание неинициализированной ссылки MyType myObject; MyType myObject{ nullptr }; или
MyType myObject = nullptr;
Перенос конструктора, Current и FEATURE_NAME
Создание объекта в переменную с помощью args var myObject = new MyType(args); auto myObject{ MyType{ args } }; или
auto myObject{ MyType(args) }; или
auto myObject = MyType{ args }; или
auto myObject = MyType(args); или
MyType myObject{ args }; или
MyType myObject(args);
Перенос метода Footer_Click
Создание объекта в переменную без args var myObject = new T(); MyType myObject; Перенос метода BuildClipboardFormatsOutputString
Сокращенная инициализация объектов var p = new FileOpenPicker{
    ViewMode = PickerViewMode.List
};
FileOpenPicker p;
p.ViewMode(PickerViewMode::List);
Пакетная операция с векторами var p = new FileOpenPicker{
    FileTypeFilter = { ".png", ".jpg", ".gif" }
};
FileOpenPicker p;
p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" });
Перенос метода CopyButton_Click
Выполнить итерацию по коллекции foreach (var v in c) for (auto&& v : c) Перенос метода BuildClipboardFormatsOutputString
Перехватить исключение catch (Exception ex) catch (winrt::hresult_error const& ex) Перенос метода PasteButton_Click
Сведения об исключении ex.Message ex.message() Перенос метода PasteButton_Click
Получение значения свойства myObject.MyProperty myObject.MyProperty() Перенос метода NotifyUser
Установка значения свойства myObject.MyProperty = value; myObject.MyProperty(value);
Увеличение значения свойства myObject.MyProperty += v; myObject.MyProperty(thing.Property() + v);
Для строк используйте StringBuilder
ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()
Преобразование строки языка в строку среда выполнения Windows N/A winrt::hstring{ s }
Формирование строк StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Строковое построение
Интерполяция строк $"{i++}) {s.Title}" winrt::to_hstring и/или winrt::hstring::operator+ Перенос метода OnNavigatedTo
Пустая строка для сравнения System.String.Empty winrt::hstring::empty Перенос метода UpdateStatus
Создание пустой строки var myEmptyString = String.Empty; winrt::hstring myEmptyString{ L"" };
Операции словаря 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)
Преобразование типов (вызов сбоя) (MyType)v v.as<MyType>() Перенос метода Footer_Click
Преобразование типов (null в случае ошибки) v as MyType v.try_as<MyType>() Перенос метода PasteButton_Click
XAML-элементы с x:Name — это свойства MyNamedElement MyNamedElement() Перенос конструктора, Current и FEATURE_NAME
Переключитесь на поток пользовательского интерфейса CoreDispatcher.RunAsync DispatcherQueue.TryEnqueue или winrt::resume_foreground Перенос метода NotifyUser и перенос метода HistoryAndRoaming
Создание элемента пользовательского интерфейса в императивном коде на странице XAML См. конструкцию элементов пользовательского интерфейса См. конструкцию элементов пользовательского интерфейса

В следующих разделах рассматриваются более подробные сведения о некоторых элементах таблицы.

Создание элемента пользовательского интерфейса

В этих примерах кода показано построение элемента пользовательского интерфейса в императивном коде страницы XAML.

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

ToString()

Типы C# предоставляют метод Object.ToString .

int i = 2;
var s = i.ToString(); // s is a System.String with value "2".

C++/WinRT не предоставляет такой возможности напрямую, но можно воспользоваться альтернативами.

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

C++/WinRT также поддерживает winrt::to_hstring для ограниченного количества типов. Вам потребуется добавить перегрузки для любых дополнительных типов, которые требуется строковать.

Language Преобразовать int в строку Преобразовать перечисление в строку
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);

При преобразовании перечисления в строку необходимо предоставить реализацию 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));
        }
    }
}

Эти строковые представления часто неявно используются механизмом привязки данных.

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

Эти привязки будут выполнять winrt::to_hstring привязанного свойства. В случае второго примера ( StatusEnum) необходимо указать собственную перегрузку winrt::to_hstring, в противном случае вы получите ошибку компилятора.

Также см. Перенос метода Footer_Click.

Формирование строк

Для сборки строк C# имеет встроенный тип StringBuilder .

Category C# C++/WinRT
Формирование строк StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Добавить строку среда выполнения Windows с сохранением нулевых символов builder.Append(s); builder << std::wstring_view{ s };
Добавить новую строку builder.Append(Environment.NewLine); builder << std::endl;
Доступ к результату s = builder.ToString(); ws = builder.str();

Также см. раздел "Перенос метода BuildClipboardFormatsOutputString" и перенос метода DisplayChangedFormats.

Выполнение кода в основном потоке пользовательского интерфейса

Этот пример взят из примера сканера штрихкодов.

Если вы хотите работать с основным потоком пользовательского интерфейса в проекте C#, обычно используется метод DispatcherQueue.TryEnqueue (или более старый CoreDispatcher.RunAsync в UWP). Вот как выглядит шаблон в C#.

private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    DispatcherQueue.TryEnqueue(() =>
    {
        // Do work on the main UI thread here.
    });
}

Гораздо проще выразить это в C++/WinRT. Обратите внимание, что мы принимаем параметры по значению при предположении, что мы хотим получить к ним доступ после первой точки приостановки ( co_awaitв данном случае). Дополнительные сведения см. в разделе "Передача параметров".

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

Если вам нужно выполнить работу по приоритету, отличному от значения по умолчанию, см. функцию winrt::resume_foreground , которая имеет перегрузку, которая принимает приоритет. Примеры кода, показывающие, как ожидать вызова winrt::resume_foreground, см. в статье Программирование с учетом сходства потоков.

Определение классов среды выполнения в IDL

См. IDL для типа MainPage и объедините свои .idl файлы.

Включите необходимые файлы заголовков пространства имён Windows для C++/WinRT

В C++/WinRT каждый раз, когда требуется использовать тип из пространства имён Windows, необходимо подключить соответствующий заголовочный файл пространства имён Windows для C++/WinRT. Пример см. в разделе "Перенос метода NotifyUser".

Бокс и распаковка

C# автоматически упаковывает скаляры в объекты. C++/WinRT требует явного вызова функции winrt::box_value . В обоих языках требуется явная распаковка. См. Боксинг и анбоксинг в C++/WinRT.

В следующих таблицах мы будем использовать эти определения.

C# C++/WinRT
int i; int i;
string s; winrt::hstring s;
object o; IInspectable o;
Operation C# C++/WinRT
Боксинг o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
Распаковки i = (int)o;
s = (string)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

C++/CX и C# генерируют исключение, если вы попытаетесь распаковать нулевой указатель в тип значения. C++/WinRT считает это ошибкой программирования и аварийно завершает работу. В C++/WinRT используйте функцию winrt::unbox_value_or , если вы хотите обработать ситуацию, когда объект не относится к типу, который вы подумали.

Scenario C# C++/WinRT
Распаковать известное целочисленное значение i = (int)o; i = unbox_value<int>(o);
Если o имеет значение NULL System.NullReferenceException Авария
Если o не является упакованным значением типа int System.InvalidCastException Авария
Распаковать int, использовать значение по умолчанию, если null; завершиться с ошибкой во всех остальных случаях i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Если возможно, распакуйте int; для всего остального используйте резервный вариант i = as int? ?? fallback; i = unbox_value_or<int>(o, fallback);

Пример см. в разделе "Перенос метода OnNavigatedTo" и перенос метода Footer_Click.

Бокс и распаковка строки

Строка в некотором смысле является типом значения и другими способами ссылочным типом. C# и C++/WinRT обрабатывают строки по-разному.

Тип ABI HSTRING — это указатель на строку с подсчётом ссылок. Но он не является производным от IInspectable, поэтому это не технически объект. Кроме того, HSTRING со значением null представляет собой пустую строку. Упаковка объектов, не наследуемых от IInspectable, выполняется путем помещения их в оболочку IReference<T>, а среда выполнения Windows предоставляет стандартную реализацию в виде объекта PropertyValue (пользовательские типы обозначаются как PropertyType::OtherType).

C# представляет строку среда выполнения Windows в качестве ссылочного типа. В то время как C++/WinRT проектирует строку как тип значения. Это означает, что прямоугольная строка NULL может иметь различные представления в зависимости от того, как вы там попали.

Behavior C# C++/WinRT
Объявления object o;
string s;
IInspectable o;
hstring s;
Категория строкового типа Тип ссылки Тип значения
null HSTRING проецируется как "" hstring{}
Являются ли значения null и "" идентичны? No Yes
Корректность значения null s = null;
s.Length вызывает NullReferenceException
s = hstring{};
s.size() == 0 (допустимо)
Если вы присваиваете объекту пустую строку o = (string)null;
o == null
o = box_value(hstring{});
o != nullptr
Если вы назначаете "" объекту o = "";
o != null
o = box_value(hstring{L""});
o != nullptr

Базовый бокс и распаковка.

Operation C# C++/WinRT
Упаковать строку o = s;
Пустая строка становится ненулевой объектом.
o = box_value(s);
Пустая строка становится ненулевой объектом.
Распаковать известную строку s = (string)o;
Объект NULL становится пустой строкой.
InvalidCastException, если это не строка.
s = unbox_value<hstring>(o);
Происходит сбой объекта NULL.
Происходит сбой, если значение не является строкой.
Распаковка возможной строки s = o as string;
Объект null или нестроковое значение становится строкой null.

OR

s = o as string ?? fallback;
Значение NULL или нестроковое значение становится резервным значением.
Пустая строка сохранена.
s = unbox_value_or<hstring>(o, fallback);
Значение NULL или нестроковое значение становится резервным значением.
Пустая строка сохранена.

Предоставление классу доступа к расширению разметки {Binding}

Если вы планируете использовать расширение разметки {Binding} для привязки к вашему типу данных, см. объект Binding, объявленный с помощью {Binding}.

Использование объектов, заданных в XAML-разметке

В проекте C# можно использовать закрытые члены и именованные элементы из разметки XAML. Но в C++/WinRT все сущности, потребляемые с помощью расширения разметки XAML {x:Bind} , должны предоставляться публично в IDL.

Кроме того, привязка к логическому значению отображается как true или false в C#, а в C++/WinRT — как Windows.Foundation.IReference`1<Boolean>.

Дополнительные сведения и примеры кода см. в разделе "Использование объектов из разметки".

Обеспечение доступности источника данных для разметки XAML

В C++/WinRT версии 2.0.190530.8 или более поздних версий winrt::single_threaded_observable_vector создает наблюдаемый вектор, который поддерживает как IObservableVector<T>, так и IObservableVector<IInspectable>. Пример см. в разделе "Перенос свойства "Сценарии".

Вы можете создать свой Midl-файл (.idl), например, так (см. также Выделение классов среды выполнения в файлы Midl (.idl)).

namespace Bookstore
{
    runtimeclass BookSku { ... }

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

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

И реализуйте вот так.

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

Дополнительные сведения см. в статьях Элементы управления элементами в XAML; привязка к коллекции C++/WinRT и Коллекции в C++/WinRT.

Предоставление источника данных доступным для разметки XAML (до C++/WinRT 2.0.190530.8)

Привязка данных XAML требует, чтобы источник элементов реализовал IIterable<IInspectable>, а также одну из следующих комбинаций интерфейсов.

  • IObservableVector<IInspectable>
  • IBindableVector и INotifyCollectionChanged
  • IBindableVector и IBindableObservableVector
  • IBindableVector сам по себе (не будет реагировать на изменения)
  • IVector<IInspectable>
  • IBindableIterable (будет итерировать и сохранять элементы в частной коллекции)

Универсальный интерфейс, например IVector<T> , не может быть обнаружен во время выполнения. Каждый IVector<T> имеет другой идентификатор интерфейса (IID), который является функцией T. Любой разработчик может произвольно расширить набор T , поэтому ясно, что код привязки XAML никогда не может знать полный набор запросов. Это ограничение не является проблемой для C#, так как каждый объект CLR, реализующий IEnumerable<T> , автоматически реализует IEnumerable. На уровне ABI, это означает, что каждый объект, реализующий интерфейс IObservableVector<T>, автоматически также реализует IObservableVector<IInspectable>.

C++/WinRT не предлагает эти гарантии. Если класс среды выполнения C++/WinRT реализует IObservableVector<T>, то мы не можем предположить, что реализация IObservableVector<IInspectable> также предоставляется.

Следовательно, вот как должен выглядеть предыдущий пример.

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

И реализация.

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

Если вам нужно получить доступ к объектам в m_bookSkus, вам потребуется вернуться в 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;
}

Производные классы

Чтобы получить производный от класса среды выполнения, базовый класс должен быть компонуемым. C# не требует выполнения каких-либо специальных действий, чтобы сделать классы компонуемыми, но C++/WinRT делает. Чтобы указать, что ваш класс можно использовать в качестве базового класса, используйте ключевое слово unsealed.

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

В файле заголовка для типа реализации необходимо включить файл заголовка базового класса перед включением автогенерированного заголовка для производного класса. В противном случае вы получите такие ошибки, как "Недопустимое использование этого типа в качестве выражения".

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

Важные API