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


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

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

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

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

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

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

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

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

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

Замечание

Если у вас есть проект компонента среды выполнения 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, который еще не перенесен.

Замечание

Как C++/CX, так и windows SDK объявляют типы в корневом пространстве имен 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>Добавить новый элемент...>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.

Также см. классы среды выполнения Factoring в 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 в качестве параметров функции в виде ссылок на 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 и оператор со стрелками (->) для разыменовки переменной шляпы.

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

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

При переносе в эквивалентный код C++/WinRT можно получить длинный путь, удалив шляпы и изменив оператор стрелки (->) на оператор dot (.). Проецируемые типы 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 вызывается функция получения и задания функций.

В следующих примерах 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 — это значение; поэтому его можно выделить в стеке или в виде поля объекта. Вы никогда не используйте ref new (ни new) для выделения объекта C++/WinRT. За кулисами вызывается 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 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# вызывают исключения, если попытаться распаковать null-указатель в значимый тип. 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 не является упакованным 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, поэтому технически не является объектом . Кроме того, null HSTRING представляет пустую строку. Боксирование объектов, не производных от IInspectable, осуществляется путем их упаковки внутри IReference<T>, при этом Windows Runtime предоставляет стандартную реализацию в виде объекта PropertyValue (пользовательские типы передаются как PropertyType::OtherType).

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

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

Поведение C++/CX C++/WinRT
Объявления Object^ o;
String^ s;
IInspectable o;
hstring s;
Категория строкового типа Ссылочный тип Тип значения
NULL используется в проектах HSTRING (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);
Объект NULL вызывает сбой.
Программа аварийно завершится, если входные данные не являются строкой.
Распакуйте возможную строку s = dynamic_cast<String^>(o);
Пустой объект или объект, который не является строкой, становится пустой строкой.
s = unbox_value_or<hstring>(o, fallback);
Значение NULL или не являющееся строкой превращается в резервное значение.
Пустая строка сохранена.

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

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

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

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

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

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

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

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

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

C++/CX C++/WinRT
Платформа Agile^ winrt::agile_ref
Platform::Array^ См. порт Platform::Array^
Платформа::Исключение^ 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 возвращает 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 ОШИБКА_ОТМЕНЕНО
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 Недопустимое назначение делегата
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^ эквивалентен типу ABI среды выполнения Windows HSTRING. Для C++/WinRT эквивалентен winrt::hstring. Но с помощью C++/WinRT можно вызывать API среды выполнения Windows с помощью расширенных типов строк библиотеки C++, таких как std::wstringи /или широкие строковые литералы. Дополнительные сведения и примеры кода см. в обработке строк в C++/WinRT.

С помощью C++/CX можно получить доступ к свойству Platform::String::Data, чтобы извлечь строку в формате массива в стиле C const wchar_t* (например, для передачи его в 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 для ограниченного количества типов. Вам потребуется добавить перегрузки для любых дополнительных типов, которые требуется строковать.

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

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

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