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

Это первый раздел из серии статей, описывающих, как перенести исходный код в C++/CX в его эквивалент в C++/WinRT.

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

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

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

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

Возможность переноса за один проход

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

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

Примечание.

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

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

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

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

Другой вариант — создайте новый проект в Visual Studio, используя один из шаблонов проекта C++/WinRT (см. Поддержка Visual Studio для C++/WinRT, XAML, расширения VSIX и пакета NuGet). А затем добавьте поддержку C++/CX для этого проекта. Действия, которые необходимо выполнить, описаны в статье Взаимодействие между C++/WinRT и C++/CX. Вы можете начать перемещение исходного кода и перенести часть исходного кода из C++/CX в C++/WinRT.

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

Примечание.

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

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

Важно!

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

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

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

  • Вы можете создать новый проект WRC на C++/CX, переместить как можно больше кода C++/CX в этот проект, а затем изменить проект XAML на C++/WinRT.
  • Или создать новый проект WRC на C++/WinRT, оставить проект 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++/WinRT в проект C++/CX) и выполнить постепенный перенос. Или создать новый проект C++/WinRT и добавить к нему поддержку C++/CX (см. Добавление поддержки C++/CX в проект C++/WinRT), переместить файлы и выполнить постепенный перенос.
  • Постепенный перенос проекта 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 означает, что вы должны сделать это.

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

Если вы забыли указать файл заголовка, компиляция выполнится, но компоновщик выдаст ошибки из-за отсутствия классов consume_.

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

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

void LogPresenceRecord(PresenceRecord^ record);

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

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

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

Ссылки переменных и полей

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

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

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

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

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

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

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

TextBlock^ textBlock;

class MyClass
{
    TextBlock^ textBlock;
};

Об эквиваленте в C++/WinRT см. в статье Отложенная инициализация.

Свойства

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

В следующих примерах 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 через дескриптор, обычно именуемый ссылкой с циркумфлексом (^). Создайте новый объект с помощью ключевого слова ref new, которое, в свою очередь, вызывает RoActivateInstance для активации нового экземпляра класса среды выполнения.

using namespace Windows::Storage::Streams;

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

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

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

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

Если инициализация ресурса является затратной, рекомендуется отложить инициализацию до того момента, когда возникнет фактическая необходимость в ресурсе. Как уже было сказано, конструктор по умолчанию для указателя 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++ используют конструктор по умолчанию, и это может привести к непреднамеренному созданию объектов.

Сценарий 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 };

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

Каждый раз при наличии Platform::Array^ (см. раздел Перенос Platform::Array^) в C++/CX вы можете переносить его в std::vector в C++/WinRT (на самом деле в любой непрерывный контейнер), а не оставлять его в виде массива. Выбор 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(Windows::UI::Xaml::DependencyObject^ d, Windows::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(Windows::UI::Xaml::DependencyObject const& d, Windows::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 : 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>
    {
        ...
    }
}

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

Вот типичный пример обработки события в 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;
Операция 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.

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

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

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

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

Параллельная обработка и асинхронные операции

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

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

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

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

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

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

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

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

C++/CX C++/WinRT
Platform::Agile^ winrt::agile_ref
Platform::Array^ См. Перенос 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.

winrt::agile_ref<Windows::UI::Core::CoreWindow> m_window;

Перенос 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 возвращается значение, отличное от S_OK HRESULT. Для 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 ошибки, и функцию message, которая возвращает строковое представление 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::Data, чтобы получить строку в виде массива const wchar_t* языков группы C (например, чтобы передать ее в std::wcout).

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

Чтобы сделать то же самое в C++/WinRT, можно использовать функцию hstring::c_str, чтобы получить версию строки, заканчивающуюся символом 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 для ограниченного числа типов. Вам нужно добавить перегрузки для любых дополнительных типов, к которым необходимо применить метод stringify.

Язык Stringify int Stringify enum
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);

Если применяется 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, иначе возникнет ошибка компилятора.

String-building

В C++/CX и C++/WinRT используется стандартный способ сборки строк — std::wstringstream.

Операция 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, C++/WinRT не может создать Platform::String из 8-битной строки.

Операция 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 с сохранением значений NULL 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 с остановкой при первом значении NULL 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