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

В этой статье указаны все технические особенности переноса исходного кода из проекта C# в его эквивалент в C++/WinRT.This topic comprehensively catalogs the technical details involved in porting the source code in a C# project to its equivalent in C++/WinRT.

Пример переноса одного из образцов приложений универсальной платформы Windows (UWP) см. в статье Перенос примера буфера обмена в C++/WinRT из C# — пример использования.For a case study of porting one of the Universal Windows Platform (UWP) app samples, see the companion topic Porting the Clipboard sample to C++/WinRT from C#. Вы можете получить рекомендации и действия по переносу, следуя пошаговому руководству и выполняя перенос образца.You can gain porting practice and experience by following along with that walkthrough, and porting the sample for yourself as you go.

Подготовка и предполагаемая работаHow to prepare, and what to expect

В этом примере переноса примера буфера обмена в C++/WinRT из C# показаны примеры решений по проектированию программного обеспечения, которые будут приняты при переносе проекта в C++/WinRT.The case study Porting the Clipboard sample to C++/WinRT from C# illustrates examples of the kinds of software design decisions that you'll make while porting a project to C++/WinRT. Поэтому рекомендуется подготовиться к переносу и хорошо понимать работу имеющегося кода.So, it's a good idea to prepare for porting by gaining a solid understanding of how the existing code works. Вы получите хорошее представление о функциональных возможностях приложения, а также о структуре кода. Таким образом решения, которые вы сможете принимать, будут осознанными и продуктивными.That way, you'll get a good overview of the app's functionality, and the code's structure, and then the decisions that you make will always take you forward, and in the right direction.

Изменения, которые следует ожидать от переноса, можно сгруппировать в четыре категории.In terms of what kinds of porting changes to expect, you could group them into four categories.

  • Перенос языковой проекции.Port the language projection. Среда выполнения Windows (WinRT) проецируется на различные языки программирования.The Windows Runtime (WinRT) is projected into various programming languages. Каждая из этих языковых проекций разработана так, чтобы быть идиоматической для рассматриваемых языков программирования.Each of those language projections is designed to feel idiomatic to the programming language in question. Для C# некоторые типы среды выполнения Windows проецируются как типы .NET.For C#, some Windows Runtime types are projected as .NET types. Например, вы будете преобразовывать System.Collections.Generic.IReadOnlyList<T> обратно в Windows.Foundation.Collections.IVectorView<T> .So for example you'll be translating System.Collections.Generic.IReadOnlyList<T> back to Windows.Foundation.Collections.IVectorView<T>. Кроме того, в C# некоторые операции среды выполнения Windows проецируются как удобные функции языка C#.Also in C#, some Windows Runtime operations are projected as convenient C# language features. В качестве примера в C# для регистрации делегатов обработки событий вы используете синтаксис оператора +=.An example is that in C# you use the += operator syntax to register an event-handling delegate. Поэтому вы будете преобразовывать языковые функции, например, обратно в основную выполняемую операцию (в этом примере — регистрация событий).So you'll be translating language features such as that back to the fundamental operation that's being performed (event registration, in this example).
  • Перенос синтаксиса языкаPort language syntax. Многие из этих изменений являются простыми механическими преобразованиями, заменяющими один символ на другой.Many of these changes are simple mechanical transforms, replacing one symbol for another. Например, изменение точки (.) на двойное двоеточие (::).For example, changing dot (.) to double-colon (::).
  • Перенос процедуры языка.Port language procedure. Некоторые из них могут быть простыми и повторяющимися изменениями (например, с myObject.MyProperty на myObject.MyProperty()).Some of these can be simple, repetitive changes (such as myObject.MyProperty to myObject.MyProperty()). Другие нуждаются в более глубоких изменениях (например, перенос процедуры, которая включает использование System.Text.StringBuilder, в процедуру, использующую std::wostringstream).Others need deeper changes (for example, porting a procedure that involves the use of System.Text.StringBuilder to one that involves the use of std::wostringstream).
  • Задачи, связанные с переносом в среде C++/WinRT.Porting-related tasks that are specific to C++/WinRT. Некоторые сведения среды выполнения Windows обрабатываются с помощью C# в фоновом режиме.Certain details of the Windows Runtime are taken care of impliclicly by C#, behind the scenes. В C++/WinRT эти сведения обрабатываются явно.Those details are done explicitly in C++/WinRT. В качестве примера можно использовать файл .idl для определения классов среды выполнения.An example is that you use an .idl file to define your runtime classes.

Оставшаяся часть этого раздела структурирована в соответствии с этой классификацией.The rest of this topic is structured according to that taxonomy.

Изменения, затрагивающие языковую проекциюChanges that involve the language projection

КатегорияCategory C#C# C++/WinRTC++/WinRT См. также статьюSee also
Нетипизированный объектUntyped object object или System.Objectobject, or System.Object Windows::Foundation::IInspectableWindows::Foundation::IInspectable Перенос метода EnableClipboardContentChangedNotificationsPorting the EnableClipboardContentChangedNotifications method
Пространства имен проекцииProjection namespaces using System; using namespace Windows::Foundation;
using System.Collections.Generic; using namespace Windows::Foundation::Collections;
Размер коллекцииSize of a collection collection.Count collection.Size() Перенос метода BuildClipboardFormatsOutputStringPorting the BuildClipboardFormatsOutputString method
Тип стандартной коллекцииTypical collection type IList<T> и Добавить, чтобы добавить элемент.IList<T>, and Add to add an element. IVector<T> и Добавить, чтобы добавить элемент.IVector<T>, and Append to add an element. Если вы используете std::vector в любом месте, используйте push_back, чтобы добавить элемент.If you use a std::vector anywhere, then push_back to add an element.
Тип коллекции, доступной только для чтенияRead-only collection type IReadOnlyList<T> IReadOnlyList<T> IVectorView<T> IVectorView<T> Перенос метода BuildClipboardFormatsOutputStringPorting the BuildClipboardFormatsOutputString method
Делегат обработчика событий как член классаEvent handler delegate as class member myObject.EventName += Handler; token = myObject.EventName({ get_weak(), &Class::Handler }); Перенос метода EnableClipboardContentChangedNotificationsPorting the EnableClipboardContentChangedNotifications method
Отзыв делегата обработчика событийRevoke event handler delegate myObject.EventName -= Handler; myObject.EventName(token); Перенос метода EnableClipboardContentChangedNotificationsPorting the EnableClipboardContentChangedNotifications method
Ассоциативный контейнерAssociative container IDictionary<K, V> IDictionary<K, V> IMap<K, V> IMap<K, V>
Доступ к векторному элементуVector member access x = v[i];
v[i] = x;
x = v.GetAt(i);
v.SetAt(i, x);

Регистрация или отзыв обработчика событийRegister/revoke an event handler

В C++/WinRT есть несколько синтаксических параметров для регистрации или отзыва делегата обработчика событий, как описано в статье Обработка событий с помощью делегатов в C++/WinRT.In C++/WinRT, you have several syntactic options to register/revoke an event handler delegate, as described in Handle events by using delegates in C++/WinRT. Ознакомьтесь также с разделом о переносе метода EnableClipboardContentChangedNotificationsAlso see Porting the EnableClipboardContentChangedNotifications method.

Иногда, например, когда получатель события (объект, обрабатывающий событие) будет уничтожен, необходимо отозвать обработчик событий, чтобы источник события (объект, создающий событие) не вызывал уничтоженный объект.Sometimes, for example when an event recipient (an object handling an event) is about to be destroyed, you'll want to revoke an event handler so that the event source (the object raising the event) doesn't call into a destroyed object. Ознакомьтесь с разделом Отзыв зарегистрированного делегата.See Revoke a registered delegate. В подобных случаях создайте переменную-член event_token для ваших обработчиков событий.In cases like that, create an event_token member variable for your event handlers. Дополнительные сведения см. в разделе о переносе метода EnableClipboardContentChangedNotificationsFor an example, see Porting the EnableClipboardContentChangedNotifications method.

Обработчик событий можно зарегистрировать в разметке XAML.You can also register an event handler in XAML markup.

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

В C# метод OpenButton_Click может быть закрытым, но XAML все равно сможет подключать его к событию ButtonBase.Click, вызываемому OpenButton.In C#, your OpenButton_Click method can be private, and XAML will still be able to connect it to the ButtonBase.Click event raised by OpenButton.

В C++/WinRT метод OpenButton_Click должен быть открытым в вашем типе имплементации, если нужно зарегистрировать его в разметке XAML.In C++/WinRT, your OpenButton_Click method must be public in your implementation type if you want to register it in XAML markup. Если обработчик событий регистрируется только в императивном коде, он не должен быть открытым.If you register an event handler only in imperative code, then the event handler doesn't need to be public.

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 — закрытым.Alternatively, you can make the registering XAML page a friend of your implementation type, and OpenButton_Click private.

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).One final scenario is where the C# project that you're porting binds to the event handler from markup (for more background on that scenario, see Functions in x:Bind).

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

Можно просто сменить такую разметку на более простой вариант Click="OpenButton_Click".You could just change that markup to the more simple Click="OpenButton_Click". Но если вам это важно, разметку можно сохранить без изменений.Or, if you prefer, you can keep that markup as it is. Чтобы нормально поддерживать такой вариант, нужно лишь объявить в IDL обработчик событий.All you have to do to support it is to declare the event handler in IDL.

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

Примечание

Объявите функцию как void, даже если она реализована по шаблону выполнил и забыл.Declare the function as void even if you implement it as Fire and forget.

Изменения, затрагивающие синтаксис языкаChanges that involve the language syntax

КатегорияCategory C#C# C++/WinRTC++/WinRT См. также статьюSee also
Модификаторы доступаAccess modifiers public \<member\> public:
    \<member\>
Перенос метода Button_ClickPorting the Button_Click method
Доступ к элементу данныхAccess a data member this.variable this->variable
Асинхронное действиеAsync action async Task ... IAsyncAction ...
Асинхронная операцияAsync operation async Task<T> ... IAsyncOperation<T> ...
Метод "Выполнил и забыл" (асинхронный)Fire-and-forget method (implies async) async void ... winrt::fire_and_forget ... Перенос метода CopyButton_ClickPorting the CopyButton_Click method
Доступ к константе перечислимого типаAccess an enumerated constant E.Value E::Value Перенос метода DisplayChangedFormatsPorting the DisplayChangedFormats method
Совместное ожиданиеCooperatively wait await ... co_await ... Перенос метода CopyButton_ClickPorting the CopyButton_Click method
Коллекция проецируемых типов как частное полеCollection of projected types as a private field private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); std::vector
<MyNamespace::MyRuntimeClass>
m_myRuntimeClasses;
Создание GUIDGUID construction 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} };
Разделитель пространства именNamespace separator A.B.T A::B::T
NullNull null nullptr Перенос метода UpdateStatusPorting the UpdateStatus method
Получение объекта типаObtain a type object typeof(MyType) winrt::xaml_typename<MyType>() Перенос свойства ScenariosPorting the Scenarios property
Объявление параметра для методаParameter declaration for a method MyType MyType const& Передача параметровParameter-passing
Объявление параметра для асинхронного методаParameter declaration for an async method MyType MyType Передача параметровParameter-passing
Вызов статического методаCall a static method T.Method() T::Method()
СтрокиStrings string или System.Stringstring, or System.String winrt::hstringwinrt::hstring Обработка строк в C++/WinRTString handling in C++/WinRT
Строковый литералString literal "a string literal" L"a string literal" Перенос конструктора Current и FEATURE_NAMEPorting the constructor, Current, and FEATURE_NAME
Выводимый (или выведенный) типInferred (or deduced) type var auto Перенос метода BuildClipboardFormatsOutputStringPorting the BuildClipboardFormatsOutputString method
Директива UsingUsing-directive using A.B.C; using namespace A::B::C; Перенос конструктора Current и FEATURE_NAMEPorting the constructor, Current, and FEATURE_NAME
Буквальный и необработанный строковый литералVerbatim/raw string literal @"verbatim string literal" LR"(raw string literal)" Перенос метода DisplayToastPorting the DisplayToast method

Примечание

Если файл заголовка не содержит директиву using namespace для данного пространства имен, то необходимо полностью уточнить все имена типов этого пространства имен или, по крайней мере, определить их должным образом, чтобы компилятор их нашел.If a header file doesn't contain a using namespace directive for a given namespace, then you'll have to fully-qualify all type names for that namespace; or at least qualify them sufficiently for the compiler to find them. Пример см. в разделе Перенос метода DisplayToastFor an example, see Porting the DisplayToast method.

Перенос классов и членовPorting classes and members

Вам нужно будет решить для каждого типа C#, следует ли переносить его в тип среды выполнения Windows или в обычный класс, структуру, перечисление C++.You'll need to decide, for each C# type, whether to port it to a Windows Runtime type, or to a regular C++ class/struct/enumeration. Дополнительные сведения и подробные примеры, иллюстрирующие принятие этих решений, см. в статье Перенос примера буфера обмена в C++/WinRT из C# —пример использования.For more info, and detailed examples illustrating how to make those decisions, see Porting the Clipboard sample to C++/WinRT from C#.

Свойство C# обычно становится функцией доступа, функцией метода изменения и резервным членом данных.A C# property typically becomes an accessor function, a mutator function, and a backing data member. Дополнительные сведения и пример см. в разделе Перенос свойства IsClipboardContentChangedEnabled.For more info, and an example, see Porting the IsClipboardContentChangedEnabled property.

Нестатические поля следует сделать членами данных вашего типа реализации.For non-static fields, make them data members of your implementation type.

Статическое поле C# становится функцией статического доступа или функцией метода изменения C++/WinRT.A C# static field becomes a C++/WinRT static accessor and/or mutator function. Дополнительные сведения и пример см. в разделе Перенос конструктора, Current и FEATURE_NAME.For more info, and an example, see Porting the constructor, Current, and FEATURE_NAME.

Для функций-членов также необходимо решить, относится ли функция к IDL или является общедоступной или закрытой функцией-членом вашего типа реализации.For member functions, again, you'll need to decide for each one whether or not it belongs in the IDL, or whether it's a public or private member function of your implementation type. Дополнительные сведения и примеры выбора см. в разделе IDL для типа MainPage.For more info, and examples of how to decide, see IDL for the MainPage type.

Перенос разметки XAML и файлов ресурсовPorting XAML markup, and asset files

В статьеПеренос примера буфера обмена в C++/WinRT из C# — пример использования мы могли использовать ту же разметку XAML (включая ресурсы) и файлы ресурсов в проекте C# и C++/WinRT.In the case of Porting the Clipboard sample to C++/WinRT from C#, we were able to use the same XAML markup (including resources) and asset files across the C# and the C++/WinRT project. В некоторых случаях для переноса разметки может потребоваться внести в нее изменения.In some cases, edits to markup will be necessary to achieve that. См. раздел Копирование XAML и стилей, необходимых для завершения переноса MainPage.See Copy the XAML and styles necessary to finish up porting MainPage.

Изменения, затрагивающие процедуры в языкеChanges that involve procedures within the language

КатегорияCategory C#C# C++/WinRTC++/WinRT См. также статьюSee also
Управление жизненным циклом в асинхронном методеLifetime management in an async method Н/ДN/A auto lifetime{ get_strong() }; илиauto lifetime{ get_strong() }; or
auto lifetime = get_strong();
Перенос метода CopyButton_ClickPorting the CopyButton_Click method
ОсвобождениеDisposal using (var t = v) auto t{ v };
t.Close(); // or let wrapper destructor do the work
Перенос метода CopyImagePorting the CopyImage method
Создание объектаConstruct object new MyType(args) MyType{ args } илиMyType{ args } or
MyType(args)
Перенос свойства ScenariosPorting the Scenarios property
Создание неинициализированной ссылкиCreate uninitialized reference MyType myObject; MyType myObject{ nullptr }; илиMyType myObject{ nullptr }; or
MyType myObject = nullptr;
Перенос конструктора Current и FEATURE_NAMEPorting the constructor, Current, and FEATURE_NAME
Создание объекта в переменной с аргументамиConstruct object into variable with args var myObject = new MyType(args); auto myObject{ MyType{ args } }; илиauto myObject{ MyType{ args } }; or
auto myObject{ MyType(args) }; илиauto myObject{ MyType(args) }; or
auto myObject = MyType{ args }; илиauto myObject = MyType{ args }; or
auto myObject = MyType(args); илиauto myObject = MyType(args); or
MyType myObject{ args }; илиMyType myObject{ args }; or
MyType myObject(args);
Перенос метода Footer_ClickPorting the Footer_Click method
Создание объекта в переменной без аргументовConstruct object into variable without args var myObject = new T(); MyType myObject; Перенос метода BuildClipboardFormatsOutputStringPorting the BuildClipboardFormatsOutputString method
Сокращенная форма инициализации объектаObject initialization shorthand var p = new FileOpenPicker{
    ViewMode = PickerViewMode.List
};
FileOpenPicker p;
p.ViewMode(PickerViewMode::List);
Массовая операция с векторамиBulk vector operation var p = new FileOpenPicker{
    FileTypeFilter = { ".png", ".jpg", ".gif" }
};
FileOpenPicker p;
p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" });
Перенос метода CopyButton_ClickPorting the CopyButton_Click method
Итерация коллекцииIterate over collection foreach (var v in c) for (auto&& v : c) Перенос метода BuildClipboardFormatsOutputStringPorting the BuildClipboardFormatsOutputString method
Перехватывание исключенийCatch an exception catch (Exception ex) catch (winrt::hresult_error const& ex) Перенос метода PasteButton_ClickPorting the PasteButton_Click method
Сведения об исключенииException details ex.Message ex.message() Перенос метода PasteButton_ClickPorting the PasteButton_Click method
Получение значения свойстваGet a property value myObject.MyProperty myObject.MyProperty() Перенос метода NotifyUserPorting the NotifyUser method
Задание значения свойстваSet a property value myObject.MyProperty = value; myObject.MyProperty(value);
Увеличение значения свойстваIncrement a property value myObject.MyProperty += v; myObject.MyProperty(thing.Property() + v);
Для строк используйте конструкторFor strings, switch to a builder
ToString()ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()ToString()
Строка языка для строки среды выполнения WindowsLanguage string to Windows Runtime string Н/ДN/A winrt::hstring{ s }
Сборка строкString-building StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
String-buildingString-building
Интерполяция строкString interpolation $"{i++}) {s.Title}" winrt::to_hstring или winrt::hstring::operator+ winrt::to_hstring, and/or winrt::hstring::operator+ Перенос метода OnNavigatedToPorting the OnNavigatedTo method
Пустая строка для сравненияEmpty string for comparison System.String.EmptySystem.String.Empty winrt::hstring::emptywinrt::hstring::empty Перенос метода UpdateStatusPorting the UpdateStatus method
Создание пустой строкиCreate empty string var myEmptyString = String.Empty; winrt::hstring myEmptyString{ L"" };
Операции со словаремDictionary operations 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)
Преобразование типов (вызов при сбое)Type conversion (throw on failure) (MyType)v v.as<MyType>() Перенос метода Footer_ClickPorting the Footer_Click method
Преобразование типов (NULL при сбое)Type conversion (null on failure) v as MyType v.try_as<MyType>() Перенос метода PasteButton_ClickPorting the PasteButton_Click method
Элементы XAML с x:Name — это свойстваXAML elements with x:Name are properties MyNamedElement MyNamedElement() Перенос конструктора Current и FEATURE_NAMEPorting the constructor, Current, and FEATURE_NAME
Переключение в поток пользовательского интерфейсаSwitch to the UI thread CoreDispatcher.RunAsyncCoreDispatcher.RunAsync CoreDispatcher.RunAsync или winrt::resume_foregroundCoreDispatcher.RunAsync, or winrt::resume_foreground Перенос методов NotifyUser и HistoryAndRoamingPorting the NotifyUser method, and Porting the HistoryAndRoaming method
Создание элемента пользовательского интерфейса в императивном коде на странице XAMLUI element construction in imperative code in a XAML page См. раздел Создание элемента пользовательского интерфейса.See UI element construction См. раздел Создание элемента пользовательского интерфейса.See UI element construction

В следующих разделах приводятся более подробные сведения о некоторых элементах в таблице.The following sections go into more detail regarding some of the items in the table.

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

В этих примерах кода показано создание элемента пользовательского интерфейса в императивном коде страницы XAML.These code examples show the construction of a UI element in the imperative code of a XAML page.

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

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

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

C++/ WinRT не предоставляет эту функцию напрямую, но можно обратиться к ее альтернативам.C++/WinRT doesn't directly provide this facility, but you can turn to alternatives.

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

В C++/WinRT также поддерживается winrt::to_hstring для ограниченного числа типов.C++/WinRT also supports winrt::to_hstring for a limited number of types. Вам нужно добавить перегрузки для любых дополнительных типов, к которым необходимо применить метод stringify.You'll need to add overloads for any additional types you want to stringify.

LanguageLanguage Stringify intStringify int Stringify enumStringify enum
C#C# string result = "hello, " + intValue.ToString();
string result = $"hello, {intValue}";
string result = "status: " + status.ToString();
string result = $"status: {status}";
C++/WinRTC++/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.In the case of stringifying an enum, you will need to provide the implementation of 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));
        }
    }
}

Такие операции часто неявно используются привязкой данных.These stringifications are often consumed implicitly by data binding.

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

Эти привязки будут выполнять winrt::to_hstring свойства привязки.These bindings will perform winrt::to_hstring of the bound property. Во втором примере (StatusEnum) необходимо предоставить свою перегрузку winrt::to_hstring, иначе возникнет ошибка компилятора.In the case of the second example (the StatusEnum), you must provide your own overload of winrt::to_hstring, otherwise you'll get a compiler error.

Ознакомьтесь также с разделом о переносе метода Footer_Click.Also see Porting the Footer_Click method.

Сборка строкString-building

Для сборки строк в C# есть встроенный тип StringBuilder.For string building, C# has a built-in StringBuilder type.

КатегорияCategory C#C# C++/WinRTC++/WinRT
Сборка строкString-building StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
Добавление строки среды выполнения Windows с сохранением значений NULLAppend a Windows Runtime string, preserving nulls builder.Append(s); builder << std::wstring_view{ s };
Добавление новой строкиAdd a newline builder.Append(Environment.NewLine); builder << std::endl;
Доступ к результатуAccess the result s = builder.ToString(); ws = builder.str();

Смотрите также раздел Перенос метода BuildClipboardFormatsOutputString и Перенос метода DisplayChangedFormatsAlso see Porting the BuildClipboardFormatsOutputString method, and Porting the DisplayChangedFormats method.

Выполнение кода в основном потоке пользовательского интерфейсаRunning code on the main UI thread

Этот пример взят из примера сканера штрихкодов.This example is taken from the Barcode scanner sample.

Если вы хотите работать в проекте C# с основным потоком пользовательского интерфейса, обычно используется метод CoreDispatcher.RunAsync, как в этом случае.When you want to do work on the main UI thread in a C# project, you typically use the CoreDispatcher.RunAsync method, like this.

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

Гораздо проще выразить это в C++/WinRT.It's much simpler to express that in C++/WinRT. Обратите внимание, что мы принимаем параметры по значению, предполагая, что нам потребуется доступ к ним после первой точки приостановки (в этом случае — co_await).Notice that we're accepting parameters by value on the assumption we'll want to access them after the first suspension point (the co_await, in this case). Дополнительные сведения см. в разделе Передача параметров.For more info, see Parameter-passing.

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 с перегрузкой, которая имеет приоритет.If you need to do the work at a priority other than the default, then see the winrt::resume_foreground function, which has an overload that takes a priority. Примеры кода, в которых показано, как ожидать вызова winrt::resume_foreground, см. в разделе Программирование с учетом сходства потоков.For code examples showing how to await a call to winrt::resume_foreground, see Programming with thread affinity in mind.

Определение классов среды выполнения в IDLDefine your runtime classes in IDL

Ознакомьтесь с разделом IDL для типа MainPage и Консолидация .idlфайлов.See IDL for the MainPage type, and Consolidate your .idl files.

Включение нужных файлов заголовков пространства имен Windows C++/WinRT.Include the C++/WinRT Windows namespace header files that you need

Каждый раз в C++/WinRT, когда вы хотите использовать тип из пространств имен Windows, включайте соответствующий файл заголовков пространства имен C++/ WinRT для Windows.In C++/WinRT, whenever you want to use a type from a Windows namespaces, you need to include the corresponding C++/WinRT Windows namespace header file. Пример см. в разделе Перенос метода NotifyUserFor an example, see Porting the NotifyUser method.

Упаковка-преобразование и распаковка-преобразованиеBoxing and unboxing

В C# автоматически выполняется упаковка-преобразование скалярных значений в объекты.C# automatically boxes scalars into objects. В C++/WinRT необходимо явным образом вызвать функцию winrt::box_value.C++/WinRT requires you to call the winrt::box_value function explicitly. В обоих языках распаковка-преобразование выполняется явным образом.Both languages require you to unbox explicitly. См. подробнее об упаковке-преобразовании и распаковке-преобразовании в C++/WinRT.See Boxing and unboxing with C++/WinRT.

Эти определения используются в следующих таблицах.In the tables that follows, we'll use these definitions.

C#C# C++/WinRTC++/WinRT
int i; int i;
string s; winrt::hstring s;
object o; IInspectable o;
ОперацияOperation C#C# C++/WinRTC++/WinRT
Упаковка-преобразованиеBoxing o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
Распаковка-преобразованиеUnboxing i = (int)o;
s = (string)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

В C++/CX и C# вызываются исключения при попытке распаковки-преобразования пустого указателя в тип значения.C++/CX and C# raise exceptions if you try to unbox a null pointer to a value type. В C++/WinRT это считается ошибкой программирования, которая приводит к аварийному завершению.C++/WinRT considers this a programming error, and it crashes. Если в C++/WinRT объект имеет не тот тип, который предполагался, используйте функцию winrt::unbox_value_or.In C++/WinRT, use the winrt::unbox_value_or function if you want to handle the case where the object is not of the type that you thought it was.

СценарийScenario C#C# C++/WinRTC++/WinRT
Распаковка-преобразование известного целого числаUnbox a known integer i = (int)o; i = unbox_value<int>(o);
Если o имеет значение NULLIf o is null System.NullReferenceException Аварийное завершениеCrash
Если o не является упакованным целым числомIf o is not a boxed int System.InvalidCastException Аварийное завершениеCrash
Выполните распаковку-преобразование целого числа, используйте откат при значении NULL; аварийное завершение при других вариантахUnbox int, use fallback if null; crash if anything else i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Выполните распаковку-преобразование целого числа, если это возможно; используйте откат при других вариантахUnbox int if possible; use fallback for anything else i = as int? ?? fallback; i = unbox_value_or<int>(o, fallback);

Пример см. в разделе Перенос метода OnNavigatedTo и Перенос метода Footer_Click.For an example, see Porting the OnNavigatedTo method, and Porting the Footer_Click method.

Упаковка-преобразование и распаковка-преобразование строкиBoxing and unboxing a string

Строка в некотором роде является и типом значения, и ссылочным типом.A string is in some ways a value type, and in other ways a reference type. В C# и C++/WinRT строки обрабатываются по-разному.C# and C++/WinRT treat strings differently.

Тип ABI HSTRING является указателем на строку с учетом ссылок.The ABI type HSTRING is a pointer to a reference-counted string. Но он не является производным от IInspectable, поэтому с технической точки зрения это не объект.But it doesn't derive from IInspectable, so it's not technically an object. Более того, HSTRING со значением NULL представляет пустую строку.Furthermore, a null HSTRING represents the empty string. Упаковка объектов, которые не унаследованы от IInspectable, выполняется путем их упаковки в IReference<T> . Среда выполнения Windows при этом предоставляет стандартную реализацию в виде объекта PropertyValue (настраиваемые типы отображаются как PropertyType::OtherType).Boxing of things not derived from IInspectable is done by wrapping them inside an IReference<T>, and the Windows Runtime provides a standard implementation in the form of the PropertyValue object (custom types are reported as PropertyType::OtherType).

C# представляет строку среды выполнения Windows в виде ссылочного типа, хотя C++/WinRT проецирует строку в виде типа значения.C# represents a Windows Runtime string as a reference type; while C++/WinRT projects a string as a value type. Это означает, что упакованная строка со значением NULL может иметь разные представления в зависимости от способа ее получения.This means that a boxed null string can have different representations depending how you got there.

ПоведениеBehavior C#C# C++/WinRTC++/WinRT
ОбъявленияDeclarations object o;
string s;
IInspectable o;
hstring s;
Категория типа строкиString type category Ссылочный типReference type Тип значенияValue type
HSTRING со значением NULL проецируется какnull HSTRING projects as "" hstring{}
Значение NULL и "" идентичны?Are null and "" identical? НетNo ДаYes
Допустимость значения NULLValidity of null s = null;
s.Length вызывает NullReferenceExceptions.Length raises NullReferenceException
s = hstring{};
s.size() == 0 (допустимо)s.size() == 0 (valid)
Если присвоить пустую строку объектуIf you assign null string to object o = (string)null;
o == null
o = box_value(hstring{});
o != nullptr
Если присвоить "" объектуIf you assign "" to object o = "";
o != null
o = box_value(hstring{L""});
o != nullptr

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

ОперацияOperation C#C# C++/WinRTC++/WinRT
Упаковка-преобразование строкиBox a string o = s;
Пустая строка преобразуется в непустой объект.Empty string becomes non-null object.
o = box_value(s);
Пустая строка преобразуется в непустой объект.Empty string becomes non-null object.
Распаковка-преобразование известной строкиUnbox a known string s = (string)o;
Пустой объект преобразуется в пустую строку.Null object becomes null string.
InvalidCastException, если не является строкой.InvalidCastException if not a string.
s = unbox_value<hstring>(o);
Сбой пустого объекта.Null object crashes.
Сбой, если не является строкой.Crash if not a string.
Распаковка-преобразование возможной строкиUnbox a possible string s = o as string;
Пустой объект или нестрока преобразуется в пустую строку.Null object or non-string becomes null string.

ИЛИOR

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

Предоставление расширению разметки {Binding} доступа к классуMaking a class available to the {Binding} markup extension

См. подробнее об использовании расширения разметки {Binding} для привязки данных к типу данных.If you intend to use the {Binding} markup extension to data bind to your data type, then see Binding object declared using {Binding}.

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

В проекте C# вы можете использовать закрытые члены и именованные элементы из разметки XAML.In a C# project, you can consume private members and named elements from XAML markup. Но в C++/WinRT все сущности, используемые при применении расширения разметки {x:Bind} , XAML должны быть общедоступными в IDL.But in C++/WinRT, all entities consumed by using the XAML {x:Bind} markup extension must be exposed publicly in IDL.

Кроме того, привязка к логическому значению приводит к отображению true или false в C#, тогда как в C++/WinRT отображается Windows.Foundation.IReference`1<Boolean> .Also, binding to a Boolean displays true or false in C#, but it shows Windows.Foundation.IReference`1<Boolean> in C++/WinRT.

Дополнительные сведения и примеры кода см. в разделе Использование объектов из разметки.For more info, and code examples, see Consuming objects from markup.

Предоставление разметке XAML доступа к источнику данныхMaking a data source available to XAML markup

В C++/WinRT версии 2.0.190530.8 и более поздних winrt::single_threaded_observable_vector создает наблюдаемый вектор, который поддерживает как IObservableVector<T> , так и IObservableVector<IInspectable> .In C++/WinRT version 2.0.190530.8 and higher, winrt::single_threaded_observable_vector creates an observable vector that supports both IObservableVector<T> and IObservableVector<IInspectable>. Пример см. в разделе Перенос свойства ScenariosFor an example, see Porting the Scenarios property.

Вы можете создать файл Midl (.idl) аналогичным образом (см. также раздел о разделении классов среды выполнения на файлы Midl (.idl)).You can author your Midl file (.idl) like this (also see Factoring runtime classes into Midl files (.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; };
    }
}

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

// 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.For more info, see XAML items controls; bind to a C++/WinRT collection, and Collections with C++/WinRT.

Предоставление разметке XAML доступа к источнику данных (в версиях, предшествующих C++/WinRT 2.0.190530.8)Making a data source available to XAML markup (prior to C++/WinRT 2.0.190530.8)

Для привязки данных XAML требуется, чтобы источник элементов реализовывал IIterable<IInspectable> , а также одну из следующих комбинаций интерфейсов.XAML data binding requires that an items source implements IIterable<IInspectable>, as well as one of the following combinations of interfaces.

  • IObservableVector<IInspectable>IObservableVector<IInspectable>
  • IBindableVector и INotifyCollectionChangedIBindableVector and INotifyCollectionChanged
  • IBindableVector и IBindableObservableVectorIBindableVector and IBindableObservableVector
  • IBindableVector самостоятельно (не будет реагировать на изменения)IBindableVector by itself (will not respond to changes)
  • IVector<IInspectable>IVector<IInspectable>
  • IBindableIterable (будет выполнять итерацию и сохранять элементы в закрытую коллекцию)IBindableIterable (will iterate and save elements into a private collection)

Универсальный интерфейс, например IVector<T> , не может обнаруживаться во время выполнения.A generic interface such as IVector<T> can't be detected at runtime. У каждого интерфейса IVector<T> есть свой идентификатор интерфейса (IID), который является функцией T. Разработчики могут расширять набор T произвольным образом, поэтому очевидно, что коду привязки XAML будет неизвестен весь набор для выполнения запросов к нему.Each IVector<T> has a different interface identifier (IID), which is a function of T. Any developer can expand the set of T arbitrarily, so clearly the XAML binding code can never know the full set to query for. Это ограничение не приносит проблем в C#, так как каждый объект CLR, реализующий IEnumerable<T> , автоматически реализует и IEnumerable.That restriction isn't a problem for C# because every CLR object that implements IEnumerable<T> automatically implements IEnumerable. На уровне ABI это означает, что каждый объект, реализующий IObservableVector<T> , автоматически реализует IObservableVector<IInspectable> .At the ABI level, that means that every object that implements IObservableVector<T> automatically implements IObservableVector<IInspectable>.

В C++/WinRT такая возможность не предусмотрена.C++/WinRT doesn't offer that guarantee. Если класс среды выполнения C++/WinRT реализует IObservableVector<T> , нельзя ожидать предоставления реализации IObservableVector<IInspectable> .If a C++/WinRT runtime class implements IObservableVector<T>, then we can't assume that an implementation of IObservableVector<IInspectable> is somehow also provided.

Следовательно, предыдущий пример должен выглядеть так.Consequently, here's how the previous example will need to look.

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

Реализация выполняется следующим образом.And the implementation.

// 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.If you need to access objects in m_bookSkus, then you'll need to QI them back to 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;
}

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

Чтобы создавать производные классы из класса среды выполнения, базовый класс должен быть составным.In order to derive from a runtime class, the base class must be composable. В отличие от C#, в C++/WinRT нужно выполнять отдельные действия по преобразованию классов в составные.C# doesn't require that you take any special steps to make your classes composable, but C++/WinRT does. Используйте ключевое слово unsealed, чтобы указать, что вы хотите использовать класс в виде базового класса.You use the unsealed keyword to indicate that you want your class to be usable as a base class.

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

В файле заголовка типа реализации вам нужно включить файл заголовка базового класса до включения автоматически созданного заголовка для производного класса.In the header file for your implementation type, you must include the base class header file before you include the autogenerated header for the derived class. В противном случае возникнет проблема, например ошибка с сообщением о недопустимом использовании такого типа в виде выражения.Otherwise you'll get errors such as "Illegal use of this type as an expression".

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

Важные APIImportant APIs