Interoperabilità tra C++/WinRT e ABIInterop between C++/WinRT and the ABI

In questo argomento viene illustrato come eseguire la conversione tra oggetti SDK ABI (Application Binary Interface) e C++/WinRT.This topic shows how to convert between SDK application binary interface (ABI) and C++/WinRT objects. Puoi utilizzare queste tecniche per consentire l'interoperabilità tra codice che usa questi due metodi di programmazione con Windows Runtime oppure puoi utilizzarle man mano che trasferisci il tuo codice da ABI a C++/WinRT.You can use these techniques to interop between code that uses these two ways of programming with the Windows Runtime, or you can use them as you gradually move your code from the ABI to C++/WinRT.

In generale, C++/WinRT espone i tipi di ABI come void* , in modo che non sia necessario includere i file di intestazione della piattaforma.In general, C++/WinRT exposes ABI types as void*, so that you don't need to include platform header files.

Che cos'è l'ABI di Windows Runtime e quali sono i tipi di ABI?What is the Windows Runtime ABI, and what are ABI types?

Una classe di Windows Runtime (classe di runtime) è realmente un'astrazione.A Windows Runtime class (runtime class) is really an abstraction. Questa astrazione definisce un'interfaccia binaria (Application Binary Interface o ABI) che consente ai diversi linguaggi di programmazione di interagire con un oggetto.This abstraction defines a binary interface (the Application Binary Interface, or ABI) that allows various programming languages to interact with an object. Indipendentemente dal linguaggio di programmazione, l'interazione di codice client con un oggetto Windows Runtime si verifica al livello più basso, con costrutti di linguaggio client convertiti in chiamate nell'ABI dell'oggetto.Regardless of programming language, client code interaction with a Windows Runtime object happens at the lowest level, with client language constructs translated into calls into the object's ABI.

Le intestazioni di Windows SDK nella cartella "%WindowsSdkDir%Include\10.0.17134.0\winrt" (adatta il numero di versione dell'SDK al tuo caso, se necessario) sono i file di intestazione ABI di Windows Runtime.The Windows SDK headers in the folder "%WindowsSdkDir%Include\10.0.17134.0\winrt" (adjust the SDK version number for your case, if necessary), are the Windows Runtime ABI header files. Sono stati generati dal compilatore MIDL.They were produced by the MIDL compiler. Ecco un esempio di inclusione di una di queste intestazioni.Here's an example of including one of these headers.

#include <windows.foundation.h>

Ed ecco un esempio semplificato di uno dei tipi di ABI che troverai nella specifica intestazione SDK.And here's a simplified example of one of the ABI types that you'll find in that particular SDK header. Tieni presente che lo spazio dei nomi ABI, Windows::Foundation e tutti gli altri spazi dei nomi Windows, vengono dichiarati dalle intestazioni SDK all'interno dello spazio dei nomi ABI.Note the ABI namespace; Windows::Foundation, and all other Windows namespaces, are declared by the SDK headers within the ABI namespace.

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

IUriRuntimeClass è un'interfaccia COM.IUriRuntimeClass is a COM interface. Inoltre, poiché la relativa base è IInspectable, IUriRuntimeClass è un'interfaccia di Windows Runtime.But more than that—since its base is IInspectableIUriRuntimeClass is a Windows Runtime interface. Nota il tipo restituito HRESULT anziché la generazione di eccezioniNote the HRESULT return type, rather than the raising of exceptions. e l'utilizzo di elementi come l'handle HSTRING (quando hai finito, ti consigliamo di impostare tale handle su nullptr).And the use of artifacts such as the HSTRING handle (it's good practice to set that handle back to nullptr when you're finished with it). Questo dà un'idea di ciò a cui Windows Runtime assomiglia al livello binario dell'applicazione, in altri termini, al livello di programmazione COM.This gives a taste of what the Windows Runtime looks like at the application binary level; in other words, at the COM programming level.

Windows Runtime è basato su API Component Object Model (COM).The Windows Runtime is based on Component Object Model (COM) APIs. Puoi accedere a Windows Runtime in questo modo oppure tramite proiezioni di linguaggio.You can access the Windows Runtime that way, or you can access it through language projections. Una proiezione nasconde i dettagli COM e offre un'esperienza di programmazione più naturale per un determinato linguaggio.A projection hides the COM details, and provides a more natural programming experience for a given language.

Ad esempio, se cerchi nella cartella ""%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt" (ancora una volta, adatta il numero di versione SDK al tuo caso, se necessario), troverai le intestazioni di proiezione di linguaggio C++/WinRT.For example, if you look in the folder "%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt" (again, adjust the SDK version number for your case, if necessary), then you'll find the C++/WinRT language projection headers. Esiste un'intestazione per ogni spazio dei nomi Windows, così come un'intestazione ABI per ogni spazio dei nomi Windows.There's a header for each Windows namespace, just like there's one ABI header per Windows namespace. Ecco un esempio di inclusione di una delle intestazioni C++/WinRT.Here's an example of including one of the C++/WinRT headers.

#include <winrt/Windows.Foundation.h>

E a partire da tale intestazione, ecco (semplificato) l'equivalente C++/WinRT del tipo ABI appena visto.And, from that header, here (simplified) is the C++/WinRT equivalent of that ABI type we just saw.

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

L'interfaccia qui è in C++ moderno standard.The interface here is modern, standard C++. Rende superflui i HRESULT (C++/WinRT genera eccezioni, se necessario).It does away with HRESULTs (C++/WinRT raises exceptions if necessary). E la funzione di accesso restituisce un oggetto stringa semplice, pulito alla fine del relativo ambito.And the accessor function returns a simple string object, which is cleaned up at the end of its scope.

Questo argomento è per i casi in cui si cerca l'interoperabilità con, o la conversione di codice che opera a livello di ABI (Application Binary Interface).This topic is for cases when you want to interop with, or port, code that works at the Application Binary Interface (ABI) layer.

Conversione da e verso tipi ABI nel codiceConverting to and from ABI types in code

Per sicurezza e semplicità, per conversioni in entrambe le direzioni, puoi utilizzare winrt::com_ptr, com_ptr::as e winrt::Windows::Foundation::IUnknown::as.For safety and simplicity, for conversions in both directions you can simply use winrt::com_ptr, com_ptr::as, and winrt::Windows::Foundation::IUnknown::as. Ecco un esempio di codice (basato sul modello di progetto app console), che illustra inoltre come puoi utilizzare gli alias degli spazi dei nomi per fare in modo che le diverse isole gestiscano i conflitti di spazio dei nomi altrimenti possibili tra la proiezione C++/WinRT e ABI.Here's a code example (based on the Console App project template), which also illustrates how you can use namespace aliases for the different islands to deal with otherwise potential namespace collisions between the C++/WinRT projection and the 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>() };
}

Le implementazioni delle funzioni as chiamano QueryInterface.The implementations of the as functions call QueryInterface. Se desideri conversioni di livello inferiore che chiamano solo AddRef, puoi utilizzare le funzioni helper winrt::copy_to_abi e winrt::copy_from_abi.If you want lower-level conversions that only call AddRef, then you can use the winrt::copy_to_abi and winrt::copy_from_abi helper functions. Questo esempio di codice successivo aggiunge queste conversioni di livello inferiore all'esempio di codice precedente.This next code example adds these lower-level conversions to the code example above.

Importante

Quando si interopera con tipi ABI, è fondamentale che il tipo ABI usato corrisponda all'interfaccia predefinita dell'oggetto C++/WinRT.When interoperating with ABI types it's critical that the ABI type used corresponds to the default interface of the C++/WinRT object. In caso contrario, le chiamate di metodi sul tipo ABI finiranno effettivamente chiamando metodi nello stesso slot vtable sull'interfaccia predefinita con risultati molto imprevisti.Otherwise, invocations of methods on the ABI type will actually end up calling methods in the same vtable slot on the default interface with very unexpected results. Si noti che winrt::copy_to_abi non protegge da questo in fase di compilazione perché usa void* per tutti i tipi ABI e presuppone che il chiamante abbia fatto attenzione alla mancata corrispondenza tra i tipi.Note that winrt::copy_to_abi does not protect against this at compile time since it uses void* for all ABI types and assumes that the caller has been careful not to mis-match the types. Ciò consente di evitare che le intestazioni C++/WinRT facciano riferimento alle intestazioni ABI quando i tipi ABI non possono mai essere usati.This is to avoid requiring C++/WinRT headers to reference ABI headers when ABI types may never be used.

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

Ecco altre tecniche di conversioni di basso livello simili, ma questa volta utilizzano puntatori non elaborati ai tipi di interfaccia ABI (quelli definiti dalle intestazioni di Windows SDK).Here are other similarly low-level conversions techniques but using raw pointers to ABI interface types (those defined by the Windows SDK headers) this time.

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

Per le conversioni di livello più basso, che consentono di copiare solo indirizzi, puoi utilizzare le funzioni helper winrt::get_abi, winrt::detach_abi e winrt::attach_abi.For the lowest-level conversions, which only copy addresses, you can use the winrt::get_abi, winrt::detach_abi, and winrt::attach_abi helper functions.

WINRT_ASSERT è una definizione di macro e si espande in ASSERTE.WINRT_ASSERT is a macro definition, and it expands to _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{ static_cast<abi::IStringable*>(winrt::get_abi(uriAsIStringable)) };
    WINRT_ASSERT(non_owning);

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

Funzione convert_from_abiconvert_from_abi function

Questa funzione helper converte un puntatore di interfaccia ABI non elaborato in un oggetto C++/WinRT equivalente, con un sovraccarico minimo.This helper function converts a raw ABI interface pointer to an equivalent C++/WinRT object, with minimal 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;
}

La funzione chiama semplicemente QueryInterface per eseguire una query per l'interfaccia predefinita del tipo C++/WinRT richiesto.The function simply calls QueryInterface to query for the default interface of the requested C++/WinRT type.

Come abbiamo visto, una funzione helper non è necessaria per la conversione da un oggetto C++/WinRT al puntatore di interfaccia ABI equivalente.As we've seen, a helper function is not required to convert from a C++/WinRT object to the equivalent ABI interface pointer. Utilizza semplicemente la funzione membro winrt::Windows::Foundation::IUnknown::as (o try_as) per eseguire una query per l'interfaccia richiesta.Simply use the winrt::Windows::Foundation::IUnknown::as (or try_as) member function to query for the requested interface. Le funzioni as e try_as restituiscono un oggetto winrt::com_ptr che esegue il wrapping del tipo ABI richiesto.The as and try_as functions return a winrt::com_ptr object wrapping the requested ABI type.

Esempio di codice che utilizza convert_from_abiCode example using convert_from_abi

Ecco un esempio di codice che mostra questa funzione helper nella pratica.Here's a code example showing this helper function in practice.

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

Interoperabilità con i puntatori dell'interfaccia COM ABIInteroperating with ABI COM interface pointers

Il modello di funzione helper seguente illustra come copiare un puntatore dell'interfaccia COM ABI di un determinato tipo nel tipo equivalente di puntatore intelligente proiettato di C++/WinRT.The helper function template below illustrates how to copy an ABI COM interface pointer of a given type to its equivalent C++/WinRT projected smart pointer type.

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

Il modello di funzione helper seguente è equivalente, ad eccezione del fatto che copia i dati del tipo di puntatore intelligente dalle librerie di implementazione di Windows (WIL).This next helper function template is equivalent, except that it copies from the smart pointer type from the 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;
}

Vedi anche Utilizzare componenti COM con C++/WinRT.Also see Consume COM components with C++/WinRT.

Interoperabilità non sicura con i puntatori dell'interfaccia COM ABIUnsafe interop with ABI COM interface pointers

La tabella seguente illustra (tra le altre operazioni) le conversioni non sicure tra un puntatore dell'interfaccia COM ABI di un determinato tipo e il tipo equivalente di puntatore intelligente proiettato di C++/WinRT.The table that follows shows (in addition to other operations) unsafe conversions between an ABI COM interface pointer of a given type and its equivalent C++/WinRT projected smart pointer type. Per il codice nella tabella presupponi queste dichiarazioni.For the code in the table, assume these declarations.

winrt::Sample s;
ISample* p;

void GetSample(_Out_ ISample** pp);

Presupponi inoltre che ISample sia l'interfaccia predefinita per Sample.Assume further that ISample is the default interface for Sample.

Puoi asserire questo presupposto in fase di compilazione con questo codice.You can assert that at compile time with this code.

static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
OperazioneOperation Come eseguirlaHow to do it NoteNotes
Estrarre ISample* da winrt::SampleExtract ISample* from winrt::Sample p = reinterpret_cast<ISample*>(get_abi(s)); s è ancora proprietario dell'oggetto.s still owns the object.
Rimuovere ISample* da winrt::SampleDetach ISample* from winrt::Sample p = reinterpret_cast<ISample*>(detach_abi(s)); s non è più proprietario dell'oggetto.s no longer owns the object.
Trasferire ISample* in un nuovo winrt::SampleTransfer ISample* to new winrt::Sample winrt::Sample s{ p, winrt::take_ownership_from_abi }; s acquisisce la proprietà dell'oggetto.s takes ownership of the object.
Impostare ISample* in winrt::SampleSet ISample* into winrt::Sample *put_abi(s) = p; s acquisisce la proprietà dell'oggetto.s takes ownership of the object. Qualsiasi oggetto in precedenza appartenuto a s viene perso (genera un'asserzione in modalità di debug).Any object previously owned by s is leaked (will assert in debug).
Ricevere ISample* in winrt::SampleReceive ISample* into winrt::Sample GetSample(reinterpret_cast<ISample**>(put_abi(s))); s acquisisce la proprietà dell'oggetto.s takes ownership of the object. Qualsiasi oggetto in precedenza appartenuto a s viene perso (genera un'asserzione in modalità di debug).Any object previously owned by s is leaked (will assert in debug).
Sostituire ISample* con winrt::SampleReplace ISample* in winrt::Sample attach_abi(s, p); s acquisisce la proprietà dell'oggetto.s takes ownership of the object. L'oggetto in precedenza appartenuto a s viene liberato.The object previously owned by s is freed.
Copiare ISample* in winrt::SampleCopy ISample* to winrt::Sample copy_from_abi(s, p); s crea un nuovo riferimento all'oggetto.s makes a new reference to the object. L'oggetto in precedenza appartenuto a s viene liberato.The object previously owned by s is freed.
Copiare winrt::Sample in ISample*Copy winrt::Sample to ISample* copy_to_abi(s, reinterpret_cast<void*&>(p)); p riceve una copia dell'oggetto.p receives a copy of the object. Qualsiasi oggetto in precedenza appartenuto p viene perso.Any object previously owned by p is leaked.

Interoperabilità con lo struct GUID di ABIInteroperating with the ABI's GUID struct

GUID viene proiettato come winrt::guid.GUID is projected as winrt::guid. Per le API di cui esegui l'implementazione, devi usare winrt::guid per i parametri GUID.For APIs that you implement, you must use winrt::guid for GUID parameters. In alternativa, vengono eseguite conversioni automatiche tra winrt::guid e GUID purché tu includa unknwn.h (incluso implicitamente da <windows.h> e molti altri file di intestazione) prima di includere qualsiasi intestazione C++/WinRT.Otherwise, there are automatic conversions between winrt::guid and GUID as long as you include unknwn.h (implicitly included by <windows.h> and many other header files) before you include any C++/WinRT headers.

Se non esegui questa operazione, puoi forzare l'esecuzione di reinterpret_cast tra i due struct.If you don't do that, then you can hard-reinterpret_cast between them. Per la tabella seguente presupponi queste dichiarazioni.For the table that follows, assume these declarations.

winrt::guid winrtguid;
GUID abiguid;
ConversioneConversion Con #include <unknwn.h>With #include <unknwn.h> Senza #include <unknwn.h>Without #include <unknwn.h>
Da winrt::guid a GUIDFrom winrt::guid to GUID abiguid = winrtguid; abiguid = reinterpret_cast<GUID&>(winrtguid);
Da GUID a winrt::guidFrom GUID to winrt::guid winrtguid = abiguid; winrtguid = reinterpret_cast<winrt::guid&>(abiguid);

È possibile costruire un elemento winrt::guid come questo.You can construct a winrt::guid like this.

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

Per una sintesi su come costruire un elemento winrt::guid da una stringa, vedere make_guid.cpp.For a gist showing how to construct a winrt::guid from a string, see make_guid.cpp.

Interoperabilità con HSTRING di ABIInteroperating with the ABI's HSTRING

La tabella seguente illustra le conversioni tra winrt::hstring e HSTRING e altre operazioni.The table that follows shows conversions between winrt::hstring and HSTRING, and other operations. Per il codice nella tabella presupponi queste dichiarazioni.For the code in the table, assume these declarations.

winrt::hstring s;
HSTRING h;

void GetString(_Out_ HSTRING* value);
OperazioneOperation Come eseguirlaHow to do it NoteNotes
Estrarre HSTRING da hstringExtract HSTRING from hstring h = static_cast<HSTRING>(get_abi(s)); s è ancora proprietario della stringa.s still owns the string.
Rimuovere HSTRING da hstringDetach HSTRING from hstring h = reinterpret_cast<HSTRING>(detach_abi(s)); s non è più proprietario della stringa.s no longer owns the string.
Impostare HSTRING in hstringSet HSTRING into hstring *put_abi(s) = h; s acquisisce la proprietà della stringa.s takes ownership of string. Qualsiasi stringa in precedenza appartenuta a s viene persa (genera un'asserzione in modalità di debug).Any string previously owned by s is leaked (will assert in debug).
Ricevere HSTRING in hstringReceive HSTRING into hstring GetString(reinterpret_cast<HSTRING*>(put_abi(s))); s acquisisce la proprietà della stringa.s takes ownership of string. Qualsiasi stringa in precedenza appartenuta a s viene persa (genera un'asserzione in modalità di debug).Any string previously owned by s is leaked (will assert in debug).
Sostituire HSTRING con hstringReplace HSTRING in hstring attach_abi(s, h); s acquisisce la proprietà della stringa.s takes ownership of string. La stringa in precedenza appartenuta a s viene liberata.The string previously owned by s is freed.
Copiare HSTRING in hstringCopy HSTRING to hstring copy_from_abi(s, h); s esegue una copia privata della stringa.s makes a private copy of the string. La stringa in precedenza appartenuta a s viene liberata.The string previously owned by s is freed.
Copiare hstring in HSTRINGCopy hstring to HSTRING copy_to_abi(s, reinterpret_cast<void*&>(h)); h riceve una copia della stringa.h receives a copy of the string. Qualsiasi stringa in precedenza appartenuta a h viene persa.Any string previously owned by h is leaked.

API importantiImportant APIs