Interoperabilidade entre C++/WinRT e C++/CX

Antes de ler este tópico, você precisará das informações no tópico Mover do C++/CX para o C++/WinRT. Esse tópico apresenta duas opções principais de estratégia para portar seu projeto C++/CX para C++/WinRT.

  • Portar todo o projeto em uma passagem. A opção mais simples para um projeto que não é muito grande. Se você tiver um projeto de componente do Windows Runtime, essa estratégia será sua única opção.
  • Porte o projeto gradualmente (o tamanho ou a complexidade de sua base de código pode tornar isso necessário). Contudo, essa estratégia convida você a seguir um processo de portabilidade no qual, por um momento, os códigos C++/CX e C++/WinRT coexistem no mesmo projeto. Para um projeto XAML, a qualquer momento, os tipos de página XAML devem estar ou inteiramente em C++/WinRT ou inteiramente em C++/CX.

Esse tópico de interoperabilidade é relevante para essa segunda estratégia – para os casos em que você precisa portar seu projeto gradualmente. Este tópico apresenta várias formas de pares de funções auxiliares que você pode usar para converter um objeto C++/CX (e outros tipos) em um objeto C++/WinRT (e vice-versa) dentro do mesmo projeto.

Essas funções auxiliares serão muito úteis enquanto você portar seu código gradualmente de C++/CX para C++/WinRT. Ou você pode simplesmente optar por usar as projeções de linguagem C++/WinRT e C++/CX no mesmo projeto, esteja você portando ou não, e usar essas funções auxiliares para interoperar entre as duas.

Depois de ler este tópico, para obter informações e exemplos de código que mostram como dar suporte a tarefas PPL e corrotinas lado a lado no mesmo projeto (por exemplo, chamar corrotinas de cadeias de tarefas), confira o tópico mais avançado Assincronia e interoperabilidade entre C++/WinRT e C++/CX.

As funções from_cx e to_cx

Esta é uma listagem de código-fonte de um arquivo de cabeçalho chamado interop_helpers.h, que contém várias funções auxiliares de conversão. Conforme você gradualmente portar seu projeto, haverá partes ainda em C++/CX e as partes que você tiver portado para C++/WinRT. Você pode usar essas funções auxiliares para converter objetos (e outros tipos) de/em C++/CX e C++/WinRT em seu projeto nos pontos de limite entre essas duas partes.

As seções após a listagem do código explicam as funções auxiliares e como criar e usar o arquivo de cabeçalho em seu projeto.

// 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));
}

A função from_cx

A função auxiliar from_cx converte um objeto de C++/CX em um objeto de C++/WinRT equivalente. A função converte um objeto de C++/CX em seu ponteiro de interface IUnknown subjacente. Em seguida, chama QueryInterface nesse ponteiro para consultar a interface padrão do objeto de C++/WinRT. A QueryInterface é o equivalente da ABI (interface binária de aplicativo) do Windows Runtime da extensão safe_cast do C++/CX. A função winrt::put_abi recupera o endereço do ponteiro de interface IUnknown subjacente do objeto de C++/WinRT para que ele possa ser definido com outro valor.

A função to_cx

A função auxiliar to_cx converte um objeto de C++/WinRT em um objeto de C++/CX equivalente. A função winrt::get_abi recupera um ponteiro para a interface IUnknown subjacente de um objeto de C++/WinRT. A função converte esse ponteiro em um objeto de C++/CX antes de usar a extensão safe_cast do C++/CX para consultar o tipo de C++/CX solicitado.

O arquivo de cabeçalho interop_helpers.h

Para usar as funções auxiliares em seu projeto, siga estas etapas.

  • Adicione um novo item Arquivo de Cabeçalho (.h) ao seu projeto e nomeie-o como interop_helpers.h.
  • Substitua o conteúdo de interop_helpers.h pela listagem de códigos acima.
  • Adicione essas inclusões a pch.h.
// pch.h
...
#include <unknwn.h>
// Include C++/WinRT projected Windows API headers here.
...
#include <interop_helpers.h>

Adicionar suporte do C++/WinRT a um projeto C++/CX

Esta seção descreve o que fazer se você decidiu usar adicionar suporte do C++/WinRT ao seu projeto C++/CX existente e fazer o trabalho de portabilidade lá. Confira também Suporte do Visual Studio para C++/WinRT.

Para combinar o C++/CX e o C++/WinRT em um projeto C++/CX – incluindo o uso das funções auxiliares from_cx e to_cx no projeto – você precisará adicionar manualmente o suporte do C++/WinRT ao projeto.

Primeiro, abra seu projeto C++/CX no Visual Studio e confirme que a propriedade do projeto Geral>Versão da Plataforma de Destino está definida como 10.0.17134.0 (Windows 10, versão 1803) ou posterior.

Instalar o pacote NuGet do C++/WinRT

O pacote NuGet Microsoft.Windows.CppWinRT fornece suporte ao build do C++/WinRT (propriedades e destinos do MSBuild). Para instalá-lo, clique no item de menu Projeto>Gerenciar Pacotes NuGet...>Procurar, digite ou cole Microsoft.Windows.CppWinRT na caixa de pesquisa, selecione o item nos resultados da pesquisa e, em seguida, clique em Instalar para instalar o pacote para esse projeto.

Importante

A instalação do pacote NuGet do C++/WinRT faz o suporte ao C++/CX ser desativado no projeto. Se você vai portar em uma passagem, convém deixar esse suporte desativado para que as mensagens de build ajudem você a encontrar (e portar) todas as suas dependências no C++/CX (eventualmente, transformando o que era um projeto C++/CX puro em um projeto C++/WinRT puro). No entanto, confira a próxima seção para obter informações sobre como ativá-lo novamente.

Ativar o suporte do C++/CX novamente

Se você estiver portando em uma passagem, não precisará fazer isso. Mas se você precisar portar gradualmente, então, neste ponto, você precisará ativar C++/CX novamente em seu projeto. Nas propriedades do projeto, C/C++>Geral>Consumir Extensão do Windows Runtime>Sim (/ZW).

Como alternativa (ou, para um projeto XAML, além disso), você pode adicionar o suporte do C++/CX usando a página de propriedades do projeto C++/WinRT no Visual Studio. Nas propriedades do projeto, Propriedades Comuns>C++/WinRT>Linguagem do Projeto >C++/CX. Fazer isso adicionará a propriedade a seguir ao arquivo .vcxproj.

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

Importante

Sempre que precisar criar para processar o conteúdo de um Arquivo Midl (.idl) em arquivos stub, você precisará alterar a Linguagem do Projeto de volta para C++/WinRT. Depois que o build gerar esses stubs, altere a Linguagem do Projeto de volta para C++/CX.

Para obter uma lista de opções de personalização semelhantes (que ajustam o comportamento da ferramenta cppwinrt.exe), confira o arquivo Leiame do pacote NuGet Microsoft.Windows.CppWinRT.

Incluir arquivos de cabeçalho do C++/WinRT

O mínimo que você deve fazer é, em seu arquivo de cabeçalho pré-compilado (geralmente pch.h), inclua winrt/base.h conforme mostrado abaixo.

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

Mas você certamente precisará dos tipos no namespace winrt::Windows::Foundation. Talvez você já conheça outros namespaces de que precisará. Assim, inclua os cabeçalhos de API C++/WinRT projetados do Windows correspondentes a esses namespaces como esse (você não precisa incluir explicitamente winrt/base.h agora porque ele será incluído automaticamente para você).

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

Confira também o exemplo de código na seção a seguir (Adicionar suporte do C++/WinRT a um projeto C++/CX) para uma técnica que usa os aliases de namespace namespace cx e namespace winrt. Essa técnica permite que você lide com possíveis conflitos de namespace entre a projeção do C++/WinRT e a projeção do C++/CX.

Adicionar interop_helpers.h ao projeto

Agora você poderá adicionar as funções from_cx e to_cx ao seu projeto do C++/CX. Para obter instruções sobre como fazer isso, confira a seção Funções from_cx e to_cx funções acima.

Adicionar suporte do C++/CX a um projeto C++/WinRT

Esta seção descreve o que fazer se você decidiu criar um projeto C++/WinRT e fazer seu trabalho de portabilidade lá.

Para combinar o C++/WinRT e o C++/CX em um projeto C++/WinRT – incluindo o uso das funções auxiliares from_cx e to_cx no projeto – você precisará adicionar manualmente o suporte do C++/CX ao projeto.

  • Crie um projeto C++/WinRT no Visual Studio usando um dos modelos de projeto C++/WinRT (confira Suporte ao Visual Studio para C++/WinRT).
  • Ative o suporte ao projeto para C++/CX. Nas propriedades do projeto, C/C++>Geral>Utilizar Extensão do Windows Runtime>Sim (/ZW).

Um exemplo de projeto C++/WinRT mostrando as duas funções auxiliares em uso

Nesta seção, você poderá criar um projeto C++/WinRT de exemplo que demonstra como usar from_cx e to_cx. Ele também ilustra como é possível usar aliases de namespace para diferentes ilhas de código, a fim de lidar com outros possíveis conflitos de namespace entre as projeções do C++/WinRT e do C++/CX.

  • Crie um projeto Visual C++>Windows Universal>App Core (C++/WinRT).
  • Nas propriedades do projeto, C/C++>Geral>Utilizar Extensão do Windows Runtime>Sim (/ZW).
  • Adicione interop_helpers.h ao projeto. Para obter instruções sobre como fazer isso, confira a seção Funções from_cx e to_cx funções acima.
  • Substitua o conteúdo de App.cpp pela listagem de códigos a seguir.
  • Compilar e executar.

WINRT_ASSERT é uma definição de macro e se expande para _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>());
}

APIs importantes