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

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

  • Migre todo o projeto de uma só vez. A opção mais simples para um projeto que não é muito grande. Se você tiver um projeto de componente Windows Runtime, essa estratégia será sua única opção.
  • Portar o projeto gradualmente (o tamanho ou a complexidade da base de código pode fazer isso necessário). Mas essa estratégia exige que você siga um processo de portabilidade no qual, por um tempo, o código C++/CX e C++/WinRT existe lado a lado no mesmo projeto. Para um projeto XAML, em um determinado momento, os tipos de páginas XAML devem ser todos C++/WinRT ou todos C++/CX.

Este tópico de interoperabilidade é relevante para essa segunda estratégia, para casos em que você precisa portar seu projeto gradualmente. Este tópico mostra 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 à medida que 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, seja você portando ou não, e usar essas funções auxiliares para interoperar entre os dois.

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, chamando corrotinas a partir de cadeias de tarefas), consulte o tópico avançado Assincronia e interoperabilidade entre C++/WinRT e C++/CX.

As funções from_cx e to_cx

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

As seções que seguem a listagem de 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 C++/CX em um objeto C++/WinRT equivalente. A função faz a conversão de um objeto C++/CX para seu ponteiro de interface IUnknown subjacente. Em seguida, ele chama QueryInterface nesse ponteiro para consultar a interface padrão do objeto C++/WinRT. QueryInterface é o equivalente da interface binária de aplicativo (ABI) do Windows Runtime da extensão safe_castC++/CX. E a função winrt::put_abi recupera o endereço do ponteiro da interface subjacente IUnknown de um objeto C++/WinRT para que possa ser definido com outro valor.

A função to_cx

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

O interop_helpers.h arquivo de cabeçalho

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

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

Tomando um projeto C++/CX e adicionando suporte a C++/WinRT

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

Para misturar C++/CX e 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 superior.

Instalar o pacote NuGet do C++/WinRT

O pacote NuGet Microsoft.Windows.CppWinRT fornece suporte à compilação do C++/WinRT (propriedades e targets do MSBuild). Para instalá-lo, clique no item de menu Project>Manage NuGet Packages...>Navegue, digite ou cole Microsoft.Windows. CppWinRT na caixa de pesquisa, selecione o item nos resultados da pesquisa e clique em Instalar para instalar o pacote para esse project.

Important

A instalação do pacote NuGet do C++/WinRT faz com que o suporte para C++/CX seja desativado no projeto. Se você for fazer a migração de uma só vez, é uma boa ideia deixar esse suporte desativado para que as mensagens de compilação ajudem você a encontrar (e migrar) todas as suas dependências de C++/CX, acabando por transformar o que antes era um projeto puramente C++/CX em um projeto puramente C++/WinRT. Mas veja a próxima seção para obter informações sobre como ativá-la novamente.

Ativar novamente o suporte do C++/CX

Se você estiver fazendo a portabilidade de uma só vez, não precisará fazer isso. Porém, se você precisar fazer a portabilidade gradualmente, neste momento, precisará ativar o suporte do 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 suporte a C++/CX usando a página de propriedades do projeto C++/WinRT em 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>

Important

Sempre que precisar compilar para processar o conteúdo de um arquivo MIDL (.idl) em arquivos stub, você precisará alterar Idioma do Projeto novamente para C++/WinRT. Depois que o build tiver gerado esses stubs, altere Project Language de volta para C++/CX.

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

Incluir arquivos de cabeçalho C++/WinRT

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

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

Mas você quase certamente precisará dos tipos no namespace winrt::Windows::Foundation. E talvez você já saiba de outros namespaces de que precisará. Portanto, inclua os cabeçalhos da API do Windows projetados pelo C++/WinRT que correspondem a esses namespaces da seguinte forma (agora você não precisa incluir explicitamente winrt/base.h, porque ele será incluído automaticamente).

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

Consulte também o exemplo de código na seção a seguir (Tomando um projeto C++/WinRT e adicionando suporte a C++/CX) para uma técnica usando os aliases namespace cx de namespace e namespace winrt. Essa técnica permite lidar com possíveis colisões de namespace entre a projeção C++/WinRT e a projeção C++/CX.

Adicionar interop_helpers.h ao projeto

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

Tomando um projeto C++/WinRT e adicionando suporte a C++/CX

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

Para misturar C++/WinRT e 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 novo projeto C++/WinRT em Visual Studio usando um dos modelos de projeto do C++/WinRT (consulte Visual Studio suporte para C++/WinRT).
  • Ative o suporte ao projeto para C++/CX. Nas propriedades do projeto, C/C++>Geral>Consumir 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ê pode criar um projeto C++/WinRT de exemplo que demonstra como usar from_cx e to_cx. Ele também ilustra como você pode usar aliases de namespace para as diferentes ilhas de código, a fim de lidar com possíveis colisões de namespace entre a projeção C++/WinRT e a projeção C++/CX.

  • Crie um projeto do Visual C++>Windows Universal>Core App (C++/WinRT).
  • Nas propriedades do projeto, C/C++>Geral>Consumir Extensão do Windows Runtime>Sim (/ZW).
  • Adicione interop_helpers.h ao projeto. Para obter instruções sobre como fazer isso, consulte a seção de funções from_cx e to_cx 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