Interoperação entre C++/WinRT e C++/CX

Antes de leres este tópico, vais precisar da informação do tópico Passar de C++/CX para C++/WinRT. Esse tópico apresenta duas principais opções estratégicas para portar o seu projeto C++/CX para C++/WinRT.

  • Migra todo o projeto de uma só vez. A opção mais simples para um projeto que não é demasiado grande. Se tem um projeto de componente Windows Runtime, então esta estratégia é a sua única opção.
  • Porta o projeto gradualmente (o tamanho ou complexidade da tua base de código pode tornar isso necessário). Mas esta estratégia exige que sigas um processo de portabilidade em que, durante algum tempo, código C++/CX e C++/WinRT coexistem lado a lado no mesmo projeto. Para um projeto XAML, a qualquer momento, os tipos de página XAML devem ser todos C++/ WinRT ou todos C++/CX.

Este tema de interoperabilidade é relevante para essa segunda estratégia — nos casos em que precisa de migrar gradualmente o seu projeto. Este tópico mostra-te várias formas de pares de funções auxiliares que podes usar para converter um objeto C++/CX (e outros tipos) num objeto C++/WinRT (e vice-versa) dentro do mesmo projeto.

Estas funções auxiliares serão muito úteis à medida que for portando o seu código gradualmente de C++/CX para C++/WinRT. Ou podes simplesmente optar por usar as projeções das linguagens C++/WinRT e C++/CX no mesmo projeto, quer estejas a portar ou não, e usar estas 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 suportar tarefas PPL e corrotinas lado a lado no mesmo projeto (por exemplo, chamar corrotinas a partir de cadeias de tarefas), consulte o tópico mais avançado Assincronia e interoperabilidade entre C++/WinRT e C++/CX.

As funções from_cx e to_cx

Aqui está uma lista de código-fonte de um ficheiro de cabeçalho chamado interop_helpers.h, contendo várias funções auxiliares de conversão. À medida que vais portando gradualmente o teu projeto, ainda haverá partes em C++/CX e partes que portaste para C++/WinRT. Podes usar estas funções auxiliares para converter objetos (e outros tipos) para e de C++/CX e C++/WinRT no teu projeto, nos pontos de fronteira entre essas duas partes.

As secções que seguem a listagem do código explicam as funções auxiliares e como criar e usar o ficheiro de cabeçalho no 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 num objeto equivalente C++/WinRT. A função lança um objeto C++/CX para o seu apontador de interface IUnknown subjacente. Depois, invoca QueryInterface sobre esse apontador para obter a interface predefinida do objeto C++/WinRT. O QueryInterface é o equivalente da interface binária de aplicações (ABI) do Windows Runtime à extensão C++/CXsafe_cast. E a função winrt::put_abi obtém o endereço do ponteiro da interface subjacente IUnknown de um objeto C++/WinRT para que este possa ser definido com outro valor.

A função to_cx

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

O interop_helpers.h ficheiro de cabeçalho

Para usar as funções auxiliares no seu projeto, siga estes passos.

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

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

Esta secção descreve o que fazer se tiver decidido partir do seu projeto C++/CX existente, adicionar-lhe suporte para C++/WinRT e realizar aí o trabalho de migração. Veja também o suporte do Visual Studio para C++/WinRT.

Para misturar C++/CX e C++/WinRT num projeto C++/CX — incluindo usar as funções auxiliares from_cx e to_cx no projeto — terá de adicionar manualmente suporte a C++/WinRT ao projeto.

Primeiro, abra o seu projeto C++/CX no Visual Studio e confirme que a propriedade> do projetoGeneral Target Platform Version está definida para 10.0.17134.0 (Windows 10, versão 1803) ou superior.

Instale o pacote NuGet C++/WinRT

O pacote NuGet Microsoft.Windows.CppWinRT fornece suporte de compilação para C++/WinRT (propriedades e destinos do MSBuild). Para o instalar, clique no item do menu ProjectManage>NuGet Packages...>Navegar, escrever ou colar Microsoft.Windows. CppWinRT na caixa de pesquisa, seleciona o item nos resultados de pesquisa e depois clica em Instalar para instalar o pacote desse projeto.

Important

Instalar o pacote NuGet C++/WinRT faz com que o suporte a C++/CX fique desativado no projeto. Se vais portar numa só passagem, então é boa ideia deixar esse suporte desligado para que as mensagens de compilação te ajudem a encontrar (e portar) todas as tuas dependências em C++/CX (eventualmente transformando o que era um projeto puro C++/CX num projeto puro C++/WinRT). Mas veja a próxima secção para informações sobre como voltar a ligá-lo.

Voltar a ativar o suporte a C++/CX

Se estás a migrar de uma só vez, não precisas de o fazer. Mas se precisares de portar gradualmente, então neste momento terás de voltar a ativar o suporte a C++/CX no teu projeto. Nas propriedades do projeto, C/C++>Geral>Consumir Extensão do Windows Runtime>Sim (/ZW)).

Alternativamente (ou, para um projeto XAML, adicionalmente), pode adicionar suporte a C++/CX usando a página de propriedades do projeto C++/WinRT no Visual Studio. Nas propriedades do projeto, Propriedades Comuns>C++/WinRT>Idioma do Projeto>C++/CX. Ao fazer isso, a seguinte propriedade será adicionada ao seu .vcxproj ficheiro.

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

Important

Sempre que precisares de compilar para processar o conteúdo de um ficheiro Midl (.idl) em ficheiros stub, terás de mudar a Project Language de volta para C++/WinRT. Depois de a build gerar esses stubs, muda a 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 ficheiro readme do pacote NuGet Microsoft.Windows.CppWinRT.

Incluir ficheiros de cabeçalho C++/WinRT

O mínimo que deve fazer é, no seu ficheiro de cabeçalho pré-compilado (normalmente pch.h), incluir winrt/base.h como mostrado abaixo.

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

Mas quase de certeza vai precisar dos tipos no namespace winrt::Windows::Foundation. E talvez já conheças outros namespaces de que vais precisar. Por isso, inclua os cabeçalhos projetados da API do Windows em C++/WinRT que correspondam a esses namespaces como este (não precisa de incluir winrt/base.h explicitamente agora porque será incluído automaticamente para si).

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

Veja também o exemplo de código na secção seguinte (Pegar num projeto C++/WinRT e adicionar suporte a C++/CX) para uma técnica que utiliza os aliases namespace cx do namespace e namespace winrt. Essa técnica permite-lhe resolver potenciais colisões de espaço de nomes que, de outro modo, ocorreriam entre a projeção C++/WinRT e a projeção C++/CX.

Adicionar interop_helpers.h ao projeto

Agora poderá adicionar as funções from_cx e to_cx ao seu projeto C++/CX. Para instruções sobre como fazer isso, veja a secção from_cx e to_cx funções acima.

Adaptar um projeto C++/WinRT para adicionar suporte a C++/CX

Esta secção descreve o que fazer se decidiu criar um novo projeto em C++/WinRT e fazer o seu trabalho de portabilidade aí.

Para misturar C++/WinRT e C++/CX num projeto C++/WinRT — incluindo a utilização das funções de ajuda from_cx e to_cx no projeto — terá de adicionar manualmente suporte a C++/CX ao projeto.

  • Crie um novo projeto C++/WinRT no Visual Studio usando um dos modelos de projeto C++/WinRT (veja o suporte do Visual Studio para C++/WinRT).
  • Ativa o suporte a projetos para C++/CX. Nas propriedades do projeto, C/C++>Geral>Consumir Extensão do Windows Runtime>Sim (/ZW).

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

Nesta secção, pode criar um projeto de exemplo em C++/WinRT que demonstre como usar from_cx e to_cx. Também ilustra como se podem usar pseudónimos de namespace para as diferentes ilhas de código, de modo a lidar com potenciais colisões de namespace entre a projeção C++/WinRT e a projeção C++/CX.

  • Crie um projeto Visual C++>Windows>Universal Core App (C++/WinRT).
  • Nas propriedades do projeto, C/C++>Geral>Consumir Extensão do Windows Runtime>Sim (/ZW).
  • Acrescenta interop_helpers.h ao projeto. Para instruções sobre como fazer isso, veja a secção from_cx e to_cx funções acima.
  • Substitua o conteúdo do App.cpp pelo código listado abaixo.
  • Compilar e executar.

WINRT_ASSERT é uma definição macro, e expande-se 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