Dela via


Använda API:er med C++/WinRT

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 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-konsolapplikation (C++/WinRT) projekt, och kopiera och klistra in koden. Du kan dock inte använda valfria tredjeparts-Windows Runtime-typer från en opakkerad app på det sättet. Du kan bara konsumera Windows-typer på det sättet.

Om du vill använda anpassade (tredje parts) Windows Runtime-typer från en konsolapp måste du ge appen en paketidentitet så att den kan lösa registreringen av de anpassade typer som används. Mer information finns i Windows Application Packaging Project.

Du kan också skapa ett nytt projekt från projektmallarna Tom app (C++/WinRT), Core app (C++/WinRT)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 nä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 projicerad typ). En projekterad typ har samma fullständigt kvalificerade namn som Windows-typen, men den placeras i C++ winrt namnrymd med hjälp av C++-syntax. Till exempel projiceras Windows::Foundation::Uri i 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-konsolprogram (C++/WinRT) projekt 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 rubriken winrt/Windows.Foundation.h är en del av SDK:et som 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 planerade typen för körningsklassen Windows::Foundation::Uri.

Tips/Råd

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. Dessa using namespace-direktiv ä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(String), 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.

Egentligen är det beräknade värdet en proxy; det är i princip bara en smart pekare till ett underliggande objekt. Det projicerade värdes konstruktor(er) använder RoActivateInstance för att skapa en instans av den underliggande Windows Runtime-klassen (Windows.Foundation.Uri, i det här fallet), och lagra det objektets standardgränssnitt i det nya projicerade värdet. Som illustrerat nedan, delegerar dina anrop till medlemmarna i det projicerade värdet faktiskt, via den smarta pekaren, till bakgrundsobjektet där tillståndsändringar sker.

Den projicerade Windows::Foundation::Uri-typen

När värdet contosoUri faller utanför omfånget förstörs det, och det släpper sin referens till standardgränssnittet. Om den referensen är den sista referensen till Windows Runtime Windows.Foundation.Uri-objekt, så förstörs även bakgrundsobjektet.

Tips/Råd

En projekterad typ är en omslutning över en Windows Runtime-typ i syfte 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 inkluderar du rubriker från mappen %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt. 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 säkerställa 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 för 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 får du en faktisk IStringable-gränssnittspekare vid körningstid (via en engångsförfrågan). 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 speciell C++/WinRT-std::nullptr_t konstruktor. Med undantag för det skapar alla konstruktorer av projekttyp, inklusive standardkonstruktorn, ett Windows Runtime-objekt som stöds och ger dig en smart pekare 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 projicerad typ utan att den i sin tur skapar ett Windows Runtime-stödobjekt (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 projekterade typen , förutomstd::nullptr_t-konstruktorn, gör att ett Windows Runtime-objekt skapas. Konstruktorn std::nullptr_t är i princip en no-op. Den förväntar sig att det projekterade objektet initieras vid ett senare tillfälle. Oavsett om en körningstidklass har en standardkonstruktor eller inte kan du använda den här tekniken för effektiv försenad initiering.

Det här övervägandet påverkar andra platser där du anropar standardkonstruktorn, till exempel i vektorer och kartor. Tänk på det här kodexemplet, för vilket du behöver ett Blank App (C++/WinRT) projekt.

std::map<int, TextBlock> lookup;
lookup[2] = value;

Tilldelningen skapar en ny TextBlockoch 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 std::nullptr_t konstruktorn av misstag. Kompilatorns konfliktlösning gynnar den framför fabrikskonstruktorerna. Tänk till exempel på dessa två körklassdefinitioner.

// GiftBox.idl
runtimeclass GiftBox
{
    GiftBox();
}

// Gift.idl
runtimeclass Gift
{
    Gift(GiftBox giftBox); // You can create a gift inside a box.
}

Anta att vi vill skapa en Gåva som inte finns i en låda (en Gåva som är konstruerad med en oinitialiserad GiftBox). Först ska vi titta på fel sätt att göra det på. Vi vet att det finns en Gift konstruktor som tar en GiftBox. Men om vi är frestade att skicka en null-GiftBox- (anropa Gift konstruktorn via enhetlig initiering, som vi gör nedan), vi inte få 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 oinitialiserad Gift. Du får inte en present med en oinitialiserad GiftBox. 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 fabrikskonstruktören måste parametertypen vara en GiftBox. Du har fortfarande möjlighet att skicka en explicit fördröjningsinitiering GiftBox-, som du ser i rätt exempel.

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 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 initiering 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 Windows Runtime-objekt som det som dess konstruktorparameter pekar på. Resultatet är två smarta pekarobjekt som pekar på samma bakgrundsobjekt.

Här är en körningsklassdefinition som vi ska 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- inuti 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 rätt sätt att göra det är att anropa aktiveringsfabriken explicit.

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.

Anmärkning

Information om hur du installerar och använder C++/WinRT Visual Studio-tillägget (VSIX) och NuGet-paketet (som tillsammans tillhandahåller projektmall och byggstöd) finns i Visual Studio-stöd för C++/WinRT-.

I ditt programprojekt refererar du till Windows Runtime-komponentens Windows Runtime-metadatafil (.winmd) och bygger. Under bygget genererar cppwinrt.exe-verktyget ett C++-standardbibliotek som helt beskriver API-ytan för komponenten, eller projekt. 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. Din applikationsprojekts startkod registrerar körningsklassen, och konstruktören för den projicerade typen anropar RoActivateInstance för att aktivera körningsklassen från den refererade komponenten.

#include <winrt/ThermometerWRC.h>

struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    ThermometerWRC::Thermometer thermometer;
    ...
};

Mer information, kod och en genomgång av användnings-API:er som implementeras i en Windows Runtime-komponent finns i Windows Runtime-komponenter med C++/WinRT-- och Author-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. binda till en C++/WinRT-egenskap. Mer information, kod och en genomgång av hur du använder en körningsklass som implementeras i samma projekt som använder det finns i det avsnittet.

En typ som används från XAML-användargränssnittet måste vara en körningsklass, även om den finns i samma projekt som XAML. I det här scenariot genererar du en projekterad typ från körningsklassens Windows Runtime-metadata (.winmd). Återigen inkluderar du en rubrik, men sedan har du ett val mellan C++/WinRT version 1.0 eller version 2.0 sätt att konstruera instansen av körningsklassen. 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 hjälp av 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 function och skicka eventuella nödvändiga konstruktorargument. En körningsklass som implementeras i samma projekt som den konsumerande koden behöver inte registreras eller 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 : 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>();
    ...
}

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::makebehö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 : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

I MainPage.h sedan deklarera och initiera m_mainViewModel i bara ett steg, enligt nedan.

// MainPage.h
...
struct MainPage : MainPageT<MainPage>
{
    ...
    private:
        Bookstore::BookstoreViewModel m_mainViewModel;
        ...
    };
}
...

Och sedan, i MainPage-konstruktorn i MainPage.cpp, finns det inget behov av 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 beräknad typ. projekterade gränssnitt omfattar IMyRuntimeClass, IStringableoch 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 projekterad typ till en anropare.
  • Beräknade typer och gränssnitt härleds från winrt::Windows::Foundation::IUnknown. Så du kan anropa IUnknown::as på en projekterad typ eller ett gränssnitt för att fråga efter andra projekterade gränssnitt, som du också kan använda eller returnera till en anropare. Funktionen som medlem 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 ThermometerWRC::Thermometer en anpassad typ implementerad i en Windows Runtime-komponent.

auto factory = winrt::get_activation_factory<ThermometerWRC::Thermometer>();
ThermometerWRC::Thermometer thermometer = factory.ActivateInstance<ThermometerWRC::Thermometer>();

Tvetydigheter 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. Sekundärt misslyckande vid ersättning är inte ett fel;-regeln (SFINAE) gäller inte (den gäller vid överbelastningsresolution 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() (vilket i C++/WinRT är en member function) som mallparameter till IUnknown::as. Lösningen är att tvinga namnet Style tolkas som typen 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>();
    }
}

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() resulterar i UIElement.Visibility medlemsfunktionsnamnet. Men parametern Visibility::Collapsed följer ordet Visibility med ::, så metodnamnet ignoreras och kompilatorn hittar uppräkningsklassen.

Viktiga API:er