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

Prima di leggere questo argomento, sono necessarie le informazioni nell'argomento Passare a C++/WinRT da C++/CX. Questo argomento presenta due opzioni principali per la conversione del progetto C++/CX in C++/WinRT.

  • Esegui il porting dell'intero progetto in un unico passaggio. L'opzione più semplice per un progetto non troppo grande. Se si dispone di un progetto componente Windows Runtime, questa strategia è l'unica opzione.
  • Convertire gradualmente il progetto (le dimensioni o la complessità della codebase potrebbero rendere necessario questo processo). Questa strategia richiede tuttavia di seguire un processo di conversione in cui per un momento il codice C++/CX e C++/WinRT esiste side-by-side nello stesso progetto. Per un progetto XAML, in qualsiasi momento, i tipi di pagina XAML devono essere tutti C++/WinRT o tutti C++/CX.

Questo argomento di interoperabilità è rilevante per la seconda strategia, per i 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 perché convertirai gradualmente il codice da C++/CX a C++/WinRT. Oppure potresti semplicemente scegliere di usare sia le proiezioni del linguaggio C++/WinRT che C++/CX nello stesso progetto, sia che tu stia eseguendo la conversione o meno, e usi queste funzioni helper per interagire tra i due.

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

Funzioni from_cx e to_cx

Ecco un listato del codice sorgente di un file header denominato interop_helpers.h, contenente varie funzioni di supporto per la conversione. Man mano che convertirai gradualmente il progetto, ci saranno ancora parti in C++/CX e parti che hai convertito 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 queste 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 sul puntatore dell'interfaccia IUnknown sottostante. Chiama quindi QueryInterface su tale puntatore per eseguire una query per l'interfaccia predefinita dell'oggetto C++/WinRT. QueryInterface è l'equivalente ABI (Application Binary Interface) di Windows Runtime dell'estensione C++/CXsafe_cast. Inoltre, la funzione winrt::put_abi ottiene l'indirizzo del puntatore all'interfaccia IUnknown sottostante di un oggetto C++/WinRT, in modo che possa essere impostato su un valore diverso.

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 converte quel puntatore in un oggetto C++/CX prima di utilizzare l'estensione C++/CX safe_cast per ricercare il tipo C++/CX richiesto.

Il file di intestazione interop_helpers.h

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

  • Aggiungi un nuovo elemento File di intestazione (.h) al tuo progetto e assegnagli il nome interop_helpers.h.
  • Sostituire il contenuto di interop_helpers.h con l'elenco di codice precedente.
  • Aggiungi questi include a pch.h.
// pch.h
...
#include <unknwn.h>
// Include C++/WinRT projected Windows API headers here.
...
#include <interop_helpers.h>

Partire da un progetto C++/CX e aggiungervi il supporto per C++/WinRT

Questa sezione descrive cosa fare se hai deciso di partire dal tuo progetto C++/CX esistente, aggiungervi il supporto per C++/WinRT e svolgere lì il lavoro di migrazione. Vedi anche Visual Studio supporto 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, è necessario aggiungere manualmente il supporto C++/WinRT al progetto.

Aprire prima di tutto il progetto C++/CX in Visual Studio e verificare che la proprietà della piattaforma di destinazione generale>sia impostata su 10.0.17134.0 (Windows 10, versione 1803) o successiva.

Installare il pacchetto NuGet C++/WinRT

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

Importante

L'installazione del pacchetto NuGet C++/WinRT causa la disattivazione del supporto per C++/CX nel progetto. Se si intende convertire in un unico passaggio, è consigliabile lasciare disattivato il supporto in modo che i messaggi di compilazione consentano di trovare (e convertire) tutte le dipendenze in C++/CX (alla fine trasformare un progetto C++/CX puro in un progetto C++/WinRT puro). Tuttavia, vedi la sezione successiva per informazioni su come riattivarla.

Riattivare il supporto C++/CX

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

In alternativa ,oppure, per un progetto XAML, puoi aggiungere il supporto C++/CX usando la pagina delle proprietà del progetto C++/WinRT in Visual Studio. Nelle proprietà del progetto, Proprietà comuni>C++/WinRT>Lingua del progetto>C++/CX. In questo modo verrà aggiunta la proprietà seguente al .vcxproj file.

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

Importante

Ogni volta che devi eseguire la compilazione per elaborare il contenuto di un file MIDL (.idl) in file stub, dovrai reimpostare Linguaggio del progetto su C++/WinRT. Dopo che la compilazione ha generato questi stub, modificare Project Language nuovamente in C++/CX.

Per un elenco di opzioni di personalizzazione analoghe (che permettono di regolare con precisione il comportamento dello strumento cppwinrt.exe), vedere il file readme del pacchetto NuGet Microsoft.Windows.CppWinRT.

Includere i file di intestazione C++/WinRT

Come minimo, nel file header precompilato (di solito pch.h), dovresti includere winrt/base.h come mostrato di seguito.

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

Ma avrai quasi certamente bisogno dei tipi definiti nello spazio dei nomi winrt::Windows::Foundation. E potresti già conoscere altri spazi dei nomi di cui avrai bisogno. Includi quindi le intestazioni proiettate dell'API di Windows per C++/WinRT che corrispondono a tali namespace come segue (ora non è necessario includere esplicitamente winrt/base.h, perché verrà incluso 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 (Partire da un progetto C++/WinRT e aggiungere il supporto C++/CX) per una tecnica che usa gli alias dello spazio dei nomi namespace cx e namespace winrt. Questa tecnica consente di gestire potenziali conflitti di spazio dei nomi che altrimenti si verificherebbero tra la proiezione C++/WinRT e la proiezione C++/CX.

Aggiungere interop_helpers.h al progetto

A questo punto sarà possibile aggiungere le funzioni from_cx e to_cx al progetto C++/CX. Per istruzioni su questa operazione, vedere la sezione from_cx e to_cx funzioni sopra.

Partire da un progetto C++/WinRT e aggiungere il supporto per C++/CX

Questa sezione descrive le operazioni da eseguire se si è deciso di creare un nuovo progetto C++/WinRT e di eseguire le operazioni 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 C++/CX al progetto.

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

Esempio di progetto C++/WinRT che mostra 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 dello spazio dei nomi per le diverse isole di codice, per gestire eventuali conflitti di spazio dei nomi tra la proiezione C++/WinRT e la proiezione C++/CX.

  • Crea un progetto Visual C++>App Windows universale>Core (C++/WinRT).
  • Nelle proprietà del progetto, C/C++>Generale>Usa estensione Windows Runtime>Sì (/ZW).
  • Aggiungere interop_helpers.h al progetto. Per istruzioni su questa operazione, vedere la sezione from_cx e to_cx funzioni sopra.
  • 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 fino a _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