Взаимодействие между C++/WinRT и C++/CX

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

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

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

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

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

Функции from_cx и to_cx

Ниже приведен исходный код файла заголовка с именем interop_helpers.h, содержащий различные вспомогательные функции преобразования. По мере постепенного переноса проекта вы окажетесь в ситуации, когда одни его части остаются в C++, а другие уже перенесены в C++/WinRT. Приведенные далее вспомогательные функции можно использовать, чтобы преобразовывать объекты (и другие типы) C++/CX в объекты C++/WinRT и наоборот в проекте на граничных точках между этими двумя средами.

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

// interop_helpers.h
#pragma once

template <typename T>
T from_cx(Platform::Object^ from)
{
    T to{ nullptr };

    if (from != nullptr)
    {
        winrt::check_hresult(reinterpret_cast<::IUnknown*>(from)
            ->QueryInterface(winrt::guid_of<T>(), winrt::put_abi(to)));
    }

    return to;
}

template <typename T>
T^ to_cx(winrt::Windows::Foundation::IUnknown const& from)
{
    return safe_cast<T^>(reinterpret_cast<Platform::Object^>(winrt::get_abi(from)));
}

inline winrt::hstring from_cx(Platform::String^ const& from)
{
    return reinterpret_cast<winrt::hstring&>(const_cast<Platform::String^&>(from));
}

inline Platform::String^ to_cx(winrt::hstring const& from)
{
    return reinterpret_cast<Platform::String^&>(const_cast<winrt::hstring&>(from));
}

inline winrt::guid from_cx(Platform::Guid const& from)
{
    return reinterpret_cast<winrt::guid&>(const_cast<Platform::Guid&>(from));
}

inline Platform::Guid to_cx(winrt::guid const& from)
{
    return reinterpret_cast<Platform::Guid&>(const_cast<winrt::guid&>(from));
}

Функция from_cx

Вспомогательная функция from_cx преобразовывает объект C++/CX в эквивалентный объект C++/WinRT. Она автоматически приводит объект C++/CX к его базовому указателю интерфейса IUnknown. Затем она вызывает QueryInterface для этого указателя, чтобы запросить интерфейс по умолчанию для объекта C++/WinRT. QueryInterface — это эквивалент расширения safe_cast C++/CX в двоичном интерфейсе приложения (ABI) среды выполнения Windows. Функция winrt::put_abi возвращает адрес базового указателя интерфейса IUnknown объекта C++/WinRT, чтобы для него можно было задать другое значение.

Функция to_cx

Вспомогательная функция to_cx преобразовывает объект C++/WinRT в эквивалентный объект C++/CX. Функция Winrt::get_abi получает указатель на базовый интерфейс IUnknown объекта C++/WinRT. Функция приводит этот указатель к объекту C++/CX перед тем, как использовать расширение C++/CX safe_cast для запроса необходимого типа C++/CX.

interop_helpers.hФайл заголовка

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

  • Добавьте новый элемент Header File (.h) в проект и назовите его interop_helpers.h.
  • Замените содержимое interop_helpers.h кодом из списка выше.
  • Добавьте эти данные в pch.h.
// pch.h
...
#include <unknwn.h>
// Include C++/WinRT projected Windows API headers here.
...
#include <interop_helpers.h>

Создание проекта C++/CX и добавление поддержки C++/WinRT

В этом разделе описывается, что делать, если вы решили добавить к существующему проекту C++/CX поддержку C++/WinRT и выполнить перенос. Также см. сведения в разделе Поддержка Visual Studio для C++/WinRT, XAML, расширения VSIX и пакета NuGet.

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

Сначала откройте свой проект C++/CX в Visual Studio и подтвердите, что для свойства проекта Общие>Версия целевой платформы установлено значение 10.0.17134.0 (Windows 10, версия 1803) или выше.

Установка пакета C++/WinRT NuGet

Пакет NuGet Microsoft.Windows.CppWinRT обеспечивает поддержку сборки C++/WinRT (свойства и цели MSBuild). Для его установки, выберите пункт меню Проект>Управление пакетами NuGet ...>Обзор, введите или вставьте Microsoft.Windows.CppWinRT в поле поиска, выберите элемент в результатах поиска, а затем нажмите кнопку Установить, чтобы установить пакет для этого проекта.

Важно!

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

Как включить поддержку C++/CX

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

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

  <PropertyGroup Label="Globals">
    <CppWinRTProjectLanguage>C++/CX</CppWinRTProjectLanguage>
  </PropertyGroup>

Важно!

Каждый раз, когда необходимо выполнить сборку для обработки содержимого файла Midl File (.idl) в файлы заглушек, язык проекта необходимо будет снова сменить на C++/WinRT. После того, как сборка создала эти заглушки, измените язык проекта обратно на C++/CX.

Список похожих параметров настройки (которые управляют поведением средства cppwinrt.exe) см. в файле сведений пакета NuGet Microsoft.Windows.CppWinRT.

Включение файлов заголовков C++/WinRT

Меньшее, что вы должны сделать, это включить в ваш прекомпилированный файл заголовка (обычно pch.h) winrt/base.h, как показано ниже.

// pch.h
...
#include <winrt/base.h>
...

Но вам почти наверняка понадобятся типы в пространстве имен winrt::Windows::Foundation. И, возможно, вы уже знаете о других пространствах имен, которые могут пригодиться. Поэтому включите проектируемые заголовки C++/WinRT Windows API, которые соответствуют таким пространствам имен, как это (теперь winrt/base.h не нужно добавлять намеренно, потому он добавляется автоматически).

// pch.h
...
#include <winrt/Windows.Foundation.h>
// Include any other C++/WinRT projected Windows API headers here.
...

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

Добавление interop_helpers.h в проект

Теперь вы можете добавить функции from_cx и to_cx в проект C++/CX. Инструкции к выполнению этой команды см. выше в разделе Функции from_cx и to_cx.

Создание проекта C++/WinRT и добавление поддержки C++/CX

В этом разделе описывается, что делать, если вы решили создать новый проект C++/WinRT и выполнить перенос.

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

Пример проекта C++/WinRT с использованием двух вспомогательных функций

В этом разделе можно создать пример проекта C++/WinRT, в котором продемонстрировано использование from_cx и to_cx. В нем также показано, как использовать псевдонимы пространства имен для различных участков кода, чтобы справиться с потенциальными противоречиями пространства имен между проекциями C++/WinRT и C++/CX.

  • Создайте проект Visual C++>Универсальная платформа Windows>Core App (C++/WinRT) (Приложение основных компонентов (C++/WinRT)).
  • В свойствах проекта выберите C/C++ >Общие>Использовать расширение среды выполнения Windows> Да (/ZW).
  • Добавьте interop_helpers.h в проект. Инструкции к выполнению этой команды см. выше в разделе Функции from_cx и to_cx.
  • Замените содержимое App.cpp кодом из листинга ниже.
  • Выполните сборку и запуск.

WINRT_ASSERT — это макроопределение, которое передается в _ASSERTE.

// App.cpp
#include "pch.h"
#include <sstream>

namespace cx
{
    using namespace Windows::Foundation;
}

namespace winrt
{
    using namespace Windows;
    using namespace Windows::ApplicationModel::Core;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Numerics;
    using namespace Windows::UI;
    using namespace Windows::UI::Core;
    using namespace Windows::UI::Composition;
}

struct App : winrt::implements<App, winrt::IFrameworkViewSource, winrt::IFrameworkView>
{
    winrt::CompositionTarget m_target{ nullptr };
    winrt::VisualCollection m_visuals{ nullptr };
    winrt::Visual m_selected{ nullptr };
    winrt::float2 m_offset{};

    winrt::IFrameworkView CreateView()
    {
        return *this;
    }

    void Initialize(winrt::CoreApplicationView const &)
    {
    }

    void Load(winrt::hstring const&)
    {
    }

    void Uninitialize()
    {
    }

    void Run()
    {
        winrt::CoreWindow window = winrt::CoreWindow::GetForCurrentThread();
        window.Activate();

        winrt::CoreDispatcher dispatcher = window.Dispatcher();
        dispatcher.ProcessEvents(winrt::CoreProcessEventsOption::ProcessUntilQuit);
    }

    void SetWindow(winrt::CoreWindow const & window)
    {
        winrt::Compositor compositor;
        winrt::ContainerVisual root = compositor.CreateContainerVisual();
        m_target = compositor.CreateTargetForCurrentView();
        m_target.Root(root);
        m_visuals = root.Children();

        window.PointerPressed({ this, &App::OnPointerPressed });
        window.PointerMoved({ this, &App::OnPointerMoved });

        window.PointerReleased([&](auto && ...)
        {
            m_selected = nullptr;
        });
    }

    void OnPointerPressed(IInspectable const &, winrt::PointerEventArgs const & args)
    {
        winrt::float2 const point = args.CurrentPoint().Position();

        for (winrt::Visual visual : m_visuals)
        {
            winrt::float3 const offset = visual.Offset();
            winrt::float2 const size = visual.Size();

            if (point.x >= offset.x &&
                point.x < offset.x + size.x &&
                point.y >= offset.y &&
                point.y < offset.y + size.y)
            {
                m_selected = visual;
                m_offset.x = offset.x - point.x;
                m_offset.y = offset.y - point.y;
            }
        }

        if (m_selected)
        {
            m_visuals.Remove(m_selected);
            m_visuals.InsertAtTop(m_selected);
        }
        else
        {
            AddVisual(point);
        }
    }

    void OnPointerMoved(IInspectable const &, winrt::PointerEventArgs const & args)
    {
        if (m_selected)
        {
            winrt::float2 const point = args.CurrentPoint().Position();

            m_selected.Offset(
            {
                point.x + m_offset.x,
                point.y + m_offset.y,
                0.0f
            });
        }
    }

    void AddVisual(winrt::float2 const point)
    {
        winrt::Compositor compositor = m_visuals.Compositor();
        winrt::SpriteVisual visual = compositor.CreateSpriteVisual();

        static winrt::Color colors[] =
        {
            { 0xDC, 0x5B, 0x9B, 0xD5 },
            { 0xDC, 0xED, 0x7D, 0x31 },
            { 0xDC, 0x70, 0xAD, 0x47 },
            { 0xDC, 0xFF, 0xC0, 0x00 }
        };

        static unsigned last = 0;
        unsigned const next = ++last % _countof(colors);
        visual.Brush(compositor.CreateColorBrush(colors[next]));

        float const BlockSize = 100.0f;

        visual.Size(
        {
            BlockSize,
            BlockSize
        });

        visual.Offset(
        {
            point.x - BlockSize / 2.0f,
            point.y - BlockSize / 2.0f,
            0.0f,
        });

        m_visuals.InsertAtTop(visual);

        m_selected = visual;
        m_offset.x = -BlockSize / 2.0f;
        m_offset.y = -BlockSize / 2.0f;
    }
};

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    winrt::init_apartment();

    winrt::Uri uri(L"http://aka.ms/cppwinrt");
    std::wstringstream wstringstream;
    wstringstream << L"C++/WinRT: " << uri.Domain().c_str() << std::endl;

    // Convert from a C++/WinRT type to a C++/CX type.
    cx::Uri^ cx = to_cx<cx::Uri>(uri);
    wstringstream << L"C++/CX: " << cx->Domain->Data() << std::endl;
    ::OutputDebugString(wstringstream.str().c_str());

    // Convert from a C++/CX type to a C++/WinRT type.
    winrt::Uri uri_from_cx = from_cx<winrt::Uri>(cx);
    WINRT_ASSERT(uri.Domain() == uri_from_cx.Domain());
    WINRT_ASSERT(uri == uri_from_cx);

    winrt::CoreApplication::Run(winrt::make<App>());
}

Важные API