Interoperabilità tra C++/WinRT e C++/CX

Prima di leggere questo argomento, sono necessarie le informazioni dell'argomento Passare a C++/WinRT da C++/CX. Questo argomento introduce due opzioni di strategia principali per convertire il progetto C++/CX in C++/WinRT.

  • Convertire l'intero progetto in un unico passaggio. Si tratta dell'opzione più semplice per un progetto non troppo esteso. Se si dispone di un progetto di componente Windows Runtime, questa strategia costituisce l'unica opzione possibile.
  • Convertire il progetto gradualmente (la dimensione o la complessità della base di codici potrebbe rendere necessaria questa strategia). Questa strategia, tuttavia, richiede l'esecuzione di un processo di conversione in cui, per un periodo di tempo, i codici C++/CX e C++/WinRT coesistono nello stesso progetto. Per un progetto XAML, in un determinato momento, i tipi di pagina XAML devono essere o tutti C++/WinRT o tutti C++/CX.

Questo argomento sull'interoperabilità è pertinente per la seconda strategia: nei casi in cui è necessario convertire gradualmente il progetto. Questo argomento illustra varie forme di coppie di funzioni helper che è possibile usare per convertire un oggetto C++/CX (e altri tipi) in un oggetto C++/WinRT (e viceversa) all'interno dello stesso progetto.

Queste funzioni helper saranno molto utili quando si convertirà il codice gradualmente da C++/CX a C++/WinRT. In alternativa, è possibile scegliere di usare entrambe le proiezioni di linguaggio C++/WinRT e C++/CX nello stesso progetto, indipendentemente dal fatto che si stia eseguendo la conversione o meno, e le funzioni helper per l'interoperabilità tra i due codici.

Dopo la lettura di questo argomento, per informazioni ed esempi di codice che illustrino come supportare le attività e le coroutine della libreria PPL affiancate nello stesso progetto (ad esempio, per la chiamata di coroutine dalle catene di attività), vedere l'argomento più avanzato Asincronia e interoperabilità tra C++/WinRT e C++/CX.

Funzioni from_cx e to_cx

Ecco un listato di codice sorgente di un file di intestazione denominato interop_helpers.h, che contiene due funzioni helper di conversione. Con la graduale conversione del progetto, alcune parti continuano a essere in C++/CX, mentre altre risultano convertite in C++/WinRT. È possibile usare queste funzioni helper per convertire oggetti (e altri tipi) in e da C++/CX e C++/WinRT nel progetto ai punti di limite tra le due parti.

Le sezioni che seguono l'elenco di codice illustrano le funzioni helper e come creare e usare il file di intestazione nel progetto.

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

Funzione from_cx

La funzione helper from_cx converte un oggetto C++/CX in un oggetto C++/WinRT equivalente. La funzione esegue il cast di un oggetto C++/CX al puntatore all'interfaccia IUnknown sottostante. Quindi chiama QueryInterface sul puntatore per eseguire una query per l'interfaccia predefinita dell'oggetto C++/WinRT. QueryInterface è l'equivalente dell'ABI (Application Binary Interface) Windows Runtime dell'estensione safe_cast C++/CX. Inoltre, la funzione winrt::put_abi recupera l'indirizzo del puntatore all'interfaccia IUnknown sottostante di un oggetto C++/WinRT affinché possa essere impostato su un altro valore.

Funzione to_cx

La funzione helper to_cx converte un oggetto C++/WinRT in un oggetto C++/CX equivalente. La funzione winrt::get_abi recupera un puntatore all'interfaccia IUnknown sottostante di un oggetto C++/WinRT. La funzione esegue il cast di questo puntatore a un oggetto C++/CX prima di usare l'estensione safe_cast C++/CX per eseguire una query per il tipo C++/CX richiesto.

File di intestazione interop_helpers.h

Per usare le due funzioni helper nel progetto, seguire questa procedura.

  • Aggiungere un nuovo elemento file di intestazione (.h) al progetto e denominarlo interop_helpers.h.
  • Sostituire il contenuto di interop_helpers.h con il listato di codice precedente.
  • Aggiungere tali inclusioni a pch.h.
// pch.h
...
#include <unknwn.h>
// Include C++/WinRT projected Windows API headers here.
...
#include <interop_helpers.h>

Acquisizione di un progetto C++/CX e aggiunta del supporto di C++/WinRT

Questa sezione descrive le operazioni da eseguire se si è deciso di usare il progetto C++/CX esistente, aggiungere il supporto di C++/WinRT ed eseguire l'operazione di conversione. Vedere anche Supporto di Visual Studio per C++/WinRT.

Per combinare C++/CX e C++/WinRT in un progetto—C++/CX, incluso l'uso delle funzioni helper from_cx e to_cx nel progetto, sarà necessario aggiungere manualmente il supporto di C++/WinRT al progetto.

Innanzitutto aprire il progetto C++/CX in Visual Studio e verificare che la proprietà del progetto Generale>Versione piattaforma di destinazione sia impostata su 10.0.17134.0 (Windows 10, versione 1803) o versione successiva.

Installare il pacchetto NuGet di C++/WinRT

Il pacchetto NuGet Microsoft.Windows.CppWinRT fornisce supporto per la compilazione C++/WinRT (proprietà e destinazioni MSBuild). Per installarlo, fare clic sulla voce di menu Progetto>Gestisci pacchetti NuGet...>Sfoglia, digitare o incollare Microsoft.Windows.CppWinRT nella casella di ricerca, selezionare l'elemento nei risultati della ricerca, quindi fare clic su Installa per installare il pacchetto per tale progetto.

Importante

L'installazione del pacchetto NuGet C++/WinRT comporta la disattivazione del supporto di C++/CX nel progetto. Se si esegue la conversione in un unico passaggio, è consigliabile lasciare il supporto disattivato in modo che i messaggi di compilazione consentano di individuare (e convertire) tutte le dipendenze in C++/CX (eventualmente trasformando un progetto C++/CX puro in un progetto C++/WinRT puro). Per informazioni sulla riattivazione del supporto, vedere la sezione seguente.

Riattivare il supporto di C++/CX

Se si esegue la conversione in un unico passaggio, non è necessario eseguire questa operazione. Se, tuttavia, è necessario eseguire la conversione gradualmente, a questo punto sarà necessario riattivare il supporto di C++/CX nel progetto. Nelle proprietà del progetto C /C++ >Generale>Utilizza estensione di Windows Runtime> Sì (/ZW).

In alternativa, oppure per un progetto XAML, è possibile aggiungere il supporto di C++/CX usando la pagina della proprietà del progetto C++/WinRT in Visual Studio. Nelle proprietà del progetto, Proprietà comuni>C++/WinRT>Project Language>C++/CX. In questo modo, si aggiungerà la proprietà seguente al file .vcxproj.

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

Importante

Indipendentemente dal tipo di compilazione, per elaborare il contenuto di un file Midl (.idl) in file stub, sarà necessario modificare di nuovo il valore di Project Language (Linguaggio del progetto) impostandolo su C++/WinRT. Dopo che la compilazione ha generato questi stub, modificare nuovamente Project Language (Linguaggio del progetto) impostandolo su C++/CX.

Per un elenco di opzioni di personalizzazione simili, che ottimizzano il comportamento dello strumento cppwinrt.exe, vedi il file readme del pacchetto NuGet Microsoft.Windows.CppWinRT.

Includere i file di intestazione C++/WinRT

L'operazione di base prevede che nel file di intestazione precompilato (in genere pch.h) si includa winrt/base.h, come illustrato di seguito.

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

Saranno, tuttavia, necessari i tipi nello spazio dei nomi winrt::Windows::Foundation ed è possibile che si conoscano già altri spazi dei nomi necessari. Includere quindi le intestazioni API Windows proiettate C++/WinRT che corrispondono a tali spazi dei nomi. Non è necessario includere in modo esplicito winrt/base.h in questo momento, poiché verrà inclusa automaticamente.

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

Vedere anche l'esempio di codice nella sezione seguente (Acquisizione di un progetto C++/WinRT e aggiunta del supporto di C++/CX) per una tecnica che usa gli alias degli spazi dei nomi namespace cx e namespace winrt. Questa tecnica consente di gestire i conflitti di spazio dei nomi altrimenti possibili tra le proiezioni C++/WinRT e C++/CX.

Aggiungere interop_helpers.h al progetto

A questo punto è possibile aggiungere le funzioni from_cx e to_cx al progetto C++/CX. Per istruzioni su come eseguire questa operazione, vedere la sezione delle funzioni from_cx e to_cx precedente.

Acquisizione di un progetto C++/WinRT e aggiunta del supporto di C++/CX

Questa sezione descrive le operazioni da eseguire se si è deciso di creare un nuovo progetto C++/WinRT ed eseguire l'operazione di conversione.

Per combinare C++/WinRT e C++/CX in un progetto C++/WinRT, incluso l'uso delle funzioni helper from_cx e to_cx nel progetto, è necessario aggiungere manualmente il supporto di C++/CX al progetto.

  • Creare un nuovo progetto C++/WinRT in Visual Studio usando uno dei modelli di progetto di C++/WinRT. Vedere Supporto di Visual Studio per C++/WinRT.
  • Attivare il supporto del progetto per C++/CX. Nelle proprietà del progetto C /C++ >Generale>Utilizza estensioni di Windows Runtime> Sì (/ZW).

Progetto C++/WinRT di esempio che illustra le due funzioni helper in uso

In questa sezione è possibile creare un progetto C++/WinRT di esempio che illustra come usare from_cx e to_cx. Illustra anche come usare gli alias degli spazi dei nomi per fare in modo che le diverse isole di codice gestiscano i conflitti di spazio dei nomi altrimenti possibili tra le proiezioni C++/WinRT e C++/CX.

  • Crea un progetto Visual C++>Windows Universal>Core App (C++/WinRT).
  • Nelle proprietà del progetto C /C++ >Generale>Utilizza estensioni di Windows Runtime> Sì (/ZW).
  • Aggiungere interop_helpers.h al progetto. Per istruzioni su come eseguire questa operazione, vedere la sezione delle funzioni from_cx e to_cx precedente.
  • Sostituisci il contenuto di App.cpp con il listato di codice riportato di seguito.
  • Compilare ed eseguire.

WINRT_ASSERT è una definizione di macro e si espande in 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 importanti