Anteckning
Å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 skapar C++/WinRT API:er genom att använda winrt::implements grundstruktur, antingen direkt eller indirekt. Synonymer för författare i den här kontexten är skapaeller implementera. Det här avsnittet beskriver följande scenarier för att implementera API:er på en C++/WinRT-typ i den här ordningen.
Anmärkning
Det här avsnittet berör ämnet Windows Runtime-komponenter, men bara i kontexten för C++/WinRT. Om du letar efter innehåll om Windows Runtime-komponenter som omfattar alla Windows Runtime-språk kan du läsa Windows Runtime-komponenter.
- Du skapar inte en Windows Runtime-klass (körningsklass); du vill bara implementera ett eller flera Windows Runtime-gränssnitt för internt bruk i din app. Du härleder direkt från winrt::implements i det här fallet och implementerar funktioner.
- Du redigerar en körningsklass. Du kanske redigerar en komponent som ska användas från en app. Eller så kanske du redigerar en typ som ska användas från XAML-användargränssnittet (UI), och i så fall både implementerar och använder du en körningsklass i samma kompileringsenhet. I dessa fall låter du verktygen generera klasser åt dig som härleds från winrt::implements.
I båda fallen kallas typen som implementerar dina C++/WinRT-API:er för -implementeringstypen.
Viktigt!
Det är viktigt att skilja begreppet implementeringstyp från en projekterad typ. Den beräknade typen beskrivs i Använda API:er med C++/WinRT-.
Om du inte redigering av en körningsklass
Det enklaste scenariot är när din typ implementerar ett Windows Runtime-gränssnitt och du kommer att använda den typen i samma app. I så fall behöver din typ inte vara en körningsklass; utan bara en vanlig C++-klass. Du kanske till exempel skriver en app baserat på CoreApplication-.
Om din typ refereras av XAML-användargränssnittet måste den vara en körningsklass, även om den finns i samma projekt som XAML. I så fall, se avsnittet om du skriver en körningsklass som refereras i ditt XAML-användargränssnitt.
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 Visual Studio illustrerar projektmallen Visual C++>Windows Universal>Core App (C++/WinRT) mönstret CoreApplication. Mönstret börjar med att skicka en implementering av Windows::ApplicationModel::Core::IFrameworkViewSource till CoreApplication::Run.
using namespace Windows::ApplicationModel::Core;
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
IFrameworkViewSource source = ...
CoreApplication::Run(source);
}
CoreApplication använder gränssnittet för att skapa appens första vy. Konceptuellt ser IFrameworkViewSource ut så här.
struct IFrameworkViewSource : IInspectable
{
IFrameworkView CreateView();
};
Konceptuellt sett gör implementeringen av CoreApplication::Kör detta återigen.
void Run(IFrameworkViewSource viewSource) const
{
IFrameworkView view = viewSource.CreateView();
...
}
Som utvecklare implementerar du därför gränssnittet IFrameworkViewSource. C++/WinRT har basstruktureringsmallen winrt::implementerar för att göra det enkelt att implementera ett gränssnitt (eller flera) utan att använda PROGRAMMERING i COM-stil. Du härleder bara din typ från implementeraroch implementerar sedan gränssnittets funktioner. Så här gör du.
// App.cpp
...
struct App : implements<App, IFrameworkViewSource>
{
IFrameworkView CreateView()
{
return ...
}
}
...
Det är ordnat med IFrameworkViewSource. Nästa steg är att returnera ett objekt som implementerar gränssnittet IFrameworkView. Du kan också välja att implementera gränssnittet på App. Nästa kodexempel representerar en minimal app som åtminstone kommer att få igång ett fönster på skrivbordet.
// App.cpp
...
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
IFrameworkView CreateView()
{
return *this;
}
void Initialize(CoreApplicationView const &) {}
void Load(hstring const&) {}
void Run()
{
CoreWindow window = CoreWindow::GetForCurrentThread();
window.Activate();
CoreDispatcher dispatcher = window.Dispatcher();
dispatcher.ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
}
void SetWindow(CoreWindow const & window)
{
// Prepare app visuals here
}
void Uninitialize() {}
};
...
Eftersom din App typ är enIFrameworkViewSourcekan du bara skicka vidare en till Run.
using namespace Windows::ApplicationModel::Core;
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
CoreApplication::Run(winrt::make<App>());
}
Om du skapar en körningsklass i en Windows Runtime-komponent
Om din typ paketeras i en Windows Runtime-komponent för användning från en annan binär fil (den andra binärfilen är vanligtvis ett program) måste din typ vara en köretidsklass. Du deklarerar en körningsklass i en Microsoft Interface Definition Language (IDL) (.idl) fil (se faktorisera körningsklasser i Midl-filer (.idl)).
Varje IDL-fil resulterar i en .winmd fil, och Visual Studio sammanfogar alla dessa i en enda fil med samma namn som rotnamnområdet. Den sista .winmd filen kommer att vara den som konsumenterna av komponenten refererar till.
Här är ett exempel på hur du deklarerar en runtime klass i en IDL-fil.
// MyRuntimeClass.idl
namespace MyProject
{
runtimeclass MyRuntimeClass
{
// Declaring a constructor (or constructors) in the IDL causes the runtime class to be
// activatable from outside the compilation unit.
MyRuntimeClass();
String Name;
}
}
Den här IDL-filen deklarerar en Windows Runtime-klass (runtime). En körningsklass är en typ som kan aktiveras och användas via moderna COM-gränssnitt, vanligtvis över processgränser. När du lägger till en IDL-fil i projektet och skapar genererar C++/WinRT-verktygskedjan (midl.exe och cppwinrt.exe) en implementeringstyp åt dig. Ett exempel på IDL-filarbetsflödet i praktiken finns i XAML-kontroller; bind till en C++/WinRT-egenskap.
Med hjälp av exemplet IDL ovan är implementeringstypen en C++ struct stub med namnet winrt::MyProject::implementation::MyRuntimeClass i källkodsfiler med namnet \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h och MyRuntimeClass.cpp.
Implementeringstypen ser ut så här.
// MyRuntimeClass.h
...
namespace winrt::MyProject::implementation
{
struct MyRuntimeClass : MyRuntimeClassT<MyRuntimeClass>
{
MyRuntimeClass() = default;
winrt::hstring Name();
void Name(winrt::hstring const& value);
};
}
// winrt::MyProject::factory_implementation::MyRuntimeClass is here, too.
Observera att det F-bundna polymorfismmönstret som används (MyRuntimeClass använder sig själv som ett mallargument till sin bas, MyRuntimeClassT). Detta kallas även det märkligt återkommande mallmönstret (CRTP). Om du följer arvskedjan uppåt stöter du på MyRuntimeClass_base.
Du kan förenkla implementeringen av enkla egenskaper med hjälp av Windows Implementeringsbibliotek (WIL). Så här gör du:
// MyRuntimeClass.h
...
namespace winrt::MyProject::implementation
{
struct MyRuntimeClass : MyRuntimeClassT<MyRuntimeClass>
{
MyRuntimeClass() = default;
wil::single_threaded_rw_property<winrt::hstring> Name;
};
}
Se Enkla egenskaper.
template <typename D, typename... I>
struct MyRuntimeClass_base : implements<D, MyProject::IMyRuntimeClass, I...>
Så i det här scenariot är roten i arvshierarkin återigen winrt::implements basstrukturmallen.
Mer information, kod och en genomgång av redigerings-API:er i en Windows Runtime-komponent finns i Windows Runtime-komponenter med C++/WinRT-- och Author-händelser i C++/WinRT-.
Om du skapar en runtimeklass som ska refereras i ditt XAML-användargränssnitt
Om din typdefinition refereras av ditt XAML-användargränssnitt måste den vara en runtime-klass, även om den finns i samma projekt som XAML. Även om de vanligtvis aktiveras över körbara gränser kan en körningsklass i stället användas i kompileringsenheten som implementerar den.
I det här scenariot skapar och använder du API:erna för både och. Proceduren för att implementera din körningsklass är i stort sett densamma som för en Windows Runtime-komponent. Så se föregående avsnitt–Om du redigerar en körningsklass i en Windows Runtime-komponent. Den enda detalj som skiljer sig är att C++/WinRT-verktygskedjan från IDL genererar inte bara en implementeringstyp utan även en beräknad typ. Det är viktigt att bara förstå att det kan vara tvetydigt att bara säga "MyRuntimeClass" i det här scenariot. det finns flera entiteter med det namnet, av olika slag.
- MyRuntimeClass är namnet på en körningsklass. Men det här är verkligen en abstraktion: deklareras i IDL och implementeras på något programmeringsspråk.
-
MyRuntimeClass är namnet på C++-strukturen winrt::MyProject::implementation::MyRuntimeClass, som är C++/WinRT-implementeringen av körtidsklassen. Som vi har sett finns den här structen endast i implementeringsprojektet om det finns separata implementerings- och användningsprojekt. Det här är implementeringstypeneller implementeringen. Den här typen genereras (av verktyget
cppwinrt.exe) i filerna\MyProject\MyProject\Generated Files\sources\MyRuntimeClass.hochMyRuntimeClass.cpp. -
MyRuntimeClass är namnet på den projekterade typen i form av C++ struct winrt::MyProject::MyRuntimeClass. Om det finns separata implementerings- och användningsprojekt finns den här structen endast i det förbrukande projektet. Det här är den projicerade typen, eller projektionen. Den här typen genereras (av
cppwinrt.exe) i filen\MyProject\MyProject\Generated Files\winrt\impl\MyProject.2.h.
Här är de delar av den projekterade typen som är relevanta för det här ämnet.
// MyProject.2.h
...
namespace winrt::MyProject
{
struct MyRuntimeClass : MyProject::IMyRuntimeClass
{
MyRuntimeClass(std::nullptr_t) noexcept {}
MyRuntimeClass();
};
}
En exempelgenomgång om hur du implementerar INotifyPropertyChanged-gränssnittet på en körningsklass finns i XAML-kontroller. binda till en C++/WinRT-egenskap.
Proceduren för att tillämpa din körningsklass i det här scenariot beskrivs i Konsumera API:er med C++/WinRT.
Faktorisering av körningsklasser i Midl-filer (.idl)
Visual Studio-projekt- och objektmallarna skapar en separat IDL-fil för varje körningsklass. Det ger en logisk korrespondens mellan en IDL-fil och dess genererade källkodsfiler.
Men om du konsoliderar alla projektets körningsklasser till en enda IDL-fil kan det avsevärt förbättra byggtiden. Om du annars skulle ha komplexa (eller cirkulära) import beroenden bland dem, kan det faktiskt vara nödvändigt att konsolidera. Och det kan vara lättare att skapa och granska dina körtidsklasser om de är samlade.
Konstruktorer för Körtidsklass
Här är några punkter att ta bort från de listor som vi har sett ovan.
- Varje konstruktor som du deklarerar i din IDL gör att en konstruktor genereras både på din implementeringstyp och på din planerade typ. IDL-deklarerade konstruktorer används för att konsumera körningsklassen från en annan kompileringsenhet.
- Oavsett om du har IDL-deklarerade konstruktorer eller inte genereras en konstruktoröverlagring som accepterar std::nullptr_t för den projicerade typen. Att anropa konstruktorn std::nullptr_t är det första av två steg i att använda sig av körningsklassen inom samma kompileringsenhet. Mer detaljer och ett kodexempel finner du i Använda API:er med C++/WinRT.
- Om du använder körningsklassen från samma kompileringsenhet kan du även implementera icke-standardkonstruktorer direkt på implementeringstypen (som, kom ihåg, finns i
MyRuntimeClass.h).
Anmärkning
Om du förväntar dig att din runtime-klass ska användas från en annan kompileringsenhet (vilket är vanligt) ska du inkludera konstruktorer i din IDL (åtminstone en standardkonstruktor). Genom att göra det får du även en fabriksimplementering tillsammans med din implementeringstyp.
Om du bara vill skapa och använda körningsklassen inom samma kompileringsenhet ska du inte deklarera några konstruktorer i din IDL. Du behöver ingen fabriksimplementering och en kommer inte att genereras. Standardkonstruktorn för implementeringstypen tas bort, men du kan enkelt redigera den och standardredigera den i stället.
Om du bara vill skapa och använda körningsklassen inom samma kompileringsenhet och du behöver konstruktorparametrar skapar du de konstruktorer som du behöver direkt på din implementeringstyp.
Körningstidsklassmetoder, egenskaper och händelser
Vi har sett att arbetsflödet är att använda IDL för att deklarera din körningsklass och dess medlemmar, och sedan genererar verktyget prototyper och stub-implementeringar åt dig. När det gäller de autogenererade prototyperna för medlemmarna i din körningsklass kan du redigera dem så att de skickar runt olika typer från de typer som du deklarerar i din IDL. Men du kan bara göra det så länge den typ som du deklarerar i IDL kan vidarebefordras till den typ som du deklarerar i den implementerade versionen.
Här följer några exempel.
- Du kan lätta på parametertyperna. Om din metod till exempel tar en SomeClassi IDL kan du välja att ändra den till IInspectable i implementeringen. Detta fungerar eftersom alla SomeClass- kan vidarebefordras till IInspectable (det omvända skulle naturligtvis inte fungera).
- Du kan acceptera en kopierbar parameter efter värde i stället för som referens. Ändra till exempel
SomeClass const&tillSomeClass. Det är nödvändigt när du behöver undvika att samla in en referens i en coroutine (se Parameter-passing). - Du kan lätta på returvärdet. Du kan till exempel ändra void till winrt::fire_and_forget.
De två sista är mycket användbara när du skriver en asynkron händelsehanterare.
Instansiera och returnera implementeringstyper och gränssnitt
I det här avsnittet tar vi som exempel en implementeringstyp med namnet MyType, som implementerar gränssnitten IStringable och IClosable.
Du kan derivera MyType- direkt från winrt::implements (det är inte en runtime-klass).
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
struct MyType : implements<MyType, IStringable, IClosable>
{
winrt::hstring ToString(){ ... }
void Close(){}
};
Eller så kan du generera den från IDL (det är en körtidsklass).
// MyType.idl
namespace MyProject
{
runtimeclass MyType: Windows.Foundation.IStringable, Windows.Foundation.IClosable
{
MyType();
}
}
Du kan inte allokera implementeringstypen direkt.
MyType myimpl; // error C2259: 'MyType': cannot instantiate abstract class
Men du kan gå från MyType till ett IStringable- eller IClosable-objekt som du kan använda eller returnera som en del av din projektion genom att anropa winrt::make-funktionsmallen. får att returnera implementeringstypens standardgränssnitt.
IStringable istringable = winrt::make<MyType>();
Anmärkning
Men om du refererar till din typ från ditt XAML-användargränssnitt finns det både en implementeringstyp och en projekterad typ i samma projekt. I så fall ser till att returnerar en instans av den projicerade typen. Ett kodexempel på det scenariot finns i XAML-kontroller; bind till en C++/WinRT-egenskap.
Vi kan bara använda istringable (i kodexemplet ovan) för att anropa medlemmarna i IStringable-gränssnittet . Men ett C++/WinRT-gränssnitt (som är ett beräknat gränssnitt) härleds från winrt::Windows::Foundation::IUnknown. Därför kan du anropa IUnknown::as (eller IUnknown::try_as) på den för att fråga efter andra planerade typer eller gränssnitt, som du också kan använda eller returnera.
Tips/Råd
Ett scenario där du inte ska anropa som eller try_as är körningsklassavledning ("sammansättningsbara klasser"). När en implementeringstyp inkluderar en annan klass ska du inte anropa som eller try_as för att utföra en okontrollerad eller kontrollerad QueryInterface av den ingående klassen. Använd istället datamedlemmen (this->) m_inner och anropa som eller try_as på den. Mer information finns i Härledning av Runtime-klass i det här avsnittet.
istringable.ToString();
IClosable iclosable = istringable.as<IClosable>();
iclosable.Close();
Om du behöver komma åt alla implementeringsmedlemmar och sedan returnera ett gränssnitt till en anropare använder du funktionsmallen winrt::make_self. make_self returnerar en winrt::com_ptr som omfattar implementeringstypen. Du kan komma åt medlemmarna i alla dess gränssnitt (med hjälp av piloperatorn), du kan returnera den till en anropare as-is, eller så kan du anropa som på den och returnera det resulterande gränssnittsobjektet till en anropare.
winrt::com_ptr<MyType> myimpl = winrt::make_self<MyType>();
myimpl->ToString();
myimpl->Close();
IClosable iclosable = myimpl.as<IClosable>();
iclosable.Close();
Klassen MyType ingår inte i projektionen. det är implementeringen. Men på så sätt kan du anropa dess implementeringsmetoder direkt, utan att behöva använda ett virtuellt funktionsanrop. I exemplet ovan, även om MyType::ToString använder samma signatur som den planerade metoden på IStringableanropar vi den icke-virtuella metoden direkt, utan att korsa programmets binära gränssnitt (ABI).
com_ptr innehåller bara en pekare till MyType- struct, så att du också kan komma åt annan intern information om MyType via variabeln myimpl och piloperatorn.
Om du har ett gränssnittsobjekt och du råkar veta att det är ett gränssnitt för implementeringen kan du återgå till implementeringen med hjälp av winrt::get_self funktionsmall. Återigen är det en teknik som undviker virtuella funktionsanrop och låter dig komma direkt vid implementeringen.
Anmärkning
Om du inte har installerat Windows SDK version 10.0.17763.0 (Windows 10, version 1809) eller senare måste du anropa winrt::from_abi i stället för winrt::get_self.
Här är ett exempel. Det finns ett annat exempel i Implementera BgLabelControl anpassad kontrollklass.
void ImplFromIClosable(IClosable const& from)
{
MyType* myimpl = winrt::get_self<MyType>(from);
myimpl->ToString();
myimpl->Close();
}
Men bara det ursprungliga gränssnittsobjektet innehåller en referens. Om du vill behålla det kan du anropa com_ptr::copy_from.
winrt::com_ptr<MyType> impl;
impl.copy_from(winrt::get_self<MyType>(from));
// com_ptr::copy_from ensures that AddRef is called.
Själva implementeringstypen härleds inte från winrt::Windows::Foundation::IUnknown, så den har inga som funktion. Men som du ser i funktionen ImplFromIClosable ovan kan du komma åt medlemmarna i alla dess gränssnitt. Men om du gör det returnerar du inte instansen av råimplementeringstypen till anroparen. Använd i stället någon av de tekniker som redan visas och returnera ett projicerat gränssnitt eller en com_ptr.
Om du har en instans av din implementeringstyp och du behöver skicka den till en funktion som förväntar sig motsvarande projekttyp kan du göra det, som du ser i kodexemplet nedan. Det finns en konverteringsoperator för din implementeringstyp (förutsatt att implementeringstypen genererades av verktyget cppwinrt.exe) som gör detta möjligt. Du kan skicka ett implementeringstypvärde direkt till en metod som förväntar sig ett värde av motsvarande projekttyp. Från en medlemsfunktion av implementeringstyp kan du skicka *this till en metod som förväntar sig ett värde av motsvarande projekttyp.
// MyClass.idl
import "MyOtherClass.idl";
namespace MyProject
{
runtimeclass MyClass
{
MyClass();
void MemberFunction(MyOtherClass oc);
}
}
// MyClass.h
...
namespace winrt::MyProject::implementation
{
struct MyClass : MyClassT<MyClass>
{
MyClass() = default;
void MemberFunction(MyProject::MyOtherClass const& oc) { oc.DoWork(*this); }
};
}
...
// MyOtherClass.idl
import "MyClass.idl";
namespace MyProject
{
runtimeclass MyOtherClass
{
MyOtherClass();
void DoWork(MyClass c);
}
}
// MyOtherClass.h
...
namespace winrt::MyProject::implementation
{
struct MyOtherClass : MyOtherClassT<MyOtherClass>
{
MyOtherClass() = default;
void DoWork(MyProject::MyClass const& c){ /* ... */ }
};
}
...
//main.cpp
#include "pch.h"
#include <winrt/base.h>
#include "MyClass.h"
#include "MyOtherClass.h"
using namespace winrt;
// MyProject::MyClass is the projected type; the implementation type would be MyProject::implementation::MyClass.
void FreeFunction(MyProject::MyOtherClass const& oc)
{
auto defaultInterface = winrt::make<MyProject::implementation::MyClass>();
MyProject::implementation::MyClass* myimpl = winrt::get_self<MyProject::implementation::MyClass>(defaultInterface);
oc.DoWork(*myimpl);
}
...
Körningsklasshärledning
Du kan skapa en runtime-klass som härleds från en annan runtime-klass, förutsatt att basklassen deklareras som "oförseglad". Windows Runtime-termen för klasshärledning är "sammansättningsbara klasser". Koden för att implementera en härledd klass beror på om basklassen tillhandahålls av en annan komponent eller av samma komponent. Som tur är behöver du inte lära dig de här reglerna– du kan bara kopiera exempelimplementeringarna från utdatamappen sourcescppwinrt.exe som kompilatorn har skapat.
Tänk på det här exemplet.
// MyProject.idl
namespace MyProject
{
[default_interface]
runtimeclass MyButton : Windows.UI.Xaml.Controls.Button
{
MyButton();
}
unsealed runtimeclass MyBase
{
MyBase();
overridable Int32 MethodOverride();
}
[default_interface]
runtimeclass MyDerived : MyBase
{
MyDerived();
}
}
I exemplet ovan härleds MyButton från XAML-knappkontrollen , som tillhandahålls av en annan komponent. I så fall ser implementeringen ut precis som implementeringen av en icke-komposterbar klass:
namespace winrt::MyProject::implementation
{
struct MyButton : MyButtonT<MyButton>
{
};
}
namespace winrt::MyProject::factory_implementation
{
struct MyButton : MyButtonT<MyButton, implementation::MyButton>
{
};
}
I exemplet ovan härleds å andra sidan MyDerived från en annan klass i samma komponent. I det här fallet kräver implementeringen ytterligare en mallparameter som anger implementeringsklassen för basklassen.
namespace winrt::MyProject::implementation
{
struct MyDerived : MyDerivedT<MyDerived, implementation::MyBase>
{ // ^^^^^^^^^^^^^^^^^^^^^^
};
}
namespace winrt::MyProject::factory_implementation
{
struct MyDerived : MyDerivedT<MyDerived, implementation::MyDerived>
{
};
}
I båda fallen kan implementeringen anropa en metod från basklassen genom att kvalificera den med typaliaset base_type :
namespace winrt::MyProject::implementation
{
struct MyButton : MyButtonT<MyButton>
{
void OnApplyTemplate()
{
// Call base class method
base_type::OnApplyTemplate();
// Do more work after the base class method is done
DoAdditionalWork();
}
};
struct MyDerived : MyDerivedT<MyDerived, implementation::MyBase>
{
int MethodOverride()
{
// Return double what the base class returns
return 2 * base_type::MethodOverride();
}
};
}
Tips/Råd
När en implementeringstyp inkluderar en annan klass ska du inte anropa som eller try_as för att utföra en okontrollerad eller kontrollerad QueryInterface av den ingående klassen. Använd istället datamedlemmen (this->) m_inner och anropa som eller try_as på den.
Härrör från en typ som har en icke-standardkonstruktor
ToggleButtonAutomationPeer::ToggleButtonAutomationPeer(ToggleButton) är ett exempel på en konstruktor som inte är standard. Det finns ingen standardkonstruktor, så för att skapa en ToggleButtonAutomationPeermåste du ange en ägare. Om du härleder från ToggleButtonAutomationPeermåste du därför ange en konstruktor som tar en ägare och skickar den till basen. Nu ska vi se hur det ser ut i praktiken.
// MySpecializedToggleButton.idl
namespace MyNamespace
{
runtimeclass MySpecializedToggleButton :
Windows.UI.Xaml.Controls.Primitives.ToggleButton
{
...
};
}
// MySpecializedToggleButtonAutomationPeer.idl
namespace MyNamespace
{
runtimeclass MySpecializedToggleButtonAutomationPeer :
Windows.UI.Xaml.Automation.Peers.ToggleButtonAutomationPeer
{
MySpecializedToggleButtonAutomationPeer(MySpecializedToggleButton owner);
};
}
Den genererade konstruktorn för din implementeringstyp ser ut så här.
// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
(MyNamespace::MySpecializedToggleButton const& owner)
{
...
}
...
Det enda som saknas är att du behöver skicka konstruktorparametern till basklassen. Kommer du ihåg det F-bundna polymorfismmönstret som vi nämnde ovan? När du är bekant med informationen om det mönstret som används av C++/WinRT kan du ta reda på vad basklassen heter (eller så kan du bara titta i implementeringsklassens huvudfil). Så här anropar du basklasskonstruktorn i det här fallet.
// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
(MyNamespace::MySpecializedToggleButton const& owner) :
MySpecializedToggleButtonAutomationPeerT<MySpecializedToggleButtonAutomationPeer>(owner)
{
...
}
...
Basklasskonstruktorn förväntar sig en ToggleButton. Och MySpecializedToggleButtonär enToggleButton.
Tills du gör den redigering som beskrivs ovan (för att skicka konstruktorparametern till basklassen) flaggar kompilatorn konstruktorn och påpekar att det inte finns någon lämplig standardkonstruktor tillgänglig för en typ som heter (i det här fallet) MySpecializedToggleButtonAutomationPeer_base<MySpecializedToggleButtonAutomationPeer>. Det är faktiskt grundklassen för basklassen för din implementeringstyp.
Namnområden: projekterade typer, implementeringstyper och fabriker
Som du har sett tidigare i det här avsnittet finns det en C++/WinRT-körningsklass i form av mer än en C++-klass i mer än ett namnområde. Därför har namnet MyRuntimeClass en betydelse i winrt::MyProject namnområde och en annan betydelse i winrt::MyProject::implementation namnrymd. Tänk på vilket namnområde du för närvarande har i kontexten och använd sedan namnområdesprefix om du behöver ett namn från ett annat namnområde. Nu ska vi titta närmare på namnrymderna i fråga.
- winrt::MyProject. Det här namnområdet innehåller projekterade typer. Ett objekt av en projekterad typ är en proxy. Det är i princip en smart pekare till ett stödobjekt, där det stödobjektet kan implementeras här i projektet eller implementeras i en annan kompileringsenhet.
- winrt::MyProject::implementation. Det här namnområdet innehåller implementeringstyper. Ett objekt av en implementeringstyp är inte en pekare. det är ett värde – ett fullständigt C++-stackobjekt. Skapa inte en implementeringstyp direkt. anropa i stället winrt::makeoch skicka implementeringstypen som mallparameter. Vi har visat exempel på winrt::make i praktiken tidigare i det här avsnittet, och det finns ett annat exempel i XAML-kontroller; binda till en C++/WinRT-egenskap. Se även Diagnostisering av direktallokeringar.
- winrt::MyProject::factory_implementation. Det här namnområdet innehåller fabriker. Ett objekt i det här namnområdet stöder IActivationFactory.
Den här tabellen visar den minsta namnområdeskvalifikation som du behöver använda i olika kontexter.
| Namnområdet som finns i kontexten | Så här anger du den projicerade typen | Så här anger du implementeringstypen |
|---|---|---|
| winrt::MyProject | MyRuntimeClass |
implementation::MyRuntimeClass |
| winrt::MyProject::implementation | MyProject::MyRuntimeClass |
MyRuntimeClass |
Viktigt!
När du vill returnera en projekterad typ från implementeringen bör du vara försiktig så att du inte instansierar implementeringstypen genom att skriva MyRuntimeClass myRuntimeClass;. Rätt tekniker och kod för det scenariot visas tidigare i det här avsnittet i avsnittet Instansiera och returnera implementeringstyper och gränssnitt.
Problemet med MyRuntimeClass myRuntimeClass; i det scenariot är att det skapar ett winrt::MyProject::implementation::MyRuntimeClass objekt på stacken. Det objektet (av implementeringstyp) fungerar som den planerade typen på vissa sätt– du kan anropa metoder på det på samma sätt. och konverteras till och med till en projekterad typ. Men objektet förstörs enligt vanliga C++-regler när scope avslutas. Så, om du returnerade en projicerad typ (en smart pekare) till det objektet, är den pekaren nu en dinglande pekare.
Den här minnesskadade typen av bugg är svår att diagnostisera. För felsökningsversioner hjälper en C++/WinRT-försäkran dig därför att fånga upp det här misstaget med hjälp av en stackidentifiering. Men coroutines allokeras på högen, så du kommer inte att få hjälp med detta misstag om du gör det inuti en coroutine. Mer information finns i Diagnostisera direktallokeringar.
Använda planerade typer och implementeringstyper med olika C++/WinRT-funktioner
Här är olika platser där en C++/WinRT-funktion förväntar sig en typ och vilken typ den förväntar sig (beräknad typ, implementeringstyp eller båda).
| Egenskap | Accepterar | Noteringar |
|---|---|---|
T (representerar en smart pekare) |
Prognostiserad | Se varningen i Namnområden: beräknade typer, implementeringstyper och fabriker om att använda implementeringstypen av misstag. |
agile_ref<T> |
Båda | Om du använder implementeringstypen måste konstruktorargumentet vara com_ptr<T>. |
com_ptr<T> |
Genomförande | Om du använder den beräknade typen genereras felet: 'Release' is not a member of 'T'. |
default_interface<T> |
Båda | Om du använder implementeringstypen returneras det första implementerade gränssnittet. |
get_self<T> |
Genomförande | Om du använder den beräknade typen genereras felet: '_abi_TrustLevel': is not a member of 'T'. |
guid_of<T>() |
Båda | Returnerar GUID för standardgränssnittet. |
IWinRTTemplateInterface<T> |
Prognostiserad | Om du använder implementeringstypen, kompileras koden, men det är ett misstag, se varningen i Namnområden: projicerade typer, implementeringstyper och fabriker. |
make<T> |
Genomförande | Om du använder den beräknade typen genereras felet: 'implements_type': is not a member of any direct or indirect base class of 'T' |
make_agile(T const&) |
Båda | Om du använder implementeringstypen måste argumentet vara com_ptr<T>. |
make_self<T> |
Genomförande | Om du använder den beräknade typen genereras felet: 'Release': is not a member of any direct or indirect base class of 'T' |
name_of<T> |
Prognostiserad | Om du använder implementeringstypen får du det strängifierade GUID:et för standardgränssnittet. |
weak_ref<T> |
Båda | Om du använder implementeringstypen måste konstruktorargumentet vara com_ptr<T>. |
Delta i enhetlig konstruktion och direkt implementeringsåtkomst
I det här avsnittet beskrivs en funktionalitet i C++/WinRT 2.0 som är valbar, även om den är aktiverad som standard för nya projekt. För ett befintligt projekt måste du anmäla dig genom att konfigurera verktyget cppwinrt.exe. I Visual Studio anger du projektegenskapen Vanliga egenskaper>C++/WinRT>Optimerad till Ja. Det innebär att du lägger till <CppWinRTOptimized>true</CppWinRTOptimized> i projektfilen. Och det har samma effekt som att lägga till växeln när du anropar cppwinrt.exe från kommandoraden.
-opt[imize]-växeln möjliggör det som ofta kallas enhetlig konstruktion. Med enhetlig (eller enhetlig) konstruktion använder du själva C++/WinRT-språkprojektionen för att skapa och använda dina implementeringstyper (typer som implementeras av komponenten, för användning av program) effektivt och utan problem med lastaren.
Innan vi beskriver funktionen ska vi först visa situationen utan enhetlig konstruktion. För att illustrera börjar vi med den här Windows Runtime-exempelklassen.
// MyClass.idl
namespace MyProject
{
runtimeclass MyClass
{
MyClass();
void Method();
static void StaticMethod();
}
}
Som C++-utvecklare som är bekant med att använda C++/WinRT-biblioteket kanske du vill använda klassen så här.
using namespace winrt::MyProject;
MyClass c;
c.Method();
MyClass::StaticMethod();
Och det skulle vara helt rimligt förutsatt att den använda koden som visas inte fanns inom samma komponent som implementerar den här klassen. Som språkprojektion skyddar C++/WinRT dig som utvecklare från ABI (det COM-baserade binära programgränssnittet som Windows Runtime definierar). C++/WinRT anropar inte direkt i implementeringen; det går via ABI.
På kodraden där du skapar ett MyClass -objekt (MyClass c;) anropar C++/WinRT-projektionen därför RoGetActivationFactory för att hämta klassen eller aktiveringsfabriken och använder sedan den fabriken för att skapa objektet. Den sista raden använder också fabriken för att göra vad som verkar vara ett statiskt metodanrop. Allt detta kräver att klassen registreras och att modulen implementerar DllGetActivationFactory startpunkt. C++/WinRT har en mycket snabb fabrikscache, så inget av detta orsakar ett problem för ett program som använder din komponent. Problemet är att du inom din komponent just har gjort något som är lite problematiskt.
För det första, oavsett hur snabbt C++/WinRT-fabrikscachen är, kommer anrop via RoGetActivationFactory- (eller till och med efterföljande anrop via fabrikscachen) alltid att vara långsammare än att anropa direkt i implementeringen. Ett anrop till RoGetActivationFactory följt av IActivationFactory::ActivateInstance följt av QueryInterface kommer uppenbarligen inte att vara lika effektivt som att använda ett C++ new-uttryck för en lokalt definierad typ. Därför är erfarna C++/WinRT-utvecklare vana vid att använda winrt::make eller winrt::make_self helper functions när du skapar objekt i en komponent.
// MyClass c;
MyProject::MyClass c{ winrt::make<implementation::MyClass>() };
Men som du kan se är det inte alls lika bekvämt eller koncist. Du måste använda en hjälpfunktion för att skapa objektet, och du måste också skilja mellan implementeringstypen och den planerade typen.
För det andra innebär användningen av projektionen för att skapa klassen att dess aktiveringsinstans cachelagras. Normalt är det vad du vill, men om fabriken finns i samma modul (DLL) som gör anropet, har du effektivt låst DLL:en och förhindrat den från att lossas. I många fall spelar det ingen roll; men vissa systemkomponenter måste stödja avlastning.
Det är här termen enhetlig konstruktion kommer in. Oavsett om skapandekoden finns i ett projekt som bara använder klassen, eller om den finns i projektet som faktiskt implementerar klassen, kan du fritt använda samma syntax för att skapa objektet.
// MyProject::MyClass c{ winrt::make<implementation::MyClass>() };
MyClass c;
När du bygger ditt komponentprojekt med växeln -opt[imize], kompileras anropet via språkprojektionen till samma effektiva anrop till funktionen winrt::make, som direkt skapar implementationstypen. Det gör din syntax enkel och förutsägbar, det undviker prestandapåverkan vid anrop genom fabriken, och det undviker att låsa komponenten i processen. Förutom komponentprojekt är detta också användbart för XAML-program. Genom att kringgå RoGetActivationFactory- för klasser som implementeras i samma program kan du konstruera dem (utan att behöva registreras) på samma sätt som om de var utanför komponenten.
Enhetlig konstruktion gäller för alla anrop som betjänas av fabriken i bakgrunden. I praktiken innebär det att optimeringen hanterar både konstruktorer och statiska medlemmar. Här är det ursprungliga exemplet igen.
MyClass c;
c.Method();
MyClass::StaticMethod();
Utan -opt[imize]kräver den första och sista instruktionen anrop via fabriksobjektet.
Med-opt[imize]gör ingen av dem det. Och dessa anrop kompileras direkt mot implementeringen och har till och med potential att infogas. Vilket talar till den andra termen som ofta används när man talar om -opt[imize], nämligen direkt implementering åtkomst.
Språkprojektioner är praktiska, men när du har direkt åtkomst till implementeringen kan och bör du dra nytta av det för att skapa den mest effektiva koden som möjligt. C++/WinRT kan göra det åt dig, utan att tvinga dig att lämna projektionens säkerhet och produktivitet.
Detta är en förändring som bryter kompatibiliteten eftersom komponenten måste samarbeta för att språkprojektionen ska kunna nå in och få direkt åtkomst till dess implementeringstyperna. Eftersom C++/WinRT är ett header-fil-bibliotek kan du granska innehållet och se vad som händer. Utan -opt[imize]definieras konstruktorn MyClass och StaticMethod medlem av projektionen så här.
namespace winrt::MyProject
{
inline MyClass::MyClass() :
MyClass(impl::call_factory<MyClass>([](auto&& f){
return f.template ActivateInstance<MyClass>(); }))
{
}
inline void MyClass::StaticMethod()
{
impl::call_factory<MyClass, MyProject::IClassStatics>([&](auto&& f) {
return f.StaticMethod(); });
}
}
Det är inte nödvändigt att följa alla ovanstående; avsikten är att visa att båda anropen omfattar ett anrop till en funktion med namnet call_factory. Det är din ledtråd att dessa anrop omfattar fabrikscachen och att de inte har direkt åtkomst till implementeringen.
Med-opt[imize]definieras inte samma funktioner alls. I stället deklareras de av projektionen och deras definitioner lämnas upp till komponenten.
Komponenten kan sedan tillhandahålla definitioner som anropar direkt till implementeringen. Nu har vi kommit fram till brytförändringen. Dessa definitioner genereras åt dig när du använder både -component och -opt[imize], och de visas i en fil med namnet Type.g.cpp, där Type är namnet på den körningsklass som implementeras. Därför kan du stöta på olika länkfel när du först aktiverar -opt[imize] i ett befintligt projekt. Du måste inkludera den genererade filen i implementeringen för att kunna sy ihop saker och ting.
I vårt exempel kan MyClass.h se ut så här (oavsett om -opt[imize] används).
// MyClass.h
#pragma once
#include "MyClass.g.h"
namespace winrt::MyProject::implementation
{
struct MyClass : ClassT<MyClass>
{
MyClass() = default;
static void StaticMethod();
void Method();
};
}
namespace winrt::MyProject::factory_implementation
{
struct MyClass : ClassT<MyClass, implementation::MyClass>
{
};
}
Din MyClass.cpp är där allt förenas.
#include "pch.h"
#include "MyClass.h"
#include "MyClass.g.cpp" // !!It's important that you add this line!!
namespace winrt::MyProject::implementation
{
void MyClass::StaticMethod()
{
}
void MyClass::Method()
{
}
}
Så om du vill använda enhetlig konstruktion i ett befintligt projekt måste du redigera varje implementerings .cpp fil så att du #include <Sub/Namespace/Type.g.cpp> efter inkluderingen (och definitionen) av implementeringsklassen. Filen innehåller definitionerna av de funktioner som projektionen lämnade odefinierat. Så här ser dessa definitioner ut i filen MyClass.g.cpp.
namespace winrt::MyProject
{
MyClass::MyClass() :
MyClass(make<MyProject::implementation::MyClass>())
{
}
void MyClass::StaticMethod()
{
return MyProject::implementation::MyClass::StaticMethod();
}
}
Detta avslutar projektionen på ett elegant sätt med effektiva anrop direkt till implementeringen, samtidigt som det undviker anrop till fabrikscachen och uppfyller länkningens krav.
Det sista som -opt[imize] gör åt dig är att ändra implementeringen av projektets module.g.cpp (filen som hjälper dig att implementera DLL-filens DllGetActivationFactory och DllCanUnloadNow exporter) på ett sådant sätt att inkrementella versioner tenderar att vara mycket snabbare genom att eliminera den starka typkoppling som krävdes av C++/WinRT 1.0. Detta kallas ofta typraderade fabriker. Utan -opt[imize]startar module.g.cpp-filen som genereras för komponenten genom att inkludera definitionerna för alla dina implementeringsklasser – MyClass.h, i det här exemplet. Sedan skapas implementeringsfabriken direkt för varje klass på detta sätt.
if (requal(name, L"MyProject.MyClass"))
{
return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}
Ännu en gång behöver du inte följa alla detaljer. Det som är användbart att se är att detta kräver en fullständig definition för alla klasser som implementeras av komponenten. Detta kan ha en dramatisk effekt på den inre loopen eftersom alla ändringar i en enda implementering gör att module.g.cpp kompileras om. Med -opt[imize]är detta inte längre fallet. I stället händer två saker med den genererade module.g.cpp-filen. Den första är att den inte längre innehåller några implementeringsklasser. I det här exemplet inkluderas inte MyClass.h alls. I stället skapas implementeringsfabrikerna utan någon kunskap om implementeringen.
void* winrt_make_MyProject_MyClass();
if (requal(name, L"MyProject.MyClass"))
{
return winrt_make_MyProject_MyClass();
}
Det är uppenbart att det inte finns något behov av att inkludera deras definitioner, och det är upp till länkaren att lösa definitionen för funktionen winrt_make_Component_Class. Naturligtvis behöver du inte tänka på detta, eftersom den MyClass.g.cpp fil som genereras åt dig (och som du tidigare inkluderade för att stödja enhetlig konstruktion) också definierar den här funktionen. Här är hela MyClass.g.cpp filen som genereras för det här exemplet.
void* winrt_make_MyProject_MyClass()
{
return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}
namespace winrt::MyProject
{
MyClass::MyClass() :
MyClass(make<MyProject::implementation::MyClass>())
{
}
void MyClass::StaticMethod()
{
return MyProject::implementation::MyClass::StaticMethod();
}
}
Som du ser skapar funktionen winrt_make_MyProject_MyClass direkt implementeringens fabrik. Allt detta innebär att du gärna kan ändra en viss implementering och module.g.cpp behöver inte kompileras om alls. Det är först när du lägger till eller tar bort Windows Runtime-klasser som module.g.cpp uppdateras och behöver kompileras om.
Åsidosätta virtuella metoder i basklassen
Din härledda klass kan ha problem med virtuella metoder om både basklassen och den härledda klassen är appdefinierade klasser, men den virtuella metoden definieras i en grandparent Windows Runtime-klass. I praktiken händer detta om du härleder från XAML-klasser. Resten av det här avsnittet fortsätter från exemplet i härledda klasser.
namespace winrt::MyNamespace::implementation
{
struct BasePage : BasePageT<BasePage>
{
void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
struct DerivedPage : DerivedPageT<DerivedPage>
{
void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
}
Hierarkin är Windows::UI::Xaml::Controls::Page<– BasePage<– DerivedPage. Metoden BasePage::OnNavigatedFrom åsidosätter korrekt page::OnNavigatedFrom, men DerivedPage::OnNavigatedFrom åsidosätter inte BasePage::OnNavigatedFrom.
Här återanvänder DerivedPageIPageOverrides vtable från BasePage, vilket innebär att den inte åsidosätter metoden IPageOverrides::OnNavigatedFrom. En potentiell lösning kräver att BasePage själv är en mallklass och att implementeringen helt och hållet finns i en headerfil, men detta gör saker och ting oacceptabelt komplicerade.
Som en lösning deklarerar du metoden OnNavigatedFrom som explicit virtuell i basklassen. På så sätt, när vtable-posten för DerivedPage::IPageOverrides::OnNavigatedFrom anropar BasePage::IPageOverrides::OnNavigatedFrom, anropar producenten BasePage::OnNavigatedFrom, som (på grund av dess virtualitet) i sin tur anropar DerivedPage::OnNavigatedFrom.
namespace winrt::MyNamespace::implementation
{
struct BasePage : BasePageT<BasePage>
{
// Note the `virtual` keyword here.
virtual void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
struct DerivedPage : DerivedPageT<DerivedPage>
{
void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
}
Detta kräver att alla medlemmar i klasshierarkin är överens om returvärdet och parametertyperna för metoden OnNavigatedFrom. Om de inte håller med bör du använda versionen ovan som den virtuella metoden och omsluta alternativen.
Anmärkning
Din IDL behöver inte deklarera den åsidosatta metoden. Mer information finns i Implementera åsidosättbara metoder.
Viktiga API:er
- winrt::com_ptr struktmall
- winrt::com_ptr::copy_from funktion
- winrt::from_abi funktionsmall
- winrt::get_self funktionsmall
- winrt::implements struct template
- winrt::make funktionsmall
- winrt::make_self funktionsmall
- winrt::Windows::Foundation::IUnknown::som funktion
- winrt::Windows::Foundation::IUnknown::try_as funktion