Переход на C++/WinRT из C++/CX

В этой статье описано, как перенести исходный код в проекте C++/CX в эквивалент C++/WinRT.

Если в проекте также используются типы библиотеки шаблонов C++ для среда выполнения Windows (WRL), см. раздел Переход с WRL на C++/WinRT.

Стратегии переноса

Стоит знать, что перенос кода с C++/CX на C++/WinRT обычно не вызывает сложностей, за исключением перехода от задач библиотеки параллельных шаблонов (PPL) к корутинам. Модели отличаются. Не существует естественного взаимно-однозначного соответствия между задачами PPL и корутинами, и нет простого способа механически портировать код, который работал бы во всех случаях. Сведения об этом конкретном аспекте переноса и о вариантах взаимодействия между двумя моделями см. в статье Асинхронность и взаимодействие между C++/WinRT и C++/CX.

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

Перенос за один проход

Если вы можете перенести весь проект за один этап, то вся необходимая информация будет в этом разделе (и вам не понадобятся разделы interop, которые идут после него). Мы рекомендуем начать создание проекта в Visual Studio с помощью одного из шаблонов проектов C++/WinRT (см. Visual Studio поддержку C++/WinRT). Затем переместите файлы исходного кода в новый проект и перенесите весь исходный код C++/CX в C++/WinRT по мере этого.

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

Note

Если у вас есть проект компонента среда выполнения Windows, перенос в одном проходе является единственным вариантом. Проект компонента среда выполнения Windows, написанный на C++, должен содержать либо весь исходный код C++/CX, либо весь исходный код C++/WinRT. Они не могут сосуществовать в этом типе проекта.

Перенос проекта постепенно

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

Чтобы подготовить проект к постепенному переносу, можно добавить поддержку C++/WinRT в проект C++/CX. Описанные ниже действия описаны в разделе "Создание проекта C++/CX" и добавление поддержки C++/WinRT. После этого можно выполнять перенос постепенно.

Другой вариант — создать проект в Visual Studio с помощью одного из шаблонов проектов C++/WinRT (см. Visual Studio поддержку C++/WinRT). Затем добавьте поддержку C++/CX в этот проект. Описанные действия описаны в разделе "Создание проекта C++/WinRT" и добавление поддержки C++/CX. Затем вы можете начать переносить туда свой исходный код и по ходу работы портировать часть исходного кода с C++/CX на C++/WinRT.

В любом случае вы будете обеспечивать двустороннюю совместимость между вашим кодом C++/WinRT и любым кодом C++/CX, который вы еще не перенесли.

Note

Как C++/CX, так и пакет SDK Windows объявляют типы в корневом пространстве имен Windows. Тип Windows, проецируемый в C++/WinRT, имеет то же полное имя, что и тип Windows, но он помещается в пространство имен winrt C++. Эти разные пространства имен позволяют переносить код с C++/CX на C++/WinRT в удобном для вас темпе.

Постепенный перенос проекта XAML

Это важно

Для проекта, использующего XAML, в любое время все типы страниц XAML должны быть полностью C++/CX или полностью C++/WinRT. Вы по-прежнему можете использовать совместно C++/CX и C++/WinRT за пределами типов страниц XAML в рамках одного проекта (в моделях, моделях представления и в других местах).

В этом сценарии мы рекомендуем следующий порядок действий: создать новый проект C++/WinRT и перенести в него исходный код и разметку из проекта C++/CX. Если все типы страниц XAML являются C++/WinRT, вы можете добавить новые страницы XAML с Project>Add New Item...>Visual C++>Пустая страница (C++/WinRT).

Кроме того, можно использовать компонент среда выполнения Windows (WRC) для вывода кода из проекта XAML C++/CX при его переносе.

  • Вы можете создать новый проект WRC C++/CX, переместить столько кода C++/CX, сколько можно сделать в этом проекте, а затем изменить проект XAML на C++/WinRT.
  • Или можно создать новый проект C++/WinRT WRC, оставить проект XAML как C++/CX и начать перенос C++/CX в C++/WinRT и переместить полученный код из проекта XAML и в проект компонента.
  • Кроме того, в одном решении у вас может быть проект компонента C++/CX наряду с проектом компонента C++/WinRT; из проекта приложения можно добавить ссылки на оба проекта и постепенно переносить код с одного на другой. Опять же, дополнительные сведения об использовании двух языковых проекций в одном проекте см. в разделе "Взаимодействие между C++/WinRT и C++/CX ".

Первые шаги при переносе проекта C++/CX в C++/WinRT

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

  • Перенос за один проход. Создайте проект в Visual Studio с помощью одного из шаблонов проектов C++/WinRT. Переместите файлы из проекта C++/CX в новый проект и перенесите исходный код C++/CX.
  • Постепенный перенос проекта, не использующего XAML. Вы можете добавить поддержку C++/WinRT в свой проект C++/CX (см. Взять проект C++/CX и добавить поддержку C++/WinRT), а затем выполнять перенос постепенно. Вы также можете создать проект C++/WinRT и добавить поддержку C++/CX в него (см. статью "Создание проекта C++/WinRT" и добавление поддержки C++/CX), постепенно перемещать файлы и переносить порт.
  • Поэтапный перенос проекта XAML. Создайте новый проект C++/WinRT, перенесите файлы и постепенно переносите код. В любой момент типы ваших страниц XAML должны быть либо все C++/WinRT, либо все C++/CX.

Остальная часть этого раздела применяется независимо от выбранной стратегии переноса. Он содержит каталог технических сведений, связанных с переносом исходного кода из C++/CX в C++/WinRT. Если вы выполняете перенос постепенно, вам, вероятно, также стоит ознакомиться с Взаимодействием между C++/WinRT и C++/CX и Асинхронностью и взаимодействием между C++/WinRT и C++/CX.

Соглашения об именах файлов

Файлы разметки XAML

Источник файла C++/CX C++/WinRT
Файлы XAML разработчика MyPage.xaml
MyPage.xaml.h
MyPage.xaml.cpp
MyPage.xaml
MyPage.h
MyPage.cpp
MyPage.idl (см. ниже)
Созданные XAML-файлы MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.g.h

Обратите внимание, что C++/WinRT удаляет .xaml из имен файлов *.h и *.cpp.

C++/WinRT добавляет дополнительный файл разработчика, midl-файл (IDL). C++/CX автоматически создает этот файл самостоятельно, добавляя в него все открытые и защищённые члены. В C++/WinRT вы добавляете и создаете файл самостоятельно. Дополнительные сведения, примеры кода и пошаговое руководство о написании IDL см. в статье Элементы управления XAML; привязка к свойству C++/WinRT.

Также см. Вынесение классов среды выполнения в файлы MIDL (.idl)

Классы среды выполнения

C++/CX не накладывает ограничения на имена файлов заголовков; Обычно несколько определений классов среды выполнения помещают в один файл заголовка, особенно для небольших классов. Но C++/WinRT требует, чтобы каждый класс среды выполнения имеет собственный файл заголовка с именем класса.

C++/CX C++/WinRT
Common.h
ref class A { ... }
ref class B { ... }
Common.idl
runtimeclass A { ... }
runtimeclass B { ... }
A.h
namespace implements {
  struct A { ... };
}
B.h
namespace implements {
  struct B { ... };
}

В C++/CX менее распространённым (но всё ещё допустимым) вариантом является использование файлов заголовков с другими именами для настраиваемых элементов управления XAML. Чтобы соответствовать имени класса, необходимо переименовать этот файл заголовка.

C++/CX C++/WinRT
A.xaml
<Page x:Class="LongNameForA" ...>
A.xaml
<Page x:Class="LongNameForA" ...>
A.h
partial ref class LongNameForA { ... }
LongNameForA.h
namespace implements {
  struct LongNameForA { ... };
}

Требования к файлу заголовка

C++/CX не требует включения специальных файлов заголовков, так как он автоматически создает файлы заголовков из .winmd файлов. В C++/CX обычно используют директивы using для пространств имен, к которым обращаются по имени.

using namespace Windows::Media::Playback;

String^ NameOfFirstVideoTrack(MediaPlaybackItem^ item)
{
    return item->VideoTracks->GetAt(0)->Name;
}

Директива using namespace Windows::Media::Playback позволяет писать MediaPlaybackItem без префикса пространства имен. Мы также затронули пространство имен Windows.Media.Core, поскольку item->VideoTracks->GetAt(0) возвращает Windows.Media.Core.VideoTrack. Но нам не нужно было вводить имя VideoTrack в любом месте, поэтому нам не нужна using Windows.Media.Core директива.

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

#include <winrt/Windows.Media.Playback.h>
#include <winrt/Windows.Media.Core.h> // !!This is important!!

using namespace winrt;
using namespace Windows::Media::Playback;

winrt::hstring NameOfFirstVideoTrack(MediaPlaybackItem const& item)
{
    return item.VideoTracks().GetAt(0).Name();
}

С другой стороны, хотя событие MediaPlaybackItem.AudioTracksChanged имеет тип TypedEventHandler<MediaPlaybackItem, Windows. Foundation.Collections.IVectorChangedEventArgs> не нужно включатьwinrt/Windows.Foundation.Collections.h, так как мы не использовали это событие.

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

<!-- MainPage.xaml -->
<Rectangle Height="400"/>

Использование класса Rectangle требует добавления этой директивы include.

// MainPage.h
#include <winrt/Microsoft.UI.Xaml.Shapes.h>

Если вы забудете подключить заголовочный файл, то всё скомпилируется нормально, но вы получите ошибки линковки, поскольку отсутствуют классы consume_.

Передача параметров

При написании исходного кода C++/CX вы передаете типы C++/CX в качестве параметров функции в виде ссылок на hat (^).

void LogPresenceRecord(PresenceRecord^ record);

В C++/WinRT для синхронных функций следует использовать const& параметры по умолчанию. Это позволит избежать копирования и накладных расходов на синхронизацию. Но ваши корутины должны использовать сквозное значение, чтобы обеспечить их захват по значению и избежать проблем со временем существования (дополнительные сведения см. в разделе "Параллелизм" и асинхронные операции с C++/WinRT).

void LogPresenceRecord(PresenceRecord const& record);
IASyncAction LogPresenceRecordAsync(PresenceRecord const record);

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

Ссылки на переменные и поля

При написании исходного кода C++/CX вы используете hat-переменные (^) для ссылки на объекты среды выполнения Windows, а оператор стрелки (->) — для разыменования hat-переменной.

IVectorView<User^>^ userList = User::Users;

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList->Size; ++iUser)
    ...

При переносе на эквивалентный код C++/WinRT многого можно добиться, просто удалив символы hat и заменив оператор стрелки (->) оператором точки (.). Проецируемые типы C++/WinRT — это значения, а не указатели.

IVectorView<User> userList = User::Users();

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList.Size(); ++iUser)
    ...

Конструктор по умолчанию для ссылки типа hat в C++/CX инициализирует её значением null. Ниже приведен пример кода C++/CX, в котором мы создадим переменную или поле правильного типа, но неинициализированную. Другими словами, изначально это не содержит ссылки на TextBlock; позже мы намерены присвоить ссылку.

TextBlock^ textBlock;

class MyClass
{
    TextBlock^ textBlock;
};

Эквивалент в C++/WinRT см. в разделе "Отложенная инициализация".

Properties

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

В следующих примерах xboxUserId, UserState, PresenceDeviceRecords и Size являются всеми свойствами.

Получение значения из свойства

Вот как получить значение свойства в C++/CX.

void Sample::LogPresenceRecord(PresenceRecord^ record)
{
    auto id = record->XboxUserId;
    auto state = record->UserState;
    auto size = record->PresenceDeviceRecords->Size;
}

Эквивалентный исходный код C++/WinRT вызывает функцию с тем же именем, что и свойство, но без параметров.

void Sample::LogPresenceRecord(PresenceRecord const& record)
{
    auto id = record.XboxUserId();
    auto state = record.UserState();
    auto size = record.PresenceDeviceRecords().Size();
}

Обратите внимание, что функция PresenceDeviceRecords возвращает объект среда выполнения Windows, имеющий функцию Size. Так как возвращаемый объект также является проецируемым типом C++/WinRT, мы обращаемся к Size с помощью оператора точки.

Задание свойства новому значению

Установка свойства на новое значение следует аналогичному шаблону. Во-первых, в C++/CX.

record->UserState = newValue;

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

record.UserState(newValue);

Создание экземпляра класса

Вы работаете с объектом C++/CX через ссылку на него, обычно называемую hat-ссылкой (^). Вы создаете новый объект с помощью ref new ключевого слова, которое, в свою очередь, вызывает RoActivateInstance для активации нового экземпляра класса среды выполнения.

using namespace Windows::Storage::Streams;

class Sample
{
private:
    Buffer^ m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
};

Объект C++/WinRT — это значение; поэтому его можно выделить в стеке или в виде поля объекта. Вы никогда не используете ref new (или) newдля выделения объекта C++/WinRT. В фоновом режиме RoActivateInstance по-прежнему вызывается.

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
private:
    Buffer m_gamerPicBuffer{ MAX_IMAGE_SIZE };
};

Если ресурс является дорогостоящим для инициализации, обычно откладывать инициализацию его до тех пор, пока он не потребуется. Как уже упоминалось, конструктор по умолчанию ссылки hat в C++/CX инициализирует её значением null.

using namespace Windows::Storage::Streams;

class Sample
{
public:
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer^ m_gamerPicBuffer;
};

Тот же код, перенесенный в C++/WinRT. Обратите внимание на использование конструктора std::nullptr_t . Дополнительные сведения об этом конструкторе см. в разделе "Отложенная инициализация".

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer m_gamerPicBuffer{ nullptr };
};

Как конструктор по умолчанию влияет на коллекции

Типы коллекций в C++ используют конструктор по умолчанию, что может привести к непреднамеренному созданию объектов.

Scenario C++/CX C++/WinRT (неверно) C++/WinRT (правильно)
Локальная переменная, изначально пустая TextBox^ textBox; TextBox textBox; // Creates a TextBox! TextBox textBox{ nullptr };
Переменная-член, первоначально пустая class C {
  TextBox^ textBox;
};
class C {
  TextBox textBox; // Creates a TextBox!
};
class C {
  TextBox textbox{ nullptr };
};
Глобальная переменная, изначально пустая TextBox^ g_textBox; TextBox g_textBox; // Creates a TextBox! TextBox g_textBox{ nullptr };
Вектор пустых ссылок std::vector<TextBox^> boxes(10); // Creates 10 TextBox objects!
std::vector<TextBox> boxes(10);
std::vector<TextBox> boxes(10, nullptr);
Установка значения в карте std::map<int, TextBox^> boxes;
boxes[2] = value;
std::map<int, TextBox> boxes;
// Creates a TextBox at 2,
// then overwrites it!
boxes[2] = value;
std::map<int, TextBox> boxes;
boxes.insert_or_assign(2, value);
Массив пустых ссылок TextBox^ boxes[2]; // Creates 2 TextBox objects!
TextBox boxes[2];
TextBox boxes[2] = { nullptr, nullptr };
Сопрячь std::pair<TextBox^, String^> p; // Creates a TextBox!
std::pair<TextBox, String> p;
std::pair<TextBox, String> p{ nullptr, nullptr };

Дополнительные сведения о коллекциях пустых ссылок

Всякий раз, когда в C++/CX у вас есть Platform::Array^ (см. Перенос Platform::Array^), вы можете при переносе на C++/WinRT заменить его на std::vector (или, фактически, на любой контейнер с непрерывным размещением элементов), а не оставлять в виде массива. Есть преимущества выбора std::vector.

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

Вектор можно заполнить пустыми ссылками при инициализации (как в таблице выше) или после инициализации — с помощью, например, такого кода.

std::vector<TextBox> boxes(10); // 10 default-constructed TextBoxes.
boxes.resize(10, nullptr); // 10 empty references.

Дополнительные сведения о примере std::map

Оператор [] подстрока для std::map ведет себя так.

  • Если ключ найден на карте, верните ссылку на существующее значение (которое можно перезаписать).
  • Если ключ не найден в отображении, создайте в нём новую запись, состоящую из ключа (перемещённого, если возможно перемещение) и значения, сконструированного по умолчанию, и верните ссылку на это значение (которое затем можно перезаписать).

Другими словами, [] оператор всегда создает запись в карте. Это отличается от C#, Java и JavaScript.

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

Обычно имеется ссылка на базовый класс, которая, как вам известно, указывает на объект производного класса. В C++/CX вы используете dynamic_cast для приведения ссылки на базовый класс к ссылке на производный класс. dynamic_cast — это на самом деле всего лишь скрытый вызов QueryInterface. Ниже приведен типичный пример: вы обрабатываете событие изменения свойства зависимостей, и вы хотите выполнить приведение из DependencyObject обратно к фактическому типу, которому принадлежит свойство зависимости.

void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject^ d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs^ e)
{
    BgLabelControl^ theControl{ dynamic_cast<BgLabelControl^>(d) };

    if (theControl != nullptr)
    {
        // succeeded ...
    }
}

Эквивалентный код C++/WinRT заменяет dynamic_cast на вызов функции IUnknown::try_as, которая инкапсулирует QueryInterface. Кроме того, у вас есть возможность вызвать IUnknown::as, что создает исключение, если запрос на обязательный интерфейс (интерфейс по умолчанию запрашиваемого типа) не возвращается. Ниже приведен пример кода C++/WinRT.

void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const& d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
    {
        // succeeded ...
    }

    try
    {
        BgLabelControlApp::BgLabelControl theControl{ d.as<BgLabelControlApp::BgLabelControl>() };
        // succeeded ...
    }
    catch (winrt::hresult_no_interface const&)
    {
        // failed ...
    }
}

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

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

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

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

// DerivedPage.h
#include "BasePage.h"       // This comes first.
#include "DerivedPage.g.h"  // Otherwise this header file will produce an error.

namespace winrt::MyNamespace::implementation
{
    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        ...
    }
}

Обработка событий с помощью делегата

Ниже приведен типичный пример обработки события в C++/CX с помощью лямбда-функции в качестве делегата в данном случае.

auto token = myButton->Click += ref new RoutedEventHandler([=](Platform::Object^ sender, RoutedEventArgs^ args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

Это эквивалентно в C++/WinRT.

auto token = myButton().Click([=](IInspectable const& sender, RoutedEventArgs const& args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

Вместо лямбда-функции вы можете реализовать делегат как обычную функцию либо как указатель на функцию-член. Дополнительные сведения см. в разделе "Обработка событий с помощью делегатов в C++/WinRT".

Если вы переносите код из кодовой базы C++/CX, где события и делегаты используются внутри приложения (не между двоичными модулями), то winrt::delegate поможет вам воспроизвести этот шаблон в C++/WinRT. См. также параметризованные делегаты, простые сигналы и обратные вызовы в проекте.

Отзыв делегата

В C++/CX оператор используется -= для отмены предыдущей регистрации событий.

myButton->Click -= token;

Это эквивалентно в C++/WinRT.

myButton().Click(token);

Дополнительные сведения и параметры см. в разделе "Отзыв зарегистрированного делегата".

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

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

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

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

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

Scenario C++/CX C++/WinRT
Распаковать известное целочисленное значение i = (int)o; i = unbox_value<int>(o);
Если o имеет значение NULL Platform::NullReferenceException Авария
Если o не является упакованным значением типа int Platform::InvalidCastException Авария
Распаковать int, использовать значение по умолчанию, если null; завершиться с ошибкой во всех остальных случаях i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
Если возможно, распакуйте int; для всего остального используйте резервный вариант auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

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

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

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

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

Кроме того, C++/CX позволяет разыменовывать значение NULL String^, в этом случае оно ведет себя как строка "".

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

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

Operation C++/CX C++/WinRT
Упаковать строку o = s;
Пустая строка становится nullptr.
o = box_value(s);
Пустая строка становится ненулевой объектом.
Распаковать известную строку s = (String^)o;
Пустой объект становится пустой строкой.
InvalidCastException, если это не строка.
s = unbox_value<hstring>(o);
Происходит сбой объекта NULL.
Происходит сбой, если значение не является строкой.
Распаковка возможной строки s = dynamic_cast<String^>(o);
Нулевой объект или нестроковое значение становится пустой строкой.
s = unbox_value_or<hstring>(o, fallback);
Значение NULL или нестроковое значение становится резервным значением.
Пустая строка сохранена.

Конкурентность и асинхронные операции

Библиотека параллельных шаблонов (PPL) (concurrency::task, например) была обновлена для поддержки ссылок hat C++/CX.

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

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

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

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

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

Сопоставление типов платформ C++/CX с типами C++/WinRT

C++/CX предоставляет несколько типов данных в пространстве имен платформы . Эти типы не являются стандартными типами C++, поэтому их можно использовать только если включены языковые расширения среда выполнения Windows (в свойстве проекта Visual Studio C/C++>General>Consume среда выполнения Windows Extension>Yes (/ZW)). В таблице ниже показано, как перенести типы Platform к их эквивалентам в C++/WinRT. После того как вы это сделаете, поскольку C++/WinRT — это стандартный C++, вы можете отключить параметр /ZW.

C++/CX C++/WinRT
Платформа::Agile^ winrt::agile_ref
Platform::Array^ См. Port Platform::Array^
Platform::Exception^ winrt::hresult_error
Platform::InvalidArgumentException^ winrt::hresult_invalid_argument
Platform::Object^ winrt::Windows::Foundation::IInspectable
Platform::String^ winrt::hstring

Перенести Platform::Agile^ на winrt::agile_ref

Тип Platform::Agile^ в C++/CX представляет класс среда выполнения Windows, к которому можно получить доступ из любого потока. Эквивалент C++/WinRT — winrt::agile_ref.

В C++/CX.

Platform::Agile<Windows::UI::Core::CoreWindow> m_window;

В C++/WinRT (WinUI 3 использует Microsoft::UI::Xaml::Window вместо CoreWindow).

winrt::agile_ref<Microsoft::UI::Xaml::Window> m_window;

Port Platform::Array^

В случаях, когда C++/CX требует использования массива, C++/WinRT позволяет использовать любой смежный контейнер. Ознакомьтесь с тем, как конструктор по умолчанию влияет на коллекции по причине, почему std::vector является хорошим выбором.

Итак, всякий раз, когда у вас есть Platform::Array^ в C++/CX, среди вариантов переноса — использование списка инициализации, std::array или std::vector. Дополнительные сведения и примеры кода см. в Стандартные списки инициализаторов и Стандартные массивы и векторы.

Перенести Platform::Exception^ в winrt::hresult_error

Тип Platform::Exception^ создаётся в C++/CX, когда API среда выполнения Windows возвращает значение HRESULT, отличное от S_OK. Эквивалент C++/WinRT — winrt::hresult_error.

Чтобы перенести в C++/WinRT, измените весь код, использующий Platform::Exception^ для использования winrt::hresult_error.

В C++/CX.

catch (Platform::Exception^ ex)

В C++/WinRT.

catch (winrt::hresult_error const& ex)

C++/WinRT предоставляет эти классы исключений.

Тип исключения Базовый класс HRESULT
winrt::hresult_error вызов hresult_error::to_abi
winrt::hresult_access_denied winrt::hresult_error E_ACCESSDENIED
winrt::hresult_canceled winrt::hresult_error ERROR_CANCELLED
winrt::hresult_changed_state winrt::hresult_error E_CHANGED_STATE
winrt::hresult_class_not_available winrt::hresult_error CLASS_E_CLASSNOTAVAILABLE
winrt::hresult_illegal_delegate_assignment winrt::hresult_error E_ILLEGAL_DELEGATE_ASSIGNMENT
winrt::hresult_illegal_method_call winrt::hresult_error E_ILLEGAL_METHOD_CALL
winrt::hresult_illegal_state_change winrt::hresult_error E_ILLEGAL_STATE_CHANGE
winrt::hresult_invalid_argument winrt::hresult_error E_INVALIDARG
winrt::hresult_no_interface winrt::hresult_error E_NOINTERFACE
winrt::hresult_not_implemented winrt::hresult_error E_NOTIMPL
winrt::hresult_out_of_bounds winrt::hresult_error E_BOUNDS
winrt::hresult_wrong_thread winrt::hresult_error RPC_E_WRONG_THREAD

Обратите внимание, что каждый класс (через базовый класс hresult_error ) предоставляет функцию to_abi , которая возвращает HRESULT ошибки и функцию сообщения , которая возвращает строковое представление этого HRESULT.

Ниже приведен пример возникновения исключения в C++/CX.

throw ref new Platform::InvalidArgumentException(L"A valid User is required");

И эквивалент в C++/WinRT.

throw winrt::hresult_invalid_argument{ L"A valid User is required" };

Перенести Platform::Object^ на winrt::Windows::Foundation::IInspectable

Как и все типы C++/WinRT, winrt::Windows::Foundation::IInspectable является типом значения. Вот как инициализировать переменную этого типа до null.

winrt::Windows::Foundation::IInspectable var{ nullptr };

Перенести Platform::String^ на winrt::hstring

Platform::String^ эквивалентен типу HSTRING ABI среды выполнения Windows. Для C++/WinRT эквивалентен winrt::hstring. Но с помощью C++/WinRT можно вызывать API-интерфейсы среда выполнения Windows с помощью типов строк стандартной библиотеки C++, таких как std::wstring и /или широкие строковые литералы. Дополнительные сведения и примеры кода см. в разделе "Обработка строк" в C++/WinRT.

С помощью C++/CX можно получить доступ к свойству Platform::String::D ata , чтобы получить строку в виде массива const wchar_t* в стиле C (например, чтобы передать его в std::wcout).

auto var{ titleRecord->TitleName->Data() };

Чтобы сделать то же самое с C++/WinRT, можно использовать функцию hstring::c_str , чтобы получить строку в стиле C, завершающуюся null, так же, как и из std::wstring.

auto var{ titleRecord.TitleName().c_str() };

Когда речь идёт о реализации API, принимающих строки или возвращающих их, обычно изменяют любой код C++/CX, использующий Platform::String^, заменяя его на winrt::hstring.

Ниже приведен пример API C++/CX, который принимает строку.

void LogWrapLine(Platform::String^ str);

Для C++/WinRT можно объявить этот API в MIDL 3.0, например, так.

// LogType.idl
void LogWrapLine(String str);

Цепочка инструментов C++/WinRT создаст исходный код для вас, который выглядит следующим образом.

void LogWrapLine(winrt::hstring const& str);

ToString()

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

int i{ 2 };
auto s{ i.ToString() }; // s is a Platform::String^ with value L"2".

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

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

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

Language Преобразовать int в строку Преобразовать перечисление в строку
C++/CX String^ result = "hello, " + intValue.ToString(); String^ result = "status: " + status.ToString();
C++/WinRT hstring result = L"hello, " + to_hstring(intValue); // must define overload (see below)
hstring result = L"status: " + to_hstring(status);

При преобразовании перечисления в строку необходимо предоставить реализацию winrt::to_hstring.

namespace winrt
{
    hstring to_hstring(StatusEnum status)
    {
        switch (status)
        {
        case StatusEnum::Success: return L"Success";
        case StatusEnum::AccessDenied: return L"AccessDenied";
        case StatusEnum::DisabledByPolicy: return L"DisabledByPolicy";
        default: return to_hstring(static_cast<int>(status));
        }
    }
}

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

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

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

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

C++/CX и C++/WinRT полагаются на стандартный std::wstringstream для построения строк.

Operation C++/CX C++/WinRT
Добавление строки, сохранение значений NULL stream.print(s->Data(), s->Length); stream << std::wstring_view{ s };
Добавление строки, остановка при первом значении NULL stream << s->Data(); stream << s.c_str();
Извлечение результата ws = stream.str(); ws = stream.str();

Дополнительные примеры

В приведенных ниже примерах ws представляет собой переменную типа std::wstring. Кроме того, в то время как C++/CX может создавать платформу::String из 8-разрядной строки, C++/WinRT этого не делает.

Operation C++/CX C++/WinRT
Создание строки из литерала String^ s = "hello";
String^ s = L"hello";
// winrt::hstring s{ "hello" }; // Doesn't compile
winrt::hstring s{ L"hello" };
Преобразовать из std::wstring с сохранением нулевых символов String^ s = ref new String(ws.c_str(),
  (uint32_t)ws.size());
winrt::hstring s{ ws };
s = winrt::hstring(ws);
// s = ws; // Doesn't compile
Преобразование из std::wstring, остановка на первом нулевом символе String^ s = ref new String(ws.c_str()); winrt::hstring s{ ws.c_str() };
s = winrt::hstring(ws.c_str());
// s = ws.c_str(); // Doesn't compile
Преобразование в std::wstring, сохранение значений NULL std::wstring ws{ s->Data(), s->Length };
ws = std::wstring(s>Data(), s->Length);
std::wstring ws{ s };
ws = s;
Преобразование в std::wstring, остановка при первом значении NULL std::wstring ws{ s->Data() };
ws = s->Data();
std::wstring ws{ s.c_str() };
ws = s.c_str();
Передать литерал методу Method("hello");
Method(L"hello");
// Method("hello"); // Doesn't compile
Method(L"hello");
Передача std::wstring методу Method(ref new String(ws.c_str(),
  (uint32_t)ws.size()); // Stops on first null
Method(ws);
// param::winrt::hstring accepts std::wstring_view

Важные API

Note

Многие разделы C++/WinRT находятся в процессе переноса из документации по UWP в этот раздел. Пока миграция не завершится, ссылки в приведенном ниже списке могут привести к разделу документации UWP. Проекция языка C++/WinRT одинакова для приложений UWP и WinUI 3, поэтому содержимое применимо в обоих контекстах. Все шаблоны, относящиеся к UWP (например, жизненный цикл приложения или Windows.UI API пространства имен), явно отмечены в этих статьях.