Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Det här avsnittet visar hur du använder C++/WinRT-API:er, oavsett om de ingår i Windows, implementeras av en tredjepartskomponentleverantör eller implementeras av dig själv.
Viktigt!
För att kodexemplen i det här avsnittet ska vara korta och enkla för dig att prova kan du återskapa dem genom att skapa ett nytt Windows-konsolprogramprojekt (C++/WinRT) och kopiera och klistra in koden. Du kan dock inte använda godtyckliga anpassade Windows Runtime-typer från tredje part från en opaketerad app på det sättet. Du kan bara använda Windows-typer på det sättet.
Om du vill använda anpassade Windows Runtime-typer från tredje part i en konsolapp måste du ge appen en paketidentitet, så att den kan hitta registreringen för de anpassade typer som används. Mer information finns i Windows Programpaketering Project.
Du kan också skapa ett nytt projekt från projektmallarna Blank App, Packaged (WinUI 3 in Desktop) för C++ eller Windows Runtime Component (C++/WinRT). Dessa apptyper har redan en paketidentitet.
Om API:et finns i ett Windows namnområde
Det här är det vanligaste fallet där du använder ett Windows Runtime API. För varje typ i ett Windows namnområde som definierats i metadata definierar C++/WinRT en C++-vänlig motsvarighet (kallas den beräknade typen). En projicerad typ har samma fullständigt kvalificerade namn som den Windows typen, men den placeras i namnområdet C++ winrt med hjälp av C++-syntax. Till exempel projiceras Windows::Foundation::Uri till C++/WinRT som winrt::Windows::Foundation::Uri.
Här är ett enkelt kodexempel. Om du vill kopiera och klistra in följande kodexempel direkt i huvudkällkodsfilen för ett Windows konsolprogramprojekt (C++/WinRT) anger du först Inte använda förkompilerade rubriker i projektegenskaper.
// 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");
}
Den inkluderade huvudfilen winrt/Windows.Foundation.h är en del av SDK:t och finns i mappen %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt\. Rubrikerna i mappen innehåller Windows namnområdestyper som projiceras till C++/WinRT. I det här exemplet innehåller winrt/Windows.Foundation.hwinrt::Windows::Foundation::Uri, som är den projicerade typen för körningsklassen Windows::Foundation::Uri.
Tip
När du vill använda en typ från ett Windows namnområde ska du inkludera C++/WinRT-huvudet som motsvarar det namnområdet. Direktiven using namespace är valfria, men praktiska.
I kodexemplet ovan, efter att ha initierat C++/WinRT, stackallokerar vi ett värde för winrt::Windows::Foundation::Uri-projicerad typ via en av dess offentligt dokumenterade konstruktorer (Uri(Sträng), i det här exemplet). För detta, det vanligaste användningsfallet, är det vanligtvis allt du behöver göra. När du har ett C++/WinRT-beräknat typvärde kan du behandla det som om det vore en instans av den faktiska Windows Runtime typen, eftersom den har samma medlemmar.
Det projicerade värdet är i själva verket en proxy; det är i princip bara en smart pekare till ett underliggande objekt. Det beräknade värdets konstruktor anropar RoActivateInstance för att skapa en instans av backningsklassen Windows Runtime (Windows. Foundation.Uri i det här fallet) och lagra objektets standardgränssnitt i det nya beräknade värdet. Som illustreras nedan delegerar dina anrop till medlemmarna i det projicerade värdet faktiskt, via den smarta pekaren, till det underliggande objektet; det är där tillståndsändringar sker.
När värdet contosoUri faller utanför omfånget förstörs det och släpper sin referens till standardgränssnittet. Om den referensen är den sista referensen till det underliggande Windows Runtime-objektet Windows.Foundation.Uri, förstörs även det underliggande objektet.
Tip
En projicerad typ är ett omslutande lager kring en Windows Runtime-typ för att använda dess API:er. Ett projekterat gränssnitt är till exempel en omslutning över ett Windows Runtime-gränssnitt.
C++/WinRT-projektionshuvuden
Om du vill använda Windows namnområdes-API:er från C++/WinRT tar du med rubriker från %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt mappen. Du måste inkludera rubrikerna som motsvarar varje namnområde som du använder.
För Windows::Security::Cryptography::Certificates namespace finns till exempel motsvarande C++/WinRT-typdefinitioner i winrt/Windows.Security.Cryptography.Certificates.h. Om du inkluderar rubriken får du åtkomst till alla typer i namnområdet Windows::Security::Cryptography::Certificates.
Ibland innehåller ett namnområdeshuvud delar av relaterade namnområdesrubriker, men du bör inte förlita dig på den här implementeringsinformationen. Inkludera uttryckligen rubrikerna för de namnområden som du använder.
Metoden Certificate::GetCertificateBlob returnerar till exempel ett Windows::Storage::Streams::IBuffer-gränssnitt.
Innan du anropar metoden Certificate::GetCertificateBlob måste du inkludera winrt/Windows.Storage.Streams.h namnområdeshuvudfilen för att se till att du kan ta emot och använda den returnerade Windows::Storage::Streams::IBuffer.
Att glömma att ta med nödvändiga namnområdesrubriker innan du använder typer i det namnområdet är en vanlig källa till byggfel.
Åtkomst till medlemmar via objektet, via ett gränssnitt eller via ABI
Med C++/WinRT-projektionen är körningsrepresentationen av en Windows Runtime-klass inte mer än de underliggande ABI-gränssnitten. Men för din bekvämlighet kan du koda mot klasser på det sätt som författaren avsåg. Du kan till exempel anropa metoden ToString för en URI som om det vore en metod för klassen (i själva verket är det en metod i det separata IStringable-gränssnittet under täcket).
WINRT_ASSERT är en makrodefinition och expanderas till _ASSERTE.
Uri contosoUri{ L"http://www.contoso.com" };
WINRT_ASSERT(contosoUri.ToString() == L"http://www.contoso.com/"); // QueryInterface is called at this point.
Den här bekvämligheten uppnås via en fråga för rätt gränssnitt. Men du har alltid kontroll. Du kan välja att ge bort lite av den bekvämligheten för lite prestanda genom att hämta IStringable-gränssnittet själv och använda det direkt. I kodexemplet nedan hämtar du en faktisk pekare till gränssnittet IStringable under körning (genom en engångsfråga). Därefter är anropet till ToString direkt och undviker ytterligare anrop till QueryInterface.
...
IStringable stringable = contosoUri; // One-off QueryInterface.
WINRT_ASSERT(stringable.ToString() == L"http://www.contoso.com/");
Du kan välja den här tekniken om du vet att du kommer att anropa flera metoder i samma gränssnitt.
Om du vill komma åt medlemmar på ABI-nivå kan du för övrigt göra det. Kodexemplet nedan visar hur, och det finns mer information och kodexempel i Interop mellan C++/WinRT och 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.
}
Fördröjd initiering
I C++/WinRT har varje projekterad typ en särskild C++/WinRT std::nullptr_t konstruktor. Med undantag för just den gör alla konstruktörer för projicerade typer – inklusive standardkonstruktorn – att ett underliggande Windows Runtime-objekt skapas och ger dig en smartpekare till det. Så den regeln gäller var som helst där standardkonstruktorn används, till exempel uninitialiserade lokala variabler, onitialiserade globala variabler och onitialiserade medlemsvariabler.
Om du å andra sidan vill konstruera en variabel av en projekterad typ utan att den i sin tur skapar ett Windows Runtime objekt (så att du kan fördröja arbetet till senare) kan du göra det. Deklarera variabeln eller fältet med hjälp av den speciella C++/WinRT std::nullptr_t konstruktorn (som C++/WinRT-projektionen matar in i varje körningsklass). Vi använder den speciella konstruktorn med m_gamerPicBuffer i kodexemplet nedan.
#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();
}
Alla konstruktorer på den projicerade typen förutom konstruktorn std::nullptr_t leder till att ett bakomliggande Windows Runtime-objekt skapas. Konstruktorn std::nullptr_t är i princip en no-op. Det förutsätter att det projicerade objektet ska initieras vid ett senare tillfälle. Oavsett om en klass för körningsmiljön har en standardkonstruktor eller inte kan du använda den här tekniken för effektiv fördröjd initialisering.
Det här övervägandet påverkar andra platser där du anropar standardkonstruktorn, till exempel i vektorer och kartor. Tänk dig det här kodexemplet, där du behöver en tom app, paketerad (WinUI 3 i Desktop) för C++-projekt.
std::map<int, TextBlock> lookup;
lookup[2] = value;
Tilldelningen skapar en ny TextBlock, och skriver sedan omedelbart över den med value. Här är botemedlet.
std::map<int, TextBlock> lookup;
lookup.insert_or_assign(2, value);
Se även Hur standardkonstruktorn påverkar samlingar.
Fördröj inte initiera av misstag
Var försiktig så att du inte anropar konstruktorn std::nullptr_t av misstag. Kompilatorns konfliktlösning gynnar den framför fabrikskonstruktorerna. Tänk till exempel på dessa två körningsklassdefinitioner.
// GiftBox.idl
runtimeclass GiftBox
{
GiftBox();
}
// Gift.idl
runtimeclass Gift
{
Gift(GiftBox giftBox); // You can create a gift inside a box.
}
Anta att vi vill konstruera en Gift som inte ligger i en låda (en Gift som konstrueras med en oinitialiserad GiftBox). Först ska vi titta på fel sätt att göra det. Vi vet att det finns en Gift-konstruktor som tar en GiftBox. Men om vi är frestade att skicka en null GiftBox ( anropar giftkonstruktorn via enhetlig initiering, som vi gör nedan), får vi inte det resultat vi vill ha.
// 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) };
Vad du får här är en onitialiserad gåva. Du får ingen gåva med en oinitierad GiftBox. Det här är rätt sätt att göra det på.
// 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 }) };
I det felaktiga exemplet matchar överföring av en nullptr literal till förmån för konstruktorn för fördröjningsinitiering. För att avgöras till förmån för fabrikskonstruktorn måste parameterns typ vara en GiftBox. Du har fortfarande möjlighet att ange en GiftBox som uttryckligen fördröjningsinitieras, vilket visas i det korrekta exemplet.
Nästa exempel är också korrekt eftersom parametern har typen GiftBox och inte std::nullptr_t.
GiftBox giftBox{ nullptr };
Gift gift{ giftBox }; // Calls factory constructor.
Det är först när du skickar vidare en nullptr literal som tvetydigheten uppstår.
Kopiera inte av misstag.
Den här varningen liknar den som beskrivs i avsnittet Fördröj inte initiera av misstag ovan.
Förutom konstruktorn för fördröjningsinitiering matar C++/WinRT-projektionen även in en kopieringskonstruktor i varje körningsklass. Det är en konstruktor med en parameter som accepterar samma typ som objektet som skapas. Den resulterande smarta pekaren pekar på samma underliggande Windows Runtime-objekt som det som dess konstruktorparameter pekar på. Resultatet är två smarta pekarobjekt som pekar på samma bakgrundsobjekt.
Här är en runtime-klassdefinition som vi kommer att använda i kodexemplen.
// GiftBox.idl
runtimeclass GiftBox
{
GiftBox(GiftBox biggerBox); // You can place a box inside a bigger box.
}
Anta att vi vill skapa en GiftBox i en större GiftBox.
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) };
Det korrekta sättet att göra det är att uttryckligen anropa aktiveringsfabriken.
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) };
Om API:et implementeras i en Windows Runtime komponent
Det här avsnittet gäller om du har skapat komponenten själv eller om den kommer från en leverantör.
Note
Information om hur du installerar och använder VSIX (C++/WinRT Visual Studio Extension) och NuGet-paketet (som tillsammans tillhandahåller projektmall och byggstöd) finns i Visual Studio support för C++/WinRT.
I ditt appprojekt lägger du till en referens till Windows Runtime-komponentens Windows Runtime-metadatafil (.winmd) och bygger projektet. Under byggprocessen genererar cppwinrt.exe-verktyget ett standardbibliotek för C++ som fullständigt beskriver – eller projicerar – komponentens API-yta. Med andra ord innehåller det genererade biblioteket de planerade typerna för komponenten.
Precis som för en Windows namnområdestyp inkluderar du sedan en rubrik och konstruerar den projicerade typen via en av dess konstruktorer. Programprojektets startkod registrerar körningsklassen och den projicerade typens konstruktor anropar RoActivateInstance för att aktivera körningsklassen från den refererade komponenten.
#include <winrt/ThermometerWRC.h>
struct App : AppT<App>
{
ThermometerWRC::Thermometer thermometer;
...
};
Mer information om kod och en genomgång av hur man använder API:er som har implementerats i en Windows Runtime-komponent finns i Windows Runtime-komponenter med C++/WinRT och Skapa händelser i C++/WinRT.
Om API:et implementeras i det förbrukande projektet
Kodexemplet i det här avsnittet är hämtat från ämnet XAML-kontroller; bind till en C++/WinRT-egenskap. Mer information, kod och en genomgång av hur du använder en runtimeklass som implementeras i samma projekt som använder den finns i det avsnittet.
En typ som används i XAML-användargränssnittet måste vara en runtime-klass, även om den finns i samma projekt som XAML. I det här scenariot genererar du en projicerad typ från runtimeklassens Windows Runtime-metadata (.winmd). Återigen tar du med en rubrikfil, men sedan kan du välja mellan de sätt att konstruera instansen av runtime-klassen som används i C++/WinRT version 1.0 respektive 2.0. Metoden version 1.0 använder winrt::make; metoden version 2.0 kallas enhetlig konstruktion. Låt oss titta på var och en i tur och ordning.
Konstruera med winrt::make
Vi börjar med standardmetoden (C++/WinRT version 1.0), eftersom det är en bra idé att åtminstone känna till det mönstret. Du skapar den projekterade typen via dess std::nullptr_t konstruktor. Konstruktorn utför ingen initiering, så du måste tilldela instansen ett värde via winrt::make helper-funktionen och skicka eventuella nödvändiga konstruktorargument. En runtimeklass som implementeras i samma projekt som den kod som använder den behöver inte registreras och inte heller instansieras via Windows Runtime-/COM-aktivering.
Se XAML-kontroller; binda till en C++/WinRT-egenskap för en fullständig genomgång. Det här avsnittet visar utdrag från den genomgången.
// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
runtimeclass MainPage : Microsoft.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>();
...
}
Enhetlig konstruktion
Med C++/WinRT version 2.0 och senare finns det en optimerad form av konstruktion tillgänglig för dig som kallas enhetlig konstruktion (se Nyheter och ändringar i C++/WinRT 2.0).
Se XAML-kontroller; binda till en C++/WinRT-egenskap för en fullständig genomgång. Det här avsnittet visar utdrag från den genomgången.
Om du vill använda enhetlig konstruktion i stället för winrt::make behöver du en aktiveringsfabrik. Ett bra sätt att generera en är att lägga till en konstruktor i din IDL.
// MainPage.idl
import "BookstoreViewModel.idl";
namespace Bookstore
{
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
MainPage();
BookstoreViewModel MainViewModel{ get; };
}
}
Sedan deklarerar och initierar du MainPage.hm_mainViewModel i ett enda steg, som visas nedan.
// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
...
private:
Bookstore::BookstoreViewModel m_mainViewModel;
...
};
}
...
Och sedan, i konstruktorn för MainPage i MainPage.cpp, behövs inte koden m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();.
Mer information om enhetlig konstruktion och kodexempel finns i Anmäl dig till enhetlig konstruktion och direkt implementeringsåtkomst.
Instansiera och returnera projekterade typer och gränssnitt
Här är ett exempel på hur planerade typer och gränssnitt kan se ut i ditt förbrukande projekt. Kom ihåg att en projicerad typ (till exempel den i det här exemplet), är verktygsgenererad och inte är något som du skulle skriva själv.
struct MyRuntimeClass : MyProject::IMyRuntimeClass, impl::require<MyRuntimeClass,
Windows::Foundation::IStringable, Windows::Foundation::IClosable>
MyRuntimeClass är en projekterad typ. projekterade gränssnitt inkluderar IMyRuntimeClass, IStringable och IClosable. Det här avsnittet har visat de olika sätt på vilka du kan instansiera en projekterad typ. Här är en påminnelse och sammanfattning med MyRuntimeClass som exempel.
// 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>();
- Du kan komma åt medlemmarna i alla gränssnitt av en projekterad typ.
- Du kan returnera en projicerad typ till anroparen.
- Projekterade typer och gränssnitt härleds från winrt::Windows::Foundation::IUnknown. Så kan du anropa IUnknown::as på en projicerad typ eller ett gränssnitt för att fråga om andra projicerade gränssnitt, som du också kan använda eller returnera till anroparen. Funktionen as member fungerar som QueryInterface.
void f(MyProject::MyRuntimeClass const& myrc)
{
myrc.ToString();
myrc.Close();
IClosable iclosable = myrc.as<IClosable>();
iclosable.Close();
}
Aktiveringsfabriker
Det praktiska, direkta sättet att skapa ett C++/WinRT-objekt är följande.
using namespace winrt::Windows::Globalization::NumberFormatting;
...
CurrencyFormatter currency{ L"USD" };
Men det kan finnas tillfällen då du vill skapa aktiveringsfabriken själv och sedan skapa objekt från den när det passar dig. Här följer några exempel som visar hur du använder funktionsmallen 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");
Klasserna i de två exemplen ovan är typer från ett Windows namnområde. I nästa exempel är TermometerWRC::Termometer en anpassad typ som implementeras i en Windows Runtime komponent.
auto factory = winrt::get_activation_factory<ThermometerWRC::Thermometer>();
ThermometerWRC::Thermometer thermometer = factory.ActivateInstance<ThermometerWRC::Thermometer>();
Oklarheter kring medlem/typ
När en medlemsfunktion har samma namn som en typ finns det tvetydighet. Reglerna för C++ okvalificerad namnsökning i medlemsfunktioner gör att den söker i klassen innan den söker i namnområden. Ersättningsfelet är inte en felregel (SFINAE) som inte gäller (den gäller vid överbelastningsmatchning av funktionsmallar). Så om namnet i klassen inte är meningsfullt fortsätter kompilatorn inte att leta efter en bättre matchning – det rapporterar helt enkelt ett fel.
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>() };
}
}
Ovan tror kompilatorn att du skickar FrameworkElement.Style() (som i C++/WinRT är en medlemsfunktion) som mallparameter till IUnknown::as. Lösningen är att tvinga namnet Style att tolkas som typen Microsoft::UI::Xaml::Style.
struct MyPage : Page
{
void DoWork()
{
// One option is to fully-qualify it.
auto style{ Application::Current().Resources().
Lookup(L"MyStyle").as<Microsoft::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 Microsoft::UI::Xaml namespace when you did
// "using namespace Microsoft::UI::Xaml;".
auto style = Application::Current().Resources().
Lookup(L"MyStyle").as<::Style>();
}
}
Okvalificerad namnsökning har ett särskilt undantag om namnet följs av ::, i vilket fall det ignorerar funktioner, variabler och uppräkningsvärden. På så sätt kan du göra sådant här.
struct MyPage : Page
{
void DoSomething()
{
Visibility(Visibility::Collapsed); // No ambiguity here (special exception).
}
}
Anropet till Visibility() matchar medlemsfunktionsnamnet UIElement.Visibility. Men parametern Visibility::Collapsed följer ordet Visibility med ::, så metodnamnet ignoreras och kompilatorn hittar uppräkningsklassen.
Viktiga API:er
- QueryInterface-funktion
- RoActivateInstance-funktion
- Windows::Foundation::Uri-klass
- winrt::get_activation_factory funktionsmall
- winrt::make-funktionsmall
- winrt::Windows::Foundation::IUnknown struct
Relaterade ämnen
Windows developer