Переход на 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, см. в разделе Программирование с учетом сходства потоков.
Задачи, связанные с переносом в среде C++/WinRT
Определение классов среды выполнения в 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>
{
...
}
}