Interoperabilità tra C++/WinRT e ABI

In questo argomento viene illustrato come eseguire la conversione tra oggetti SDK ABI (Application Binary Interface) e C++/WinRT. 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.

In generale, C++/WinRT espone i tipi di ABI come void, in modo che non sia necessario includere i file di intestazione della piattaforma.

Nota

Negli esempi di codice si usa reinterpret_cast (anziché static_cast) nel tentativo di telegrafareciò che è intrinsecamente non sicuro.

Che cos'è l'ABI di Windows Runtime e quali sono i tipi di ABI?

Una classe di Windows Runtime (classe di runtime) è realmente un'astrazione. Questa astrazione definisce un'interfaccia binaria (Application Binary Interface o ABI) che consente ai diversi linguaggi di programmazione di interagire con un oggetto. 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.

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. Sono stati generati dal compilatore MIDL. Ecco un esempio di inclusione di una di queste intestazioni.

#include <windows.foundation.h>

Ed ecco un esempio semplificato di uno dei tipi di ABI che troverai nella specifica intestazione SDK. 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.

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. Inoltre, poiché la relativa base è IInspectableIUriRuntimeClass è un'interfaccia di Windows Runtime. Nota il tipo restituito HRESULT anziché la generazione di eccezioni e l'utilizzo di elementi come l'handle HSTRING (quando hai finito, ti consigliamo di impostare tale handle su nullptr). Questo dà un'idea di ciò a cui Windows Runtime assomiglia al livello binario dell'applicazione, in altri termini, al livello di programmazione COM.

Windows Runtime è basato su API Component Object Model (COM). Puoi accedere a Windows Runtime in questo modo oppure tramite proiezioni di linguaggio. Una proiezione nasconde i dettagli COM e offre un'esperienza di programmazione più naturale per un determinato linguaggio.

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. Esiste un'intestazione per ogni spazio dei nomi Windows, così come un'intestazione ABI per ogni spazio dei nomi Windows. Ecco un esempio di inclusione di una delle intestazioni C++/WinRT.

#include <winrt/Windows.Foundation.h>

E a partire da tale intestazione, ecco (semplificato) l'equivalente C++/WinRT del tipo ABI appena visto.

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

L'interfaccia qui è in C++ moderno standard. Rende superflui i HRESULT (C++/WinRT genera eccezioni, se necessario). E la funzione di accesso restituisce un oggetto stringa semplice, pulito alla fine del relativo ambito.

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).

Conversione da e verso tipi ABI nel codice

Per sicurezza e semplicità, per conversioni in entrambe le direzioni, puoi utilizzare winrt::com_ptr, com_ptr::as e 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.

// 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. Se desideri conversioni di livello inferiore che chiamano solo AddRef, puoi utilizzare le funzioni helper winrt::copy_to_abi e winrt::copy_from_abi. Questo esempio di codice successivo aggiunge queste conversioni di livello inferiore all'esempio di codice precedente.

Importante

Quando si interopera con tipi ABI, è fondamentale che il tipo ABI usato corrisponda all'interfaccia predefinita dell'oggetto C++/WinRT. 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. 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. Ciò consente di evitare che le intestazioni C++/WinRT facciano riferimento alle intestazioni ABI quando i tipi ABI non possono mai essere usati.

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).

    // 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.

WINRT_ASSERT è una definizione di macro e si espande in 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);

Funzione convert_from_abi

Questa funzione helper converte un puntatore di interfaccia ABI non elaborato in un oggetto C++/WinRT equivalente, con un sovraccarico minimo.

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.

Come abbiamo visto, una funzione helper non è necessaria per la conversione da un oggetto C++/WinRT al puntatore di interfaccia ABI equivalente. Utilizza semplicemente la funzione membro winrt::Windows::Foundation::IUnknown::as (o try_as) per eseguire una query per l'interfaccia richiesta. Le funzioni as e try_as restituiscono un oggetto winrt::com_ptr che esegue il wrapping del tipo ABI richiesto.

Esempio di codice che utilizza convert_from_abi

Ecco un esempio di codice che mostra questa funzione helper nella pratica.

// 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 ABI

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.

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).

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.

Interoperabilità non sicura con i puntatori dell'interfaccia COM ABI

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. Per il codice nella tabella presupponi queste dichiarazioni.

winrt::Sample s;
ISample* p;

void GetSample(_Out_ ISample** pp);

Presupponi inoltre che ISample sia l'interfaccia predefinita per Sample.

Puoi asserire questo presupposto in fase di compilazione con questo codice.

static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
Operation Procedura Note
Estrarre ISample* da winrt::Sample p = reinterpret_cast<ISample*>(get_abi(s)); s è ancora proprietario dell'oggetto.
Rimuovere ISample* da winrt::Sample p = reinterpret_cast<ISample*>(detach_abi(s)); s non è più proprietario dell'oggetto.
Trasferire ISample* in un nuovo winrt::Sample winrt::Sample s{ p, winrt::take_ownership_from_abi }; s acquisisce la proprietà dell'oggetto.
Impostare ISample* in winrt::Sample *put_abi(s) = p; s acquisisce la proprietà dell'oggetto. Qualsiasi oggetto in precedenza appartenuto a s viene perso (genera un'asserzione in modalità di debug).
Ricevere ISample* in winrt::Sample GetSample(reinterpret_cast<ISample**>(put_abi(s))); s acquisisce la proprietà dell'oggetto. Qualsiasi oggetto in precedenza appartenuto a s viene perso (genera un'asserzione in modalità di debug).
Sostituire ISample* con winrt::Sample attach_abi(s, p); s acquisisce la proprietà dell'oggetto. L'oggetto in precedenza appartenuto a s viene liberato.
Copiare ISample* in winrt::Sample copy_from_abi(s, p); s crea un nuovo riferimento all'oggetto. L'oggetto in precedenza appartenuto a s viene liberato.
Copiare winrt::Sample in ISample* copy_to_abi(s, reinterpret_cast<void*&>(p)); p riceve una copia dell'oggetto. Qualsiasi oggetto in precedenza appartenuto p viene perso.

Interoperabilità con lo struct GUID di ABI

GUID (/previous-versions/aa373931(v%3Dvs.80)) viene proiettato come winrt::guid. Per le API di cui esegui l'implementazione, devi usare winrt::guid per i parametri GUID. 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.

Se non esegui questa operazione, puoi forzare l'esecuzione di reinterpret_cast tra i due struct. Per la tabella seguente presupponi queste dichiarazioni.

winrt::guid winrtguid;
GUID abiguid;
Conversione Con #include <unknwn.h> Senza #include <unknwn.h>
Da winrt::guid a GUID abiguid = winrtguid; abiguid = reinterpret_cast<GUID&>(winrtguid);
Da GUID a winrt::guid winrtguid = abiguid; winrtguid = reinterpret_cast<winrt::guid&>(abiguid);

È possibile costruire un elemento winrt::guid come questo.

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.

Interoperabilità con HSTRING di ABI

La tabella seguente illustra le conversioni tra winrt::hstring e HSTRING e altre operazioni. Per il codice nella tabella presupponi queste dichiarazioni.

winrt::hstring s;
HSTRING h;

void GetString(_Out_ HSTRING* value);
Operation Procedura Note
Estrarre HSTRING da hstring h = reinterpret_cast<HSTRING>(get_abi(s)); s è ancora proprietario della stringa.
Rimuovere HSTRING da hstring h = reinterpret_cast<HSTRING>(detach_abi(s)); s non è più proprietario della stringa.
Impostare HSTRING in hstring *put_abi(s) = h; s acquisisce la proprietà della stringa. Qualsiasi stringa in precedenza appartenuta a s viene persa (genera un'asserzione in modalità di debug).
Ricevere HSTRING in hstring GetString(reinterpret_cast<HSTRING*>(put_abi(s))); s acquisisce la proprietà della stringa. Qualsiasi stringa in precedenza appartenuta a s viene persa (genera un'asserzione in modalità di debug).
Sostituire HSTRING con hstring attach_abi(s, h); s acquisisce la proprietà della stringa. La stringa in precedenza appartenuta a s viene liberata.
Copiare HSTRING in hstring copy_from_abi(s, h); s esegue una copia privata della stringa. La stringa in precedenza appartenuta a s viene liberata.
Copiare hstring in HSTRING copy_to_abi(s, reinterpret_cast<void*&>(h)); h riceve una copia della stringa. Qualsiasi stringa in precedenza appartenuta a h viene persa.

Inoltre, gli helper per le stringhe delle Librerie di implementazione di Windows (WIL) eseguono manipolazioni di base delle stringhe. Per usare gli helper per le stringhe WIL, includere <wil/resource.h> e fare riferimento alla tabella seguente. Seguire i collegamenti nella tabella per ottenere tutti i dettagli.

Operation Helper per le stringhe WIL per maggiori informazioni
Fornire un puntatore a stringa Unicode o ANSI non elaborato e una lunghezza opzionale; ottenere un wrapper unique_any opportunamente specializzato wil::make_something_string
Annullare il wrapping di un oggetto intelligente finché non viene trovato un puntatore a stringa Unicode non elaborato con terminazione null wil::str_raw_ptr
Ottenere la stringa incapsulata da un oggetto puntatore intelligente; o la stringa vuota L"" se il puntatore intelligente è vuoto wil::string_get_not_null
Concatenare un numero qualsiasi di stringhe wil::str_concat
Ottenere una stringa da una stringa di formato in stile printf e da un elenco di parametri corrispondente wil::str_printf

API importanti