Interoperabiliteit tussen C++/WinRT en de ABI

In dit onderwerp wordt beschreven hoe u converteert tussen binaire INTERFACE (ABI) voor SDK-toepassingen en C++/WinRT-objecten . U kunt deze technieken gebruiken om te interoppen tussen code die gebruikmaakt van deze twee manieren van programmeren met de Windows Runtime, of u kunt ze gebruiken wanneer u uw code geleidelijk van de ABI naar C++/WinRT verplaatst.

Over het algemeen maakt C++/WinRT ABI-typen beschikbaar als ongeldig*, zodat u geen platformheaderbestanden hoeft op te nemen.

Note

In de codevoorbeelden gebruiken we reinterpret_cast (in plaats van static_cast) om duidelijk te maken dat het om inherent onveilige typeconversies gaat.

Wat is de Windows Runtime ABI en wat zijn ABI-typen?

Een Windows Runtime-klasse (runtimeklasse) is echt een abstractie. Deze abstractie definieert een binaire interface (de binaire interface van de toepassing of ABI) waarmee verschillende programmeertalen kunnen communiceren met een object. Ongeacht de programmeertaal vindt interactie van clientcode met een Windows Runtime-object plaats op het laagste niveau, waarbij clienttaalconstructies worden omgezet in aanroepen naar de ABI van het object.

De Windows SDK-headers in de map%WindowsSdkDir%Include\10.0.17134.0\winrt" (pas zo nodig het SDK-versienummer voor uw case aan) zijn de Windows Runtime ABI-headerbestanden. Ze zijn geproduceerd door de MIDL-compiler. Hier volgt een voorbeeld van het opnemen van een van deze headers.

#include <windows.foundation.h>

Hier volgt een vereenvoudigd voorbeeld van een van de ABI-typen die u in die specifieke SDK-header vindt. Noteer de ABI-naamruimte; Windows::Foundation en alle andere Windows naamruimten worden gedeclareerd door de SDK-headers binnen de ABI-naamruimte.

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

IUriRuntimeClass is een COM-interface. Maar meer dan dat, omdat de basis IInspectable is, is IUriRuntimeClass een Windows Runtime interface. Let op het retourtype HRESULT, in plaats van het genereren van uitzonderingen. En het gebruik van artefacten zoals de HSTRING-ingang (het is een goede gewoonte om die ingang weer in nullptr te stellen wanneer u er klaar mee bent). Dit geeft een idee van hoe de Windows Runtime eruitziet op binair niveau van de toepassing, met andere woorden, op com-programmeerniveau.

De Windows Runtime is gebaseerd op COM-API's (Component Object Model). U hebt op die manier toegang tot de Windows Runtime of u kunt deze openen via taalprojecties. Een projectie verbergt de COM-details en biedt een natuurlijkere programmeerervaring voor een bepaalde taal.

Als u bijvoorbeeld in de map '%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt' zoekt (pas indien nodig het SDK-versienummer voor uw case aan), dan vindt u de C++/WinRT-taalprojectieheaders. Er is een header voor elke Windows naamruimte, net zoals er één ABI-header per Windows naamruimte is. Hier volgt een voorbeeld van het opnemen van een van de C++/WinRT-headers.

#include <winrt/Windows.Foundation.h>

En vanuit die header is hier (vereenvoudigd) het C++/WinRT-equivalent van dat ABI-type dat we zojuist hebben gezien.

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

De interface hier is modern, standaard C++. Het sluit af met HRESULTs (C++/WinRT genereert indien nodig uitzonderingen). En de accessorfunctie retourneert een eenvoudig tekenreeksobject, dat aan het einde van het bereik wordt opgeschoond.

Dit onderwerp is bedoeld voor situaties waarin u wilt interopereren met of code wilt porteren die op het niveau van de Application Binary Interface (ABI) werkt.

Converteren van en naar ABI-typen in code

Voor veiligheid en eenvoud kunt u voor conversies in beide richtingen gewoon winrt::com_ptr, com_ptr::as en winrt::Windows::Foundation::IUnknown::as gebruiken. Hier volgt een codevoorbeeld (op basis van de console-app-projectsjabloon ), die ook laat zien hoe u naamruimtealiassen voor de verschillende eilanden kunt gebruiken om andere mogelijke naamruimteconflicten tussen de C++/WinRT-projectie en de ABI af te handelen.

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

De implementaties van de as-functies roepen QueryInterface aan. Als u conversies op lager niveau wilt die alleen AddRef aanroepen, kunt u de helperfuncties winrt::copy_to_abi en winrt::copy_from_abi gebruiken. In dit volgende codevoorbeeld worden deze conversies op lager niveau toegevoegd aan het bovenstaande codevoorbeeld.

Belangrijk

Bij samenwerking met ABI-typen is het essentieel dat het gebruikte ABI-type overeenkomt met de standaardinterface van het C++/WinRT-object. Anders komen aanroepen van methoden op het ABI-type feitelijk uit bij het aanroepen van methoden in dezelfde vtable-slot van de standaardinterface, met zeer onverwachte resultaten. Houd er rekening mee dat winrt::copy_to_abi hier tijdens het compileren niet tegen beschermt, omdat het void* gebruikt voor alle ABI-typen en ervan uitgaat dat de aanroepende code zorgvuldig is geweest en de typen niet met elkaar verwisselt. Dit is om te voorkomen dat C++/WinRT-headers moeten verwijzen naar ABI-headers wanneer ABI-typen nooit kunnen worden gebruikt.

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

Hier zijn andere vergelijkbare conversietechnieken op laag niveau, maar dit keer met gebruik van raw pointers naar ABI-interfacetypes (zoals gedefinieerd in de Windows SDK-headers).

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

Voor de conversies op het laagste niveau, die alleen adressen kopiëren, kunt u de helperfuncties winrt::get_abi, winrt::d etach_abi en winrt::attach_abi gebruiken.

WINRT_ASSERT is een macrodefinitie en wordt uitgebreid naar _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);

convert_from_abi, functie

Met deze helperfunctie wordt een onbewerkte ABI-interfaceaanwijzer geconverteerd naar een equivalent C++/WinRT-object, met minimale overhead.

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

De functie roept QueryInterface aan om een query uit te voeren op de standaardinterface van het aangevraagde C++/WinRT-type.

Zoals we hebben gezien, is een helperfunctie niet vereist om te converteren van een C++/WinRT-object naar de equivalente ABI-interfacepointer. Gebruik gewoon de lidfunctie winrt::Windows::Foundation::IUnknown::as (of try_as) om een query uit te voeren op de aangevraagde interface. De functies as en try_as retourneren een winrt::com_ptr object dat het aangevraagde ABI-type verpakt.

Codevoorbeeld met convert_from_abi

Hier volgt een codevoorbeeld met deze helperfunctie in de praktijk.

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

Samenwerken met COM-interfacepointers van ABI

In het onderstaande sjabloon voor de helperfunctie wordt geïllustreerd hoe u een ABI COM-interfaceaanwijzer van een bepaald type kopieert naar het overeenkomstige slimme aanwijzertype van C++/WinRT.

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

Deze volgende helperfunctiesjabloon is gelijkwaardig, behalve dat deze wordt gekopieerd van het type slimme aanwijzer uit de Windows Implementatiebibliotheken (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;
}

Zie Ook COM-onderdelen gebruiken met C++/WinRT.

Onveilige interoperabiliteit met ABI COM-interfacepointers

De volgende tabel toont (naast andere bewerkingen) onveilige conversies tussen een ABI COM-interfacepointer van een bepaald type en het overeenkomstige geprojecteerde smartpointertype van C++/WinRT. Ga voor de code in de tabel uit van de volgende declaraties.

winrt::Sample s;
ISample* p;

void GetSample(_Out_ ISample** pp);

Stel verder dat ISample de standaardinterface is voor Sample.

U kunt bevestigen dat tijdens het compileren met deze code.

static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
Operation Hoe het te doen Notes
ISample* extraheren uit winrt::Sample p = reinterpret_cast<ISample*>(get_abi(s)); s is nog steeds eigenaar van het object.
ISample loskoppelen* van winrt::Sample p = reinterpret_cast<ISample*>(detach_abi(s)); s is niet langer eigenaar van het object.
ISample* overdragen naar nieuwe winrt::Sample winrt::Sample s{ p, winrt::take_ownership_from_abi }; s is eigenaar van het object.
ISample* instellen in winrt::Sample *put_abi(s) = p; s is eigenaar van het object. Elk object dat eerder eigendom is van s , wordt gelekt (zal bevestigen in foutopsporing).
Ontvang ISample* als winrt::Sample GetSample(reinterpret_cast<ISample**>(put_abi(s))); s is eigenaar van het object. Elk object dat eerder eigendom is van s , wordt gelekt (zal bevestigen in foutopsporing).
ISample* vervangen in winrt::Sample attach_abi(s, p); s is eigenaar van het object. Het object dat eerder eigendom is van s , wordt vrijgemaakt.
ISample* kopiëren naar winrt::Sample copy_from_abi(s, p); s maakt een nieuwe verwijzing naar het object. Het object dat eerder eigendom is van s , wordt vrijgemaakt.
Winrt::Sample naar ISample* kopiëren copy_to_abi(s, reinterpret_cast<void*&>(p)); p ontvangt een kopie van het object. Elk object dat eerder eigendom is van p , wordt gelekt.

Samenwerken met de GUID-struct van de ABI

GUID (/previous-versions/aa373931(v%3Dvs.80)) wordt geprojecteerd als winrt::guid. Voor API's die u implementeert, moet u winrt::guid gebruiken voor GUID-parameters. Anders zijn er automatische conversies tussen winrt::guid en GUID zolang u opneemt unknwn.h (impliciet opgenomen door <windows.h> en vele andere headerbestanden) voordat u C++/WinRT-headers opneemt.

Als je dat niet doet, kun je er hardreinterpret_cast tussen zitten. Ga voor de onderstaande tabel uit van de volgende declaraties.

winrt::guid winrtguid;
GUID abiguid;
Conversion Met #include <unknwn.h> Zonder #include <unknwn.h>
Van winrt::guid naar GUID abiguid = winrtguid; abiguid = reinterpret_cast<GUID&>(winrtguid);
Van GUID naar winrt::guid winrtguid = abiguid; winrtguid = reinterpret_cast<winrt::guid&>(abiguid);

U kunt als volgt een winrt::guid maken.

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

Zie make_guid.cpp voor een voorbeeld waarin wordt getoond hoe u een winrt::guid construeert uit een tekenreeks.

Samenwerken met de HSTRING van de ABI

De volgende tabel bevat conversies tussen winrt::hstring en HSTRING en andere bewerkingen. Ga voor de code in de tabel uit van de volgende declaraties.

winrt::hstring s;
HSTRING h;

void GetString(_Out_ HSTRING* value);
Operation Hoe het te doen Notes
HSTRING extraheren uit hstring h = reinterpret_cast<HSTRING>(get_abi(s)); s is nog steeds eigenaar van de tekenreeks.
HSTRING loskoppelen van hstring h = reinterpret_cast<HSTRING>(detach_abi(s)); s is niet langer eigenaar van de tekenreeks.
HSTRING instellen in hstring *put_abi(s) = h; s neemt het eigenaarschap van de tekenreeks over. Elke tekenreeks die eerder in het bezit was van s, lekt (zal een assertie veroorzaken in debugmodus).
HSTRING ontvangen in hstring GetString(reinterpret_cast<HSTRING*>(put_abi(s))); s neemt het eigenaarschap van de tekenreeks over. Elke tekenreeks die eerder eigendom is van s , wordt gelekt (wordt in foutopsporing weergegeven).
HSTRING vervangen in hstring attach_abi(s, h); s neemt het eigenaarschap van de tekenreeks over. De tekenreeks die eerder in bezit was van s, wordt vrijgegeven.
HSTRING kopiëren naar hstring copy_from_abi(s, h); s maakt een eigen kopie van de tekenreeks. De tekenreeks die eerder eigendom is van s , wordt vrijgemaakt.
HSTRING kopiëren naar HSTRING copy_to_abi(s, reinterpret_cast<void*&>(h)); h ontvangt een kopie van de tekenreeks. Elke tekenreeks die eerder in bezit was van h, lekt.

Daarnaast voeren de Windows wil-tekenreekshulpen (Implementation Libraries) eenvoudige tekenreeksbewerkingen uit. Als u de WIL-stringhelpers wilt gebruiken, neemt u <wil/resource.h> op en raadpleegt u de onderstaande tabel. Volg de koppelingen in de tabel voor volledige details.

Operation WIL-tekenreekshulp voor meer informatie
Geef een onbewerkte Unicode- of ANSI-tekenreeksaanwijzer en een optionele lengte op; een geschikte unique_any wrapper verkrijgen wil::make_something_string
Een smartobject uitvouwen totdat een onbewerkte pointer naar een null-beëindigde Unicode-tekenreeks is gevonden wil::str_raw_ptr
Haal de tekenreeks op die is verpakt door een slim aanwijzerobject; of de lege tekenreeks L"" als de slimme aanwijzer leeg is wil::string_get_not_null
Een willekeurig aantal tekenreeksen samenvoegen wil::str_concat
Een tekenreeks ophalen op basis van een printf-opmaakreeks en de bijbehorende parameterlijst wil::str_printf

Belangrijke API's