Interopérabilité entre C++/WinRT et C++/CX

Avant de lire cette rubrique, vous aurez besoin des informations de la rubrique Déplacer vers C++/WinRT à partir de C++/CX. Cette rubrique présente deux options de stratégie principales pour le portage de votre projet C++/CX vers C++/WinRT.

  • Porter l’intégralité du projet en une seule passe. Option la plus simple pour un projet qui n’est pas trop volumineux. Si vous avez un projet de composant Windows Runtime, cette stratégie est votre seule option.
  • Porter progressivement le projet (la taille ou la complexité de votre codebase peut rendre cela nécessaire). Toutefois, cette stratégie vous demande de suivre un processus de portage dans lequel le code C++/CX et C++/WinRT existe côte à côte dans le même projet. Pour un projet XAML, à tout moment, vos types de pages XAML doivent être tous C++/WinRT ou C++/CX.

Cette rubrique d’interopérabilité est pertinente pour cette deuxième stratégie, dans les cas où vous devez porter progressivement votre projet. Cette rubrique présente différentes formes de paires de fonctions d’assistance que vous pouvez utiliser pour convertir un objet C++/CX (et d’autres types) en objet C++/WinRT (et vice versa) dans le même projet.

Ces fonctions d’assistance seront très utiles, car vous portez progressivement votre code de C++/CX vers C++/WinRT. Vous pouvez également choisir d’utiliser les projections de langage C++/WinRT et C++/CX dans le même projet, que vous portiez ou non, et utilisez ces fonctions d’assistance pour interagir entre les deux.

Après avoir lu cette rubrique, pour obtenir des informations et des exemples de code montrant comment prendre en charge les tâches PPL et les coroutines côte à côte dans le même projet (par exemple, appeler des coroutines à partir de chaînes de tâches), consultez la rubrique Asynchronie plus avancée et l’interopérabilité entre C++/WinRT et C++/CX.

Fonctions from_cx et to_cx

Voici une liste de code source d’un fichier d’en-tête nommé interop_helpers.h, contenant différentes fonctions d’assistance de conversion. À mesure que vous portez progressivement votre projet, il y aura toujours des parties en C++/CX et des parties que vous avez transférées vers C++/WinRT. Vous pouvez utiliser ces fonctions d’assistance pour convertir des objets (et d’autres types) vers et depuis C++/CX et C++/WinRT dans votre projet aux points de limite entre ces deux parties.

Les sections qui suivent la liste de codes expliquent les fonctions d’assistance et expliquent comment créer et utiliser le fichier d’en-tête dans votre projet.

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

Fonction from_cx

La fonction d’assistance from_cx convertit un objet C++/CX en objet C++/WinRT équivalent. La fonction convertit un objet C++/CX en pointeur d’interface IUnknown sous-jacent. Il appelle ensuite QueryInterface sur ce pointeur pour rechercher l’interface par défaut de l’objet C++/WinRT. QueryInterface est l’équivalent, dans l’interface binaire d’application (ABI) de Windows Runtime, de l’extension C++/CXsafe_cast. Et, la fonction winrt::put_abi obtient l’adresse du pointeur d’interface IUnknown sous-jacent d’un objet C++/WinRT afin de pouvoir lui affecter une autre valeur.

Fonction to_cx

La fonction d’assistance to_cx convertit un objet C++/WinRT en objet C++/CX équivalent. La fonction winrt ::get_abi récupère un pointeur vers l’interface IUnknown sous-jacente d’un objet C++/WinRT. La fonction caste ce pointeur vers un objet C++/CX avant d’utiliser l’extension C++/CX safe_cast pour rechercher le type C++/CX demandé.

Fichier interop_helpers.h d’en-tête

Pour utiliser les fonctions d’assistance dans votre projet, procédez comme suit.

  • Ajoutez un nouvel élément de fichier d’en-tête (.h) à votre projet et nommez-le interop_helpers.h.
  • Remplacez le contenu de interop_helpers.h par le bloc de code ci-dessus.
  • Ajoutez ces éléments à pch.h.
// pch.h
...
#include <unknwn.h>
// Include C++/WinRT projected Windows API headers here.
...
#include <interop_helpers.h>

Prise en charge d’un projet C++/CX et ajout de la prise en charge de C++/WinRT

Cette section décrit ce que vous devez faire si vous avez décidé de prendre votre projet C++/CX existant, d’ajouter la prise en charge de C++/WinRT et de procéder au travail de portage là-bas. Consultez également la prise en charge de C++/WinRT dans Visual Studio.

Pour combiner C++/CX et C++/WinRT dans un projet C++/CX, notamment à l’aide des fonctions d’assistance from_cx et to_cx dans le projet, vous devez ajouter manuellement la prise en charge de C++/WinRT au projet.

Tout d’abord, ouvrez votre projet C++/CX dans Visual Studio et vérifiez que la version de la plateformecible> de la propriété de projet est définie sur 10.0.17134.0 (Windows 10, version 1803) ou ultérieure.

Installer le package NuGet C++/WinRT

Microsoft.Windows. Le package NuGet CppWinRT fournit la prise en charge des builds C++/WinRT (propriétés et cibles MSBuild). Pour l’installer, cliquez sur l’élément de menu Project>Manage de packages NuGet...>Parcourez, tapez ou collez Microsoft.Windows. CppWinRT dans la zone de recherche, sélectionnez l’élément dans les résultats de recherche, puis cliquez sur Installer pour installer le package pour cette project.

Important

L’installation du package NuGet C++/WinRT entraîne la désactivation de la prise en charge de C++/CX dans le projet. Si vous comptez effectuer le portage en une seule passe, il est judicieux de laisser cette prise en charge désactivée afin que les messages de génération vous aident à trouver (et à porter) toutes vos dépendances à C++/CX, pour passer au final d’un projet entièrement en C++/CX à un projet entièrement en C++/WinRT. Mais consultez la section suivante pour savoir comment le réactiver.

Réactiver la prise en charge de C++/CX

Si vous effectuez le portage en une seule passe, vous n’avez pas besoin de le faire. Toutefois, si vous avez besoin d’effectuer la migration progressivement, vous devrez, à ce stade, réactiver la prise en charge de C++/CX dans votre projet. Dans les propriétés du projet, C/C++>General>Consume Windows Runtime Extension>Yes (/ZW)).

Vous pouvez également (ou, pour un projet XAML, en outre), ajouter la prise en charge C++/CX à l’aide de la page de propriétés du projet C++/WinRT dans Visual Studio. Dans les propriétés du projet, Propriétés communes>C++/WinRT>Langage du projet>C++/CX. Cela ajoute la propriété suivante à votre .vcxproj fichier.

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

Important

Chaque fois que vous devez effectuer une génération afin de traiter le contenu d’un fichier Midl (.idl) en fichiers stub, vous devrez remettre Langage du projet sur C++/WinRT. Une fois que la build a généré ces stubs, remplacez Project Langue par C++/CX.

Pour obtenir une liste d’options de personnalisation similaires (qui ajustent le comportement de l’outil cppwinrt.exe), consultez le fichier Lisez-moi du package NuGet Microsoft.Windows.CppWinRT.

Inclure les fichiers d’en-tête C++/WinRT

Le moins que vous deviez faire est, dans votre fichier d’en-tête précompilé (généralement pch.h), incluez winrt/base.h comme indiqué ci-dessous.

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

Mais vous aurez presque certainement besoin des types dans l'espace de noms winrt ::Windows ::Foundation. Vous connaissez peut-être déjà d’autres espaces de noms dont vous aurez besoin. Incluez donc les fichiers d’en-tête de l’API Windows projetés par C++/WinRT qui correspondent à ces espaces de noms, comme suit (vous n’avez pas besoin d’inclure winrt/base.h explicitement à présent, car il sera inclus automatiquement).

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

Consultez également l’exemple de code dans la section suivante (Reprendre un projet C++/WinRT et y ajouter la prise en charge de C++/CX), présentant une technique utilisant les alias namespace cx d’espace de noms namespace winrt. Cette technique vous permet de gérer les collisions potentielles d’espaces de noms qui pourraient autrement se produire entre la projection C++/WinRT et la projection C++/CX.

Ajouter interop_helpers.h au projet

Vous pourrez maintenant ajouter les fonctions from_cx et to_cx à votre projet C++/CX. Pour obtenir des instructions sur cette opération, consultez la section from_cx et to_cx fonctions ci-dessus.

Prendre un projet C++/WinRT et lui ajouter la prise en charge de C++/CX

Cette section décrit ce que vous devez faire si vous avez décidé de créer un nouveau projet C++/WinRT et d’effectuer vos travaux de portage dans celui-ci.

Pour combiner C++/WinRT et C++/CX dans un projet C++/WinRT, notamment à l’aide des fonctions d’assistance from_cx et to_cx dans le projet, vous devez ajouter manuellement la prise en charge C++/CX au projet.

  • Créez un nouveau projet C++/WinRT dans Visual Studio à l’aide de l’un des modèles de projet C++/WinRT (voir la prise en charge de C++/WinRT dans Visual Studio).
  • Activez la prise en charge de C++/CX pour le projet. Dans les propriétés du projet, C/C++>General>Consume Windows Runtime Extension>Yes (/ZW).

Exemple de projet C++/WinRT montrant les deux fonctions d’assistance en cours d’utilisation

Dans cette section, vous pouvez créer un exemple de projet C++/WinRT qui montre comment utiliser from_cx et to_cx. Il illustre également comment vous pouvez utiliser des alias d’espace de noms pour les différentes îles de code afin de gérer les collisions d’espaces de noms potentielles entre la projection C++/WinRT et la projection C++/CX.

  • Créez un projet Visual C++>Windows Universal>Core App (C++/WinRT).
  • Dans les propriétés du projet, C/C++>General>Consume Windows Runtime Extension>Yes (/ZW).
  • Ajouter interop_helpers.h au projet. Pour obtenir des instructions sur cette opération, consultez la section from_cx et to_cx fonctions ci-dessus.
  • Remplacez le contenu de App.cpp par le listing de code ci-dessous.
  • Créez et exécutez.

WINRT_ASSERT est une définition de macro, et elle s’étend à _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 importantes