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

Cette rubrique montre comment convertir entre les objets ABI (Sdk Application Binary Interface) et C++/WinRT . Vous pouvez utiliser ces techniques pour interagir entre le code qui utilise ces deux méthodes de programmation avec le Windows Runtime, ou vous pouvez les utiliser lorsque vous déplacez progressivement votre code de l’ABI vers C++/WinRT.

En général, C++/WinRT expose les types ABI en tant que void*, afin que vous n’ayez pas besoin d’inclure de fichiers d’en-tête de plateforme.

Note

Dans les exemples de code, nous utilisons reinterpret_cast (plutôt que static_cast) afin de signaler clairement ce qui constitue des conversions de type intrinsèquement non sûres.

Qu’est-ce que le Windows Runtime ABI et quels sont les types ABI ?

Une classe Windows Runtime (classe runtime) est vraiment une abstraction. Cette abstraction définit une interface binaire (l’interface binaire d’application ou ABI) qui permet à différents langages de programmation d’interagir avec un objet. Quel que soit le langage de programmation, l'interaction du code client avec un objet Windows Runtime se produit au niveau le plus bas, avec des constructions de langage client traduites en appels dans l'ABI de l'objet.

Les en-têtes Windows SDK dans le dossier «%WindowsSdkDir%Include\10.0.17134.0\winrt » (ajustez le numéro de version du SDK pour votre cas, le cas échéant), sont les fichiers d’en-tête ABI Windows Runtime. Ils ont été produits par le compilateur MIDL. Voici un exemple d’inclusion de l’un de ces en-têtes.

#include <windows.foundation.h>

Voici un exemple simplifié de l’un des types ABI que vous trouverez dans cet en-tête de KIT de développement logiciel (SDK) particulier. Notez l’espace de noms ABI ; Windows ::Foundation, et tous les autres espaces de noms Windows, sont déclarés par les en-têtes du Kit de développement logiciel (SDK) dans l’espace de noms ABI.

namespace ABI::Windows::Foundation
{
    IUriRuntimeClass : public IInspectable
    {
    public:
        /* [propget] */ virtual HRESULT STDMETHODCALLTYPE get_AbsoluteUri(/* [retval, out] */__RPC__deref_out_opt HSTRING * value) = 0;
        ...
    }
}

IUriRuntimeClass est une interface COM. Mais plus que cela, étant donné que sa base est IInspectable, IUriRuntimeClass est une interface Windows Runtime. Notez le type de retour HRESULT , plutôt que le déclenchement d’exceptions. Et l’utilisation d’artefacts tels que le descripteur HSTRING (il est recommandé de réinitialiser ce descripteur à nullptr une fois que vous avez fini de l’utiliser). Cela donne un aperçu de ce que le Windows Runtime ressemble au niveau binaire de l’application ; en d’autres termes, au niveau de la programmation COM.

Le Windows Runtime est basé sur les API COM (Component Object Model). Vous pouvez accéder au Windows Runtime de cette façon, ou vous pouvez y accéder par le biais de projections de langage. Une projection masque les détails COM et fournit une expérience de programmation plus naturelle pour un langage donné.

Par exemple, si vous regardez dans le dossier «%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt » (à nouveau, ajustez le numéro de version du SDK pour votre cas, si nécessaire), vous trouverez les en-têtes de projection de langage C++/WinRT. Il existe un en-tête pour chaque espace de noms Windows, comme il existe un en-tête ABI par espace de noms Windows. Voici un exemple d’inclusion de l’un des en-têtes C++/WinRT.

#include <winrt/Windows.Foundation.h>

Et, à partir de cet en-tête, voici l’équivalent C++/WinRT (simplifié) du type ABI que nous venons de voir.

namespace winrt::Windows::Foundation
{
    struct Uri : IUriRuntimeClass, ...
    {
        winrt::hstring AbsoluteUri() const { ... }
        ...
    };
}

L’interface ici est moderne, standard C++. Il s’éloigne des HRESULT(C++/WinRT déclenche des exceptions si nécessaire). Et la fonction d’accesseur retourne un objet de chaîne simple, qui est nettoyé à la fin de son étendue.

Cette rubrique concerne les cas où vous souhaitez interagir avec ou porter du code qui fonctionne au niveau de la couche ABI (Application Binary Interface).

Conversion vers et à partir de types ABI dans le code

Pour des raisons de sécurité et de simplicité, pour les conversions dans les deux sens, vous pouvez simplement utiliser winrt ::com_ptr, com_ptr ::as et winrt ::Windows ::Foundation ::IUnknown ::as. Voici un exemple de code (basé sur le modèle de projet Application console ), qui montre également comment utiliser des alias d’espace de noms pour les différentes îles afin de gérer les collisions d’espaces de noms potentielles entre la projection C++/WinRT et l’ABI.

// pch.h
#pragma once
#include <windows.foundation.h>
#include <unknwn.h>
#include "winrt/Windows.Foundation.h"

// main.cpp
#include "pch.h"

namespace winrt
{
    using namespace Windows::Foundation;
}

namespace abi
{
    using namespace ABI::Windows::Foundation;
};

int main()
{
    winrt::init_apartment();

    winrt::Uri uri(L"http://aka.ms/cppwinrt");

    // Convert to an ABI type.
    winrt::com_ptr<abi::IStringable> ptr{ uri.as<abi::IStringable>() };

    // Convert from an ABI type.
    uri = ptr.as<winrt::Uri>();
    winrt::IStringable uriAsIStringable{ ptr.as<winrt::IStringable>() };
}

Les implémentations des fonctions as appellent QueryInterface. Si vous souhaitez des conversions de plus bas niveau qui appellent uniquement AddRef, vous pouvez utiliser les fonctions utilitaires winrt::copy_to_abi et winrt::copy_from_abi. Cet exemple de code suivant ajoute ces conversions de niveau inférieur à l’exemple de code ci-dessus.

Important

Lors de l’interopérabilité avec les types ABI, il est essentiel que le type ABI utilisé correspond à l’interface par défaut de l’objet C++/WinRT. Sinon, les appels de méthodes sur le type ABI finissent par appeler des méthodes dans le même emplacement de table virtuelle sur l’interface par défaut avec des résultats très inattendus. Notez que winrt ::copy_to_abi ne protège pas contre ce problème au moment de la compilation, car il utilise void* pour tous les types ABI et suppose que l’appelant n’a pas fait attention à ne pas correspondre aux types. Cela permet d’éviter d’exiger que les en-têtes C++/WinRT référencent des en-têtes ABI lorsque les types ABI ne peuvent jamais être utilisés.

int main()
{
    // The code in main() already shown above remains here.

    // Lower-level conversions that only call AddRef.

    // Convert to an ABI type.
    ptr = nullptr;
    winrt::copy_to_abi(uriAsIStringable, *ptr.put_void());

    // Convert from an ABI type.
    uri = nullptr;
    winrt::copy_from_abi(uriAsIStringable, ptr.get());
    ptr = nullptr;
}

Voici d’autres techniques de conversions de bas niveau similaires, mais l’utilisation de pointeurs bruts vers des types d’interface ABI (ceux définis par les en-têtes du SDK Windows) cette fois.

    // The code in main() already shown above remains here.

    // Copy to an owning raw ABI pointer with copy_to_abi.
    abi::IStringable* owning{ nullptr };
    winrt::copy_to_abi(uriAsIStringable, *reinterpret_cast<void**>(&owning));

    // Copy from a raw ABI pointer.
    uri = nullptr;
    winrt::copy_from_abi(uriAsIStringable, owning);
    owning->Release();

Pour les conversions de plus bas niveau, qui se contentent de copier des adresses, vous pouvez utiliser les fonctions utilitaires winrt::get_abi, winrt::detach_abi et winrt::attach_abi.

WINRT_ASSERT est une définition de macro, et elle s’étend à _ASSERTE.

    // The code in main() already shown above remains here.

    // Lowest-level conversions that only copy addresses

    // Convert to a non-owning ABI object with get_abi.
    abi::IStringable* non_owning{ reinterpret_cast<abi::IStringable*>(winrt::get_abi(uriAsIStringable)) };
    WINRT_ASSERT(non_owning);

    // Avoid interlocks this way.
    owning = reinterpret_cast<abi::IStringable*>(winrt::detach_abi(uriAsIStringable));
    WINRT_ASSERT(!uriAsIStringable);
    winrt::attach_abi(uriAsIStringable, owning);
    WINRT_ASSERT(uriAsIStringable);

fonction convert_from_abi

Cette fonction d’assistance convertit un pointeur d’interface ABI brut en objet C++/WinRT équivalent, avec une surcharge minimale.

template <typename T>
T convert_from_abi(::IUnknown* from)
{
    T to{ nullptr }; // `T` is a projected type.

    winrt::check_hresult(from->QueryInterface(winrt::guid_of<T>(),
        winrt::put_abi(to)));

    return to;
}

La fonction appelle simplement QueryInterface pour rechercher l’interface par défaut du type C++/WinRT demandé.

Comme nous l’avons vu, une fonction d’assistance n’est pas nécessaire pour effectuer la conversion d’un objet C++/WinRT vers le pointeur d’interface ABI équivalent. Utilisez simplement la fonction membre winrt ::Windows ::Foundation ::IUnknown ::as (ou try_as) pour interroger l’interface demandée. Les fonctions as et try_as retournent un objet winrt ::com_ptr encapsulant le type ABI demandé.

Exemple de code utilisant convert_from_abi

Voici un exemple de code montrant cette fonction d’assistance dans la pratique.

// pch.h
#pragma once
#include <windows.foundation.h>
#include <unknwn.h>
#include "winrt/Windows.Foundation.h"

// main.cpp
#include "pch.h"
#include <iostream>

using namespace winrt;
using namespace Windows::Foundation;

namespace winrt
{
    using namespace Windows::Foundation;
}

namespace abi
{
    using namespace ABI::Windows::Foundation;
};

namespace sample
{
    template <typename T>
    T convert_from_abi(::IUnknown* from)
    {
        T to{ nullptr }; // `T` is a projected type.

        winrt::check_hresult(from->QueryInterface(winrt::guid_of<T>(),
            winrt::put_abi(to)));

        return to;
    }
    inline auto put_abi(winrt::hstring& object) noexcept
    {
        return reinterpret_cast<HSTRING*>(winrt::put_abi(object));
    }
}

int main()
{
    winrt::init_apartment();

    winrt::Uri uri(L"http://aka.ms/cppwinrt");
    std::wcout << "C++/WinRT: " << uri.Domain().c_str() << std::endl;

    // Convert to an ABI type.
    winrt::com_ptr<abi::IUriRuntimeClass> ptr = uri.as<abi::IUriRuntimeClass>();
    winrt::hstring domain;
    winrt::check_hresult(ptr->get_Domain(sample::put_abi(domain)));
    std::wcout << "ABI: " << domain.c_str() << std::endl;

    // Convert from an ABI type.
    winrt::Uri uri_from_abi = sample::convert_from_abi<winrt::Uri>(ptr.get());

    WINRT_ASSERT(uri.Domain() == uri_from_abi.Domain());
    WINRT_ASSERT(uri == uri_from_abi);
}

Interopérabilité avec des pointeurs d’interface COM ABI

Le modèle de fonction d’assistance ci-dessous montre comment copier un pointeur d’interface COM ABI d’un type donné vers son type de pointeur intelligent projeté C++/WinRT équivalent.

template<typename To, typename From>
To to_winrt(From* ptr)
{
    To result{ nullptr };
    winrt::check_hresult(ptr->QueryInterface(winrt::guid_of<To>(), winrt::put_abi(result)));
    return result;
}
...
ID2D1Factory1* com_ptr{ ... };
auto cppwinrt_ptr {to_winrt<winrt::com_ptr<ID2D1Factory1>>(com_ptr)};

Ce modèle de fonction utilitaire ci-après est équivalent, sauf qu’il effectue la copie depuis un type de pointeur intelligent de la bibliothèque Windows Implementation Libraries (WIL).

template<typename To, typename From, typename ErrorPolicy>
To to_winrt(wil::com_ptr_t<From, ErrorPolicy> const& ptr)
{
    To result{ nullptr };
    if constexpr (std::is_same_v<typename ErrorPolicy::result, void>)
    {
        ptr.query_to(winrt::guid_of<To>(), winrt::put_abi(result));
    }
    else
    {
        winrt::check_result(ptr.query_to(winrt::guid_of<To>(), winrt::put_abi(result)));
    }
    return result;
}

Consultez également Consommer des composants COM avec C++/WinRT.

Interopérabilité non sécurisée avec des pointeurs d’interface COM ABI

Le tableau suivant montre (en plus d’autres opérations) des conversions non sécurisées entre un pointeur d’interface COM ABI d’un type donné et son type de pointeur intelligent projeté C++/WinRT équivalent. Pour le code figurant dans le tableau, supposez les déclarations suivantes.

winrt::Sample s;
ISample* p;

void GetSample(_Out_ ISample** pp);

Supposons que ISample est l’interface par défaut pour Sample.

Vous pouvez le vérifier à la compilation avec ce code.

static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
Operation Comment procéder Notes
Extraire ISample* de winrt ::Sample p = reinterpret_cast<ISample*>(get_abi(s)); s possède toujours l’objet.
Détacher ISample* de winrt ::Sample p = reinterpret_cast<ISample*>(detach_abi(s)); s ne possède plus l’objet.
Transférer ISample* vers un nouveau winrt ::Sample winrt::Sample s{ p, winrt::take_ownership_from_abi }; s devient propriétaire de l’objet.
Définir ISample* dans winrt ::Sample *put_abi(s) = p; s prend possession de l’objet. Tout objet précédemment détenu par s provoque une fuite (déclenchera une assertion en mode débogage).
Recevoir ISample* dans winrt ::Sample GetSample(reinterpret_cast<ISample**>(put_abi(s))); s devient propriétaire de l’objet. Tout objet ayant appartenu auparavant à s provoque une fuite (déclenchera une assertion en mode débogage).
Remplacer ISample* dans winrt ::Sample attach_abi(s, p); s prend possession de l’objet. L’objet précédemment détenu par s est libéré.
Copier ISample* dans winrt ::Sample copy_from_abi(s, p); s crée une nouvelle référence vers l’objet. L’objet précédemment détenu par s est libéré.
Copier winrt ::Sample dans ISample* copy_to_abi(s, reinterpret_cast<void*&>(p)); p reçoit une copie de l’objet. Tout objet précédemment détenu par p provoque une fuite.

Interagir avec la structure GUID de l’ABI

GUID (/previous-versions/aa373931(v%3Dvs.80)) est projeté en tant que winrt ::guid. Pour les API que vous implémentez, vous devez utiliser winrt ::guid pour les paramètres GUID. Sinon, il existe des conversions automatiques entre winrt ::guid et GUID tant que vous incluez unknwn.h (implicitement inclus par <windows.h> et de nombreux autres fichiers d’en-tête) avant d’inclure les en-têtes C++/WinRT.

Si vous ne faites pas cela, alors vous pouvez difficilereinterpret_cast - entre eux. Pour le tableau suivant, supposez les déclarations suivantes.

winrt::guid winrtguid;
GUID abiguid;
Conversion Avec #include <unknwn.h> Sans #include <unknwn.h>
De winrt ::guid au GUID abiguid = winrtguid; abiguid = reinterpret_cast<GUID&>(winrtguid);
De GUID à winrt ::guid winrtguid = abiguid; winrtguid = reinterpret_cast<winrt::guid&>(abiguid);

Vous pouvez construire un winrt ::guid comme suit.

winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} };

Pour obtenir un gist montrant comment construire un winrt ::guid à partir d’une chaîne, consultez make_guid.cpp.

Interopérabilité avec le HSTRING d’ABI

Le tableau suivant montre les conversions entre winrt ::hstring et HSTRING et d’autres opérations. Pour le code figurant dans le tableau, supposons les déclarations suivantes.

winrt::hstring s;
HSTRING h;

void GetString(_Out_ HSTRING* value);
Operation Comment procéder Notes
Extraire HSTRING de hstring h = reinterpret_cast<HSTRING>(get_abi(s)); s possède toujours la chaîne.
Détacher HSTRING de hstring h = reinterpret_cast<HSTRING>(detach_abi(s)); s ne possède plus la chaîne.
Définir HSTRING en hstring *put_abi(s) = h; s prend possession de la chaîne. Toute chaîne précédemment possédée par s provoque une fuite mémoire (déclenchera une assertion en mode débogage).
Recevoir HSTRING dans hstring GetString(reinterpret_cast<HSTRING*>(put_abi(s))); s prend possession de la chaîne. Toute chaîne précédemment possédée par s fuit (déclenchera une assertion en mode debug).
Remplacer HSTRING dans hstring attach_abi(s, h); s prend possession de la chaîne. La chaîne précédemment détenue par s est libérée.
Copier HSTRING dans hstring copy_from_abi(s, h); s crée une copie privée de la chaîne. La chaîne précédemment détenue par s est libérée.
Copier hstring vers HSTRING copy_to_abi(s, reinterpret_cast<void*&>(h)); h reçoit une copie de la chaîne. Toute chaîne précédemment détenue par h est divulguée.

En outre, les fonctions utilitaires pour les chaînes de Windows Implementation Libraries (WIL) effectuent des opérations de base sur les chaînes. Pour utiliser les helpers de chaîne WIL, incluez <wil/resource.h> et reportez-vous au tableau ci-dessous. Suivez les liens du tableau pour obtenir des détails complets.

Operation Utilitaire WIL pour les chaînes de caractères pour plus d’informations
Fournissez un pointeur brut vers une chaîne Unicode ou ANSI et une longueur facultative ; obtenez une encapsulation unique_any convenablement spécialisée wil ::make_something_string
Déballer un objet intelligent jusqu’à trouver un pointeur brut vers une chaîne Unicode terminée par un caractère nul wil::str_raw_ptr
Obtenez la chaîne encapsulée par un objet pointeur intelligent ; ou la chaîne L"" vide si le pointeur intelligent est vide wil::string_get_not_null
Concaténer un nombre quelconque de chaînes wil::str_concat
Obtenir une chaîne à partir d’une chaîne de format de style printf et d’une liste de paramètres correspondante wil ::str_printf

API importantes