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 usare API di C++/WinRT, sia che facciano parte di Windows, implementate da un fornitore di componenti di terze parti o implementate da soli.
Importante
In modo che gli esempi di codice in questo argomento siano brevi e facili da provare, è possibile riprodurli creando una nuova applicazione console di Windows (C++/WinRT) progetto e copia incollando il codice. Tuttavia, non puoi utilizzare tipi di Windows Runtime personalizzati arbitrari (di terze parti) da un'app non in pacchetto come questa. È possibile utilizzare solo i tipi di Windows in questo modo.
Per utilizzare tipi di Windows Runtime personalizzati (di terze parti) da un'app console, è necessario assegnare all'app un'identità del pacchetto in modo che possa risolvere la registrazione dei tipi personalizzati utilizzati. Per altre informazioni, vedere Progetto di creazione pacchetti di applicazioni Windows.
In alternativa, creare un nuovo progetto dai modelli di progetto App vuota (C++/WinRT), App principale (C++/WinRT)o Windows Runtime Component (C++/WinRT). Questi tipi di app già hanno un'identità del pacchetto .
Se l'API si trova in uno spazio dei nomi Windows
Questo è il caso più comune in cui si userà un'API di Windows Runtime. Per ogni tipo in uno spazio dei nomi Windows definito nei metadati, C++/WinRT definisce un equivalente compatibile con C++ (chiamato tipo proiettato
Ecco un semplice esempio di codice. Se si desidera copiare gli esempi di codice seguenti direttamente nel file di codice sorgente principale di un progetto applicazione console di Windows (C++/WinRT), impostare prima Not Using Precompiled Headers nelle proprietà del progetto.
// main.cpp
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
int main()
{
winrt::init_apartment();
Uri contosoUri{ L"http://www.contoso.com" };
Uri combinedUri = contosoUri.CombineUri(L"products");
}
L'intestazione inclusa winrt/Windows.Foundation.h fa parte dell'SDK, disponibile all'interno della cartella %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt\. Le intestazioni in quella cartella contengono tipi di namespace di Windows proiettati in C++/WinRT. In questo esempio winrt/Windows.Foundation.h contiene winrt::Windows::Foundation::Uri, ovvero il tipo proiettato per la classe di runtime Windows::Foundation::Uri.
Suggerimento
Ogni volta che vuoi usare un tipo da uno spazio dei nomi Windows, includi l'intestazione C++/WinRT corrispondente a tale spazio dei nomi. Le direttive using namespace sono facoltative, ma utili.
Nell'esempio di codice precedente, dopo l'inizializzazione di C++/WinRT, viene allocato nello stack un valore del tipo proiettato winrt::Windows::Foundation::Uri tramite uno dei suoi costruttori documentati pubblicamente (Uri(String), in questo esempio). Per questo, il caso d'uso più comune, in genere è tutto ciò che devi fare. Una volta che hai un valore di tipo proiettato C++/WinRT, puoi considerarlo come se fosse un'istanza del tipo Windows Runtime effettivo, poiché ha tutti gli stessi membri.
Infatti, tale valore proiettato è un proxy; si tratta essenzialmente di un puntatore intelligente a un oggetto di backup. I costruttori del valore proiettato chiamano RoActivateInstance per creare un'istanza della classe Windows Runtime sottostante (Windows.Foundation.Uri, in questo caso) e archiviare l'interfaccia predefinita dell'oggetto all'interno del nuovo valore proiettato. Come illustrato di seguito, le chiamate ai membri del valore proiettato delegano, tramite il puntatore intelligente, all'oggetto sottostante. È quello il luogo in cui si verificano le modifiche di stato.
Quando il valore contosoUri non rientra nell'ambito, viene distrutto e rilascia il riferimento all'interfaccia predefinita. Se tale riferimento è l'ultimo riferimento all'oggetto che esegue il backup di Windows Runtime Windows.Foundation.Uri, viene distrutto anche l'oggetto di backup.
Suggerimento
Un tipo proiettato è un wrapper su un tipo Windows Runtime per scopi di utilizzo delle API. Ad esempio, un'interfaccia proiettata è un wrapper su un'interfaccia di Windows Runtime.
Intestazioni di proiezione C++/WinRT
Per utilizzare le API dello spazio dei nomi di Windows da C++/WinRT, includi intestazioni dalla cartella %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt. È necessario includere le intestazioni corrispondenti a ogni namespace usato.
Ad esempio, per lo spazio dei nomi Windows::Security::Cryptography::Certificates, le definizioni dei tipi C++/WinRT equivalenti risiedono in winrt/Windows.Security.Cryptography.Certificates.h. L'inclusione di tale intestazione consente di accedere a tutti i tipi nello spazio dei nomi Windows::Security::Cryptography::Certificates .
In alcuni casi, un'intestazione dello spazio dei nomi includerà parti di intestazioni dello spazio dei nomi correlate, ma non è consigliabile basarsi su questo dettaglio di implementazione. Includere in modo esplicito le intestazioni per i namespace usati.
Ad esempio, il metodo Certificate::GetCertificateBlob restituisce un'interfaccia Windows::Storage::Streams::IBuffer .
Prima di chiamare il metodo Certificate::GetCertificateBlob, è necessario includere il file di intestazione del namespace winrt/Windows.Storage.Streams.h per assicurarsi di poter ricevere e operare sul Windows::Storage::Streams::IBuffer restituito.
Dimenticare di includere le intestazioni dello spazio dei nomi necessarie prima di usare i tipi in tale spazio dei nomi è un'origine comune di errori di compilazione.
Accesso ai membri attraverso l'oggetto, un'interfaccia o l'ABI
Con la proiezione C++/WinRT, la rappresentazione a runtime di una classe Windows Runtime non è altro che le interfacce ABI sottostanti. Tuttavia, per praticità, è possibile programmare utilizzando le classi nel modo previsto dall'autore. Ad esempio, è possibile chiamare il metodo ToString di un Uri come se fosse un metodo della classe (in realtà, sotto le quinte, si tratta di un metodo sull'interfaccia IStringable separata).
WINRT_ASSERT è una definizione di macro e si espande fino a _ASSERTE.
Uri contosoUri{ L"http://www.contoso.com" };
WINRT_ASSERT(contosoUri.ToString() == L"http://www.contoso.com/"); // QueryInterface is called at this point.
Questa praticità viene ottenuta tramite una query per l'interfaccia appropriata. Ma sei sempre in controllo. È possibile scegliere di sacrificare un po' di quella comodità per un po' di prestazioni recuperando l'interfaccia IStringable manualmente da te e usandola direttamente. Nell'esempio di codice seguente si ottiene un puntatore di interfaccia IStringable effettivo in fase di esecuzione (tramite una query una tantum). Successivamente, la chiamata a ToString è diretta ed evita qualsiasi ulteriore chiamata a QueryInterface.
...
IStringable stringable = contosoUri; // One-off QueryInterface.
WINRT_ASSERT(stringable.ToString() == L"http://www.contoso.com/");
È possibile scegliere questa tecnica se si sa che verranno chiamati diversi metodi sulla stessa interfaccia.
Per inciso, se si desidera accedere ai membri a livello di ABI, puoi farlo. L'esempio di codice seguente mostra come fare, e sono disponibili ulteriori dettagli ed esempi di codice in Interoperabilità tra C++/WinRT e l'ABI.
#include <Windows.Foundation.h>
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>
using namespace winrt::Windows::Foundation;
int main()
{
winrt::init_apartment();
Uri contosoUri{ L"http://www.contoso.com" };
int port{ contosoUri.Port() }; // Access the Port "property" accessor via C++/WinRT.
winrt::com_ptr<ABI::Windows::Foundation::IUriRuntimeClass> abiUri{
contosoUri.as<ABI::Windows::Foundation::IUriRuntimeClass>() };
HRESULT hr = abiUri->get_Port(&port); // Access the get_Port ABI function.
}
Inizializzazione ritardata
In C++/WinRT ogni tipo proiettato ha un costruttore C++/WinRT speciale std::nullptr_t. Ad eccezione di quella, tutti i costruttori di tipo proiettato, incluso il costruttore predefinito, causano la creazione di un oggetto Windows Runtime di supporto e forniscono un puntatore intelligente. Tale regola viene quindi applicata ovunque venga usato il costruttore predefinito, ad esempio variabili locali non inizializzate, variabili globali non inizializzate e variabili membro non inizializzate.
Se, d'altra parte, vuoi costruire una variabile di un tipo proiettato senza che a sua volta costruisca un oggetto Windows Runtime sottostante (in modo da poter ritardare il lavoro fino a un secondo momento), puoi farlo. Dichiarare la variabile o il campo usando tale speciale C++/WinRT costruttore std::nullptr_t (che la proiezione C++/WinRT inserisce in ogni classe di runtime). Questo costruttore speciale viene usato con m_gamerPicBuffer nell'esempio di codice seguente.
#include <winrt/Windows.Storage.Streams.h>
using namespace winrt::Windows::Storage::Streams;
#define MAX_IMAGE_SIZE 1024
struct Sample
{
void DelayedInit()
{
// Allocate the actual buffer.
m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
}
private:
Buffer m_gamerPicBuffer{ nullptr };
};
int main()
{
winrt::init_apartment();
Sample s;
// ...
s.DelayedInit();
}
Tutti i costruttori del tipo proiettato
Questa considerazione influisce su altre posizioni in cui si richiama il costruttore predefinito, ad esempio in vettori e mappe. Si consideri questo esempio di codice, per il quale avrai bisogno di un progetto App Vuota (C++/WinRT).
std::map<int, TextBlock> lookup;
lookup[2] = value;
L'assegnazione crea un nuovo TextBlock, e poi lo sovrascrive immediatamente con value. Ecco il rimedio.
std::map<int, TextBlock> lookup;
lookup.insert_or_assign(2, value);
Vedi anche Come il costruttore predefinito influisce sulle raccolte.
Non rimandare l'inizializzazione per sbaglio
Prestare attenzione a non richiamare il costruttore std::nullptr_t per errore. La risoluzione dei conflitti del compilatore lo favorisce rispetto ai costruttori di fabbrica. Si consideri, ad esempio, queste due definizioni di classe di runtime.
// GiftBox.idl
runtimeclass GiftBox
{
GiftBox();
}
// Gift.idl
runtimeclass Gift
{
Gift(GiftBox giftBox); // You can create a gift inside a box.
}
Si supponga di voler costruire un Gift che non si trova all'interno di una scatola (un Gift costruito con un GiftBox non inizializzato). Innanzitutto, esaminiamo il modo errato di farlo. Sappiamo che esiste un costruttore Gift che accetta un GiftBox. Ma se siamo tentati di passare una GiftBox null (richiamando il costruttore Gift tramite l'inizializzazione uniforme, come facciamo di seguito), allora non otterremo il risultato desiderato.
// These are *not* what you intended. Doing it in one of these two ways
// actually *doesn't* create the intended backing Windows Runtime Gift object;
// only an empty smart pointer.
Gift gift{ nullptr };
auto gift{ Gift(nullptr) };
Quello che si ottiene qui è un regalo non inizializzato. Non si riceve un Gift se il GiftBox non è stato inizializzato. Ecco il modo corretto per farlo.
// Doing it in one of these two ways creates an initialized
// Gift with an uninitialized GiftBox.
Gift gift{ GiftBox{ nullptr } };
auto gift{ Gift(GiftBox{ nullptr }) };
Nell'esempio non corretto, il passaggio del valore letterale nullptr risulta a favore del costruttore ad inizializzazione ritardata. Per risolvere il problema a favore del costruttore factory, il tipo del parametro deve essere un GiftBox. È comunque possibile passare un'inizializzazione ritardata esplicita della GiftBox, come illustrato nell'esempio corretto.
Questo esempio successivo è anche corretto, perché il parametro ha il tipo GiftBox e non std::nullptr_t.
GiftBox giftBox{ nullptr };
Gift gift{ giftBox }; // Calls factory constructor.
È solo quando si passa un valore letterale nullptr che si verifica l'ambiguità.
Non effettuare per errore una copia-costruzione.
Questa avvertenza è simile a quella descritta nella sezione Non ritardare l'inizializzazione per errore.
Oltre al costruttore di inizializzazione ritardata, la proiezione C++/WinRT aggiunge anche un costruttore di copia in ogni classe runtime. Si tratta di un costruttore a parametro singolo che accetta lo stesso tipo dell'oggetto da costruire. Il puntatore intelligente risultante punta allo stesso oggetto di supporto di Windows Runtime a cui punta il parametro costruttore. Il risultato è costituito da due oggetti smart pointer che puntano allo stesso oggetto associato.
Ecco una definizione di classe di runtime che verrà usata negli esempi di codice.
// GiftBox.idl
runtimeclass GiftBox
{
GiftBox(GiftBox biggerBox); // You can place a box inside a bigger box.
}
Diciamo che vogliamo costruire una GiftBox all'interno di una GiftBoxpiù grande.
GiftBox bigBox{ ... };
// These are *not* what you intended. Doing it in one of these two ways
// copies bigBox's backing-object-pointer into smallBox.
// The result is that smallBox == bigBox.
GiftBox smallBox{ bigBox };
auto smallBox{ GiftBox(bigBox) };
Il modo corretto per eseguire questa operazione consiste nel chiamare in modo esplicito la fabbrica di attivazione.
GiftBox bigBox{ ... };
// These two ways call the activation factory explicitly.
GiftBox smallBox{
winrt::get_activation_factory<GiftBox, IGiftBoxFactory>().CreateInstance(bigBox) };
auto smallBox{
winrt::get_activation_factory<GiftBox, IGiftBoxFactory>().CreateInstance(bigBox) };
Se l'API viene implementata in un componente Windows Runtime
Questa sezione si applica se il componente è stato creato manualmente o proviene da un fornitore.
Annotazioni
Per informazioni sull'installazione e l'uso dell'estensione di Visual Studio C++/WinRT (VSIX) e del pacchetto NuGet (che insieme forniscono il modello di progetto e il supporto per la compilazione), vedere supporto di Visual Studio per C++/WinRT.
Nel progetto dell'applicazione, fai riferimento al file dei metadati del componente Windows Runtime (.winmd) e compila. Durante la compilazione, lo strumento cppwinrt.exe genera una libreria standard C++ che descrive completamente, o rappresenta, l'interfaccia API per il componente. In altre parole, la libreria generata contiene i tipi proiettati per il componente.
Quindi, proprio come per un tipo di spazio dei nomi Windows, includi un'intestazione e costruisci il tipo proiettato tramite uno dei relativi costruttori. Il codice di avvio del progetto dell'applicazione registra la classe di runtime e il costruttore del tipo proiettato invoca RoActivateInstance per attivare la classe di runtime dal componente di riferimento.
#include <winrt/ThermometerWRC.h>
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
ThermometerWRC::Thermometer thermometer;
...
};
Per ulteriori dettagli, codice e una guida sull'utilizzo delle API implementate in un componente di Windows Runtime, vedere Componenti di Windows Runtime con C++/WinRT e Autorizzare eventi in C++/WinRT.
Se l'API viene implementata nel progetto utilizzatore
L'esempio di codice in questa sezione è tratto dall'argomento controlli XAML: associata a una proprietà C++/WinRT. Per altri dettagli, codice e procedura dettagliata sull'uso di una classe di runtime implementata nello stesso progetto che lo usa, vedere questo argomento.
Un tipo consumato dall'interfaccia utente XAML deve essere una classe di runtime, anche se si trova nello stesso progetto di XAML. Per questo scenario, si genera un tipo proiettato dai metadati di Windows Runtime della classe di runtime (.winmd). Anche in questo caso, includi un'intestazione, ma hai una scelta tra le modalità C++/WinRT versione 1.0 o versione 2.0 per costruire l'istanza della classe di runtime. Il metodo della versione 1.0 usa winrt::make; il metodo della versione 2.0 è noto come costruzione uniforme. Esaminiamo ognuno a loro volta.
Creazione tramite winrt::make
Iniziamo con il metodo predefinito (C++/WinRT versione 1.0), perché è consigliabile avere almeno familiarità con questo modello. Il tipo proiettato viene creato tramite il relativo costruttore std::nullptr_t
Consulta i controlli XAML ; effettua il binding a una proprietà C++/WinRT per una guida completa. Questa sezione illustra gli estratti da tale procedura dettagliata.
// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
BookstoreViewModel MainViewModel{ get; };
}
}
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
private:
Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
};
...
// MainPage.cpp
...
#include "BookstoreViewModel.h"
MainPage::MainPage()
{
m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
...
}
Costruzione uniforme
Con C++/WinRT versione 2.0 e successive, è disponibile una forma ottimizzata di costruzione, nota come costruzione uniforme (vedere Notizie e modifiche in C++/WinRT 2.0).
Consulta i controlli XAML ; effettua il binding a una proprietà C++/WinRT per una guida completa. Questa sezione illustra gli estratti da tale procedura dettagliata.
Per utilizzare una costruzione uniforme al posto di winrt::make, sarà necessaria una fabbrica di attivazione. Un buon modo per generarne uno consiste nell'aggiungere un costruttore al file IDL.
// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
MainPage();
BookstoreViewModel MainViewModel{ get; };
}
}
Quindi, in MainPage.h dichiarare e inizializzare m_mainViewModel in un solo passaggio, come illustrato di seguito.
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
private:
Bookstore::BookstoreViewModel m_mainViewModel;
...
};
}
...
Quindi, nel costruttore MainPage in MainPage.cpp, non è necessario utilizzare il codice m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.
Per ulteriori informazioni sulla costruzione uniforme ed esempi di codice, vedi Optare per la costruzione uniforme e accesso diretto all'implementazione.
Creazione di istanze e restituzione di tipi e interfacce proiettati
Ecco un esempio di come potrebbero apparire i tipi e le interfacce previsti nel progetto di implementazione. Tenere presente che un tipo proiettato ,ad esempio quello in questo esempio, è generato dallo strumento e non è qualcosa che si potrebbe creare manualmente.
struct MyRuntimeClass : MyProject::IMyRuntimeClass, impl::require<MyRuntimeClass,
Windows::Foundation::IStringable, Windows::Foundation::IClosable>
MyRuntimeClass è un tipo proiettato; le interfacce proiettate includono IMyRuntimeClass, IStringablee IClosable. In questo argomento sono stati illustrati i diversi modi in cui è possibile l'istanziamento di un tipo proiettato. Ecco un promemoria e un riepilogo, usando MyRuntimeClass come esempio.
// The runtime class is implemented in another compilation unit (it's either a Windows API,
// or it's implemented in a second- or third-party component).
MyProject::MyRuntimeClass myrc1;
// The runtime class is implemented in the same compilation unit.
MyProject::MyRuntimeClass myrc2{ nullptr };
myrc2 = winrt::make<MyProject::implementation::MyRuntimeClass>();
- È possibile accedere ai membri di tutte le interfacce appartenenti a un tipo proiettato.
- È possibile restituire un tipo proiettato a un chiamante.
- I tipi e le interfacce proiettati derivano da winrt::Windows::Foundation::IUnknown. È quindi possibile chiamare IUnknown::as su un tipo o un'interfaccia proiettata per interrogare altre interfacce proiettate, che è anche possibile utilizzare o restituire a un chiamante. Il , in qualità di funzione membro di, opera come QueryInterface.
void f(MyProject::MyRuntimeClass const& myrc)
{
myrc.ToString();
myrc.Close();
IClosable iclosable = myrc.as<IClosable>();
iclosable.Close();
}
Fabbriche di attivazione
Il modo pratico e diretto per creare un oggetto C++/WinRT è il seguente.
using namespace winrt::Windows::Globalization::NumberFormatting;
...
CurrencyFormatter currency{ L"USD" };
In alcuni casi, tuttavia, è consigliabile creare manualmente la factory di attivazione e quindi creare oggetti da esso per comodità. Ecco alcuni esempi che illustrano come usare il modello di funzione winrt::get_activation_factory.
using namespace winrt::Windows::Globalization::NumberFormatting;
...
auto factory = winrt::get_activation_factory<CurrencyFormatter, ICurrencyFormatterFactory>();
CurrencyFormatter currency = factory.CreateCurrencyFormatterCode(L"USD");
using namespace winrt::Windows::Foundation;
...
auto factory = winrt::get_activation_factory<Uri, IUriRuntimeClassFactory>();
Uri uri = factory.CreateUri(L"http://www.contoso.com");
Le classi nei due esempi precedenti sono tipi di uno spazio dei nomi Windows. In questo esempio successivo ThermometerWRC::Thermometer è un tipo personalizzato implementato in un componente Windows Runtime.
auto factory = winrt::get_activation_factory<ThermometerWRC::Thermometer>();
ThermometerWRC::Thermometer thermometer = factory.ActivateInstance<ThermometerWRC::Thermometer>();
Ambiguità tra membro e tipo
Quando una funzione membro ha lo stesso nome di un tipo, esiste un'ambiguità. Le regole per la ricerca di nomi non qualificati in C++ all'interno di funzioni membro fanno sì che la ricerca avvenga prima nella classe e poi nel namespace. Il fallimento di sostituzione non è un errore, la regola (SFINAE) non si applica (la regola si applica durante la risoluzione dell'overload dei modelli di funzione). Quindi, se il nome all'interno della classe non ha senso, il compilatore non cerca una corrispondenza migliore, ma segnala semplicemente un errore.
struct MyPage : Page
{
void DoWork()
{
// This doesn't compile. You get the error
// "'winrt::Windows::Foundation::IUnknown::as':
// no matching overloaded function found".
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<Style>() };
}
}
Sopra, il compilatore pensa di passare FrameworkElement.Style() (che, in C++/WinRT, è una funzione membro) come parametro di modello a IUnknown::as. La soluzione consiste nel forzare l'interpretazione del nome Style come tipo Windows::UI::Xaml::Style.
struct MyPage : Page
{
void DoWork()
{
// One option is to fully-qualify it.
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<Windows::UI::Xaml::Style>() };
// Another is to force it to be interpreted as a struct name.
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<struct Style>() };
// If you have "using namespace Windows::UI;", then this is sufficient.
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<Xaml::Style>() };
// Or you can force it to be resolved in the global namespace (into which
// you imported the Windows::UI::Xaml namespace when you did
// "using namespace Windows::UI::Xaml;".
auto style = Application::Current().Resources().
Lookup(L"MyStyle").as<::Style>();
}
}
La ricerca di nomi non qualificati presenta un'eccezione speciale nel caso in cui il nome sia seguito da ::, nel qual caso ignora funzioni, variabili ed enumerazioni. In questo modo è possibile eseguire operazioni come questa.
struct MyPage : Page
{
void DoSomething()
{
Visibility(Visibility::Collapsed); // No ambiguity here (special exception).
}
}
La chiamata a Visibility() corrisponde al nome della funzione membro UIElement.Visibility. Tuttavia, il parametro Visibility::Collapsed segue la parola Visibility con ::e quindi il nome del metodo viene ignorato e il compilatore trova la classe enum.
API importanti
- funzione QueryInterface
- funzione RoActivateInstance
- classe Windows::Foundation::Uri
- modello di funzione winrt::get_activation_factory
- modello di funzione winrt::make
- struct winrt::Windows::Foundation::IUnknown