Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questo argomento illustra come convertire tra l'interfaccia binaria dell'applicazione (ABI) dell'SDK e gli oggetti C++/WinRT. È possibile usare queste tecniche per interagire tra il codice che usa questi due modi di programmazione con il Windows Runtime oppure è possibile usarle man mano che si sposta gradualmente il codice dall'ABI a C++/WinRT.
In generale, C++/WinRT espone i tipi ABI come void*, in modo che non sia necessario includere i file di intestazione della piattaforma.
Note
Negli esempi di codice si usa reinterpret_cast (anziché static_cast) nel tentativo di telegrafare ciò che è intrinsecamente non sicuro.
Che cos'è l'ABI Windows Runtime e quali sono i tipi ABI?
Una classe Windows Runtime (classe di runtime) è in realtà un'astrazione. Questa astrazione definisce un'interfaccia binaria (Application Binary Interface o ABI) che consente a vari linguaggi di programmazione di interagire con un oggetto . Indipendentemente dal linguaggio di programmazione, l'interazione del codice client con un oggetto Windows Runtime avviene al livello più basso, con costrutti del linguaggio client convertiti in chiamate nell'ABI dell'oggetto.
I file di intestazione di Windows SDK nella cartella "%WindowsSdkDir%Include\10.0.17134.0\winrt" (se necessario, modificare il numero di versione dell'SDK in base al proprio caso) sono i file di intestazione dell'ABI di Windows Runtime. Sono stati prodotti dal compilatore MIDL. Ecco un esempio di inclusione di una di queste intestazioni.
#include <windows.foundation.h>
Di seguito è riportato un esempio semplificato di uno dei tipi ABI disponibili nell'intestazione dell'SDK specifico. Prendere nota dello 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. Ma più di questo, poiché la base è IInspectable, IUriRuntimeClass è un'interfaccia Windows Runtime. Si noti che il tipo restituito è HRESULT, anziché il sollevamento di eccezioni. E l'uso di artefatti come l'handle HSTRING (è consigliabile impostare di nuovo tale handle su nullptr al termine dell'operazione). In questo modo, il Windows Runtime ha un aspetto simile al livello binario dell'applicazione, in altre parole, a livello di programmazione COM.
Il Windows Runtime si basa sulle API COM (Component Object Model). È possibile accedere al Windows Runtime in questo modo oppure è possibile accedervi tramite proiezioni del linguaggio. Una proiezione nasconde i dettagli COM e offre un'esperienza di programmazione più naturale per un determinato linguaggio.
Ad esempio, se si cerca nella cartella "%WindowsSdkDir%Include\10.0.17134.0\cppwinrt\winrt" (di nuovo, modificare il numero di versione dell'SDK per il caso, se necessario), si troveranno le intestazioni di proiezione del linguaggio C++/WinRT. Esiste un'intestazione per ogni spazio dei nomi Windows, proprio come è presente 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, da quell'intestazione, ecco qui la versione (semplificata) in C++/WinRT dell'equivalente di quel tipo ABI che abbiamo appena visto.
namespace winrt::Windows::Foundation
{
struct Uri : IUriRuntimeClass, ...
{
winrt::hstring AbsoluteUri() const { ... }
...
};
}
L'interfaccia qui è moderna e standard C++. Elimina HRESULTs (C++/WinRT genera eccezioni, se necessario). E la funzione di accesso restituisce un semplice oggetto stringa, che viene eliminato al termine del proprio ambito.
Questo argomento riguarda i casi in cui si desidera interoperare con, o portare, codice che opera a livello di Application Binary Interface (ABI).
Conversione tra e dai tipi ABI nel codice
Per motivi di sicurezza e semplicità, per le conversioni in entrambe le direzioni è sufficiente usare 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 anche come usare gli alias dello spazio dei nomi per le diverse isole per gestire eventuali conflitti di spazio dei nomi tra la proiezione C++/WinRT e 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>() };
}
Le implementazioni di as funzioni chiamano QueryInterface. Se vuoi conversioni di livello inferiore che chiamano solo AddRef, puoi usare 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 interagisce con i 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 finiscono effettivamente per chiamare metodi nello stesso slot di tabella virtuale sull'interfaccia predefinita con risultati molto imprevisti. Si noti che winrt::copy_to_abi non offre protezione da questo errore a tempo di compilazione, poiché usa void* per tutti i tipi ABI e presuppone che il chiamante faccia attenzione a non associare tipi non corrispondenti. Questo serve a evitare di dover richiedere i file di intestazione C++/WinRT per fare riferimento ai file di intestazione ABI quando i tipi ABI potrebbero non essere mai 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;
}
Di seguito sono riportate altre tecniche di conversione di basso livello, ma questa volta si usano puntatori non elaborati ai tipi di interfaccia ABI (quelli definiti dalle intestazioni 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 copiano solo gli indirizzi, puoi usare le funzioni helper winrt::get_abi, winrt::d etach_abi e winrt::attach_abi .
WINRT_ASSERT è una definizione di macro e si espande fino a _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 overhead 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 si è visto, non è necessaria una funzione helper per eseguire la conversione da un oggetto C++/WinRT al puntatore dell'interfaccia ABI equivalente. Usa 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 usa convert_from_abi
Ecco un esempio di codice che mostra questa funzione helper in 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 di interfaccia COM ABI
Il modello di funzione di supporto seguente illustra come copiare un puntatore a un'interfaccia COM ABI di un dato tipo nel corrispondente tipo di puntatore intelligente proiettato 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 seguente di funzione helper è equivalente, tranne per il fatto che copia dal tipo di smart pointer di 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.
Interoperabilità non sicura con puntatori a interfacce COM ABI
La tabella seguente mostra (oltre ad altre operazioni) conversioni non sicure tra un puntatore all'interfaccia COM ABI di un determinato tipo e il relativo tipo di puntatore intelligente proiettato in C++/WinRT equivalente. Per il codice nella tabella, presupporre queste dichiarazioni.
winrt::Sample s;
ISample* p;
void GetSample(_Out_ ISample** pp);
Si supponga inoltre che ISample sia l'interfaccia predefinita per Sample.
Puoi verificarlo in fase di compilazione con questo codice.
static_assert(std::is_same_v<winrt::default_interface<winrt::Sample>, winrt::ISample>);
| Operation | Come eseguire questa operazione | Notes |
|---|---|---|
| Estrarre ISample* da winrt::Sample | p = reinterpret_cast<ISample*>(get_abi(s)); |
s possiede ancora l'oggetto. |
| Disconnettere ISample* da winrt::Sample | p = reinterpret_cast<ISample*>(detach_abi(s)); |
s non è più proprietario dell'oggetto . |
| Trasferire ISample* a 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 precedentemente di proprietà di s viene trapelato (asserisce nel debug). |
| Ricezione di ISample* in winrt::Sample | GetSample(reinterpret_cast<ISample**>(put_abi(s))); |
s acquisisce la proprietà dell'oggetto. Qualsiasi oggetto precedentemente di proprietà di s viene trapelato (asserisce nel debug). |
| Sostituire ISample* in winrt::Sample | attach_abi(s, p); |
s acquisisce la proprietà dell'oggetto. L'oggetto precedentemente di proprietà di s viene liberato. |
| Copiare ISample* in winrt::Sample | copy_from_abi(s, p); |
s crea un nuovo riferimento all'oggetto . L'oggetto precedentemente di proprietà di s viene liberato. |
| Copiare winrt::Sample in ISample* | copy_to_abi(s, reinterpret_cast<void*&>(p)); |
p riceve una copia dell'oggetto . Qualsiasi oggetto che era precedentemente di proprietà di p viene divulgato. |
Interoperabilità con lo struct GUID di ABI
GUID (/previous-versions/aa373931(v%3Dvs.80)) viene proiettato come winrt::guid. Per le API implementate, è necessario usare winrt::guid per i parametri GUID. In caso contrario, esistono conversioni automatiche tra winrt::guid e GUID , purché tu includa unknwn.h (implicitamente incluso da <windows.h> e molti altri file di intestazione) prima di includere eventuali intestazioni C++/WinRT.
Se non lo fai, puoi eseguire un hard-reinterpret_cast tra di essi. Per la tabella seguente, presupporre queste dichiarazioni.
winrt::guid winrtguid;
GUID abiguid;
| Conversion | 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); |
Puoi costruire un winrt::guid come questo.
winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} };
Per un gist che mostra come costruire un winrt::guid da una stringa, vedi make_guid.cpp.
Interoperabilità con HSTRING di ABI
La tabella seguente mostra le conversioni tra winrt::hstring e HSTRING e altre operazioni. Per il codice nella tabella, presupporre queste dichiarazioni.
winrt::hstring s;
HSTRING h;
void GetString(_Out_ HSTRING* value);
| Operation | Come eseguire questa operazione | Notes |
|---|---|---|
| Estrarre HSTRING da hstring | h = reinterpret_cast<HSTRING>(get_abi(s)); |
s è ancora proprietario della stringa. |
| Scollega HSTRING da hstring | h = reinterpret_cast<HSTRING>(detach_abi(s)); |
s non possiede più la stringa. |
| Impostare HSTRING in hstring | *put_abi(s) = h; |
s acquisisce la proprietà della stringa. Qualsiasi stringa di proprietà di s in precedenza viene persa (verrà eseguita l'asserzione nel debug). |
| Ricevere HSTRING in hstring | GetString(reinterpret_cast<HSTRING*>(put_abi(s))); |
s acquisisce la proprietà della stringa. Qualsiasi stringa di proprietà di s in precedenza viene persa (verrà eseguita l'asserzione nel debug). |
| Sostituire HSTRING in hstring | attach_abi(s, h); |
s acquisisce la proprietà della stringa. La stringa precedentemente di proprietà di s viene liberata. |
| Copiare HSTRING in hstring | copy_from_abi(s, h); |
s crea una copia privata della stringa. La stringa precedentemente di proprietà di s viene liberata. |
| Copiare hstring in HSTRING | copy_to_abi(s, reinterpret_cast<void*&>(h)); |
h riceve una copia della stringa. Qualsiasi stringa precedentemente di proprietà di h viene persa. |
Inoltre, le utilità WIL (Windows Implementation Libraries) per le stringhe eseguono operazioni di base sulle stringhe. Per usare gli helper per le stringhe WIL, includere <wil/resource.h>, e fare riferimento alla tabella seguente. Per informazioni dettagliate, seguire i collegamenti nella tabella.
| Operation | Helper per stringhe WIL per ulteriori informazioni |
|---|---|
| Fornire un puntatore stringa Unicode o ANSI non elaborato e una lunghezza facoltativa; ottenere un wrapper unique_any adeguatamente specializzato | wil::make_something_string |
| Scartare il wrapping di uno smart object finché non viene trovato un puntatore raw a una stringa Unicode con terminazione null | wil::str_raw_ptr |
Ottenere la stringa contenuta in un oggetto smart pointer; oppure la stringa vuota L"" se lo smart pointer è vuoto |
wil::string_get_not_null |
| Concatenare qualsiasi numero di stringhe | wil::str_concat |
| Ottenere una stringa da una stringa di formato di tipo printf e un elenco di parametri corrispondente | wil::str_printf |
API importanti
- Funzione AddRef
- Funzione QueryInterface
- Funzione winrt::attach_abi
- Modello di struttura winrt::com_ptr
- Funzione winrt::copy_from_abi
- Funzione winrt::copy_to_abi
- Funzione winrt::detach_abi
- Funzione winrt::get_abi
- funzione membro winrt::Windows::Foundation::IUnknown::as
- funzione membro di winrt::Windows::Foundation::IUnknown::try_as