Поделиться через


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

Совет

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

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

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

Подготовка и предполагаемая работа

Пример переноса примера буфера обмена на 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 для определения классов среды выполнения.

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

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

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

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

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

В C#: (Value)o
C++/WinRT: unbox_value<Value>(o)
Создает исключение, если распаковка-преобразование завершается сбоем. Также см. сведения в статье Упаковка-преобразование и распаковка-преобразование скалярных значений в IInspectable с помощью C++/WinRT.

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

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

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

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

Категория 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> и Добавить, чтобы добавить элемент. IVector<T> и Добавить, чтобы добавить элемент. Если вы используете 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::Windows::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::Windows::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, Windows.UI.Xaml.RoutedEventArgs e);

Примечание.

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

Изменения, затрагивающие синтаксис языка

Категория 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
Метод Fire-and-forget (подразумевает асинхронный) 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

Примечание.

Если файл заголовка не содержит директиву 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.

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

Категория C# C++/WinRT См. также
Управление жизненным циклом в асинхронном методе Н/П 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
Создание объекта в переменной с аргументами 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
Создание объекта в переменной без аргументов 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);
Для строк используйте конструктор
ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()
Строка языка для строки среды выполнения Windows Н/П winrt::hstring{ s }
String-building StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
String-building
Интерполяция строк $"{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 CoreDispatcher.RunAsync или winrt::resume_foreground Перенос методов NotifyUser и HistoryAndRoaming
Создание элемента пользовательского интерфейса в императивном коде на странице XAML См. раздел Создание элемента пользовательского интерфейса. См. раздел Создание элемента пользовательского интерфейса.

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

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

В этих примерах кода показано создание элемента пользовательского интерфейса в императивном коде страницы 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()

Типы 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 для ограниченного числа типов. Вам нужно добавить перегрузки для любых дополнительных типов, к которым необходимо применить метод stringify.

Язык Stringify int Stringify enum
C# string result = "hello, " + intValue.ToString();
string result = $"hello, {intValue}";
string result = "status: " + status.ToString();
string result = $"status: {status}";
C++/WinRT hstring result = L"hello, " + to_hstring(intValue); // must define overload (see below)
hstring result = L"status: " + to_hstring(status);

Если применяется stringify enum, необходимо предоставить реализацию 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.

String-building

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

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

Смотрите также раздел Перенос метода BuildClipboardFormatsOutputString и Перенос метода DisplayChangedFormats

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

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

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

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

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

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

Ознакомьтесь с разделом IDL для типа MainPage и Консолидация .idlфайлов.

Включение нужных файлов заголовков пространства имен Windows C++/WinRT.

Каждый раз в C++/WinRT, когда вы хотите использовать тип из пространств имен Windows, включайте соответствующий файл заголовков пространства имен C++/ WinRT для Windows. Пример см. в разделе Перенос метода 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;
Операция 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.

Сценарий C# C++/WinRT
Распаковка-преобразование известного целого числа i = (int)o; i = unbox_value<int>(o);
Если o имеет значение NULL System.NullReferenceException Сбой
Если o не является упакованным целым числом System.InvalidCastException Сбой
Выполните распаковку-преобразование целого числа, используйте откат при значении NULL; аварийное завершение при других вариантах i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Выполните распаковку-преобразование целого числа, если это возможно; используйте откат при других вариантах 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 может иметь разные представления в зависимости от способа ее получения.

Поведение C# C++/WinRT
Объявления object o;
string s;
IInspectable o;
hstring s;
Категория типа строки Тип ссылки Тип значения
HSTRING со значением NULL проецируется как "" hstring{}
Значение NULL и "" идентичны? No Да
Допустимость значения 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

Базовые операции упаковки-преобразования и распаковки-преобразования.

Операция C# C++/WinRT
Упаковка-преобразование строки o = s;
Пустая строка преобразуется в непустой объект.
o = box_value(s);
Пустая строка преобразуется в непустой объект.
Распаковка-преобразование известной строки s = (string)o;
Пустой объект преобразуется в пустую строку.
InvalidCastException, если не является строкой.
s = unbox_value<hstring>(o);
Сбой пустого объекта.
Сбой, если не является строкой.
Распаковка-преобразование возможной строки s = o as string;
Пустой объект или нестрока преобразуется в пустую строку.

ИЛИ

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

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

См. подробнее об использовании расширения разметки {Binding} для привязки данных к типу данных.

Использование объектов из разметки XAML

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

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

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

Предоставление разметке XAML доступа к источнику данных

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

Вы можете создать файл Midl (.idl) аналогичным образом (см. также раздел о разделении классов среды выполнения на файлы 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; };
    }
}

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

// 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, необходимо применить к ним метод QI в 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 : Windows.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