Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Toto téma ukazuje, jak vytvářet rozhraní API C++/WinRT pomocí winrt::implementuje základní strukturu přímo nebo nepřímo. Synonyma pro vytvářet v tomto kontextu jsou produkovat nebo implementovat. Toto téma popisuje následující scénáře implementace rozhraní API pro typ C++/WinRT v tomto pořadí.
Note
Toto téma se týká tématu prostředí Windows Runtime komponent, ale pouze v kontextu C++/WinRT. Pokud hledáte obsah o komponentách prostředí Windows Runtime, které pokrývají všechny prostředí Windows Runtime jazyky, podívejte se na prostředí Windows Runtime komponenty.
- Nevytváříte třídu prostředí Windows Runtime (třídu runtime), ale chcete implementovat jedno nebo více rozhraní prostředí Windows Runtime pro místní spotřebu v aplikaci. Odvozujete přímo z winrt::implements v tomto případě a implementujete funkce.
- Vytváříte třídu runtime. Možná vytvoříte komponentu, která se bude využívat z aplikace. Nebo můžete vytvořit typ, který se má využívat z uživatelského rozhraní XAML, a v takovém případě implementujete i využíváte třídu modulu runtime ve stejné kompilační jednotce. V těchto případech necháte nástroje generovat třídy, které jsou odvozeny z winrt::implements.
V obou případech se typ, který implementuje rozhraní API C++/WinRT, nazývá typ implementace.
Důležité
Je důležité odlišit koncept typu implementace od typu projektu. Projektovaný typ je popsán v článku Consume APIs with C++/WinRT.
Pokud nevytváříte třídu runtime
Nejjednodušší scénář nastává tehdy, když váš typ implementuje rozhraní prostředí Windows Runtime a tento typ budete používat v rámci stejné aplikace. V takovém případě váš typ nemusí být třídou modulu runtime; jen obyčejná třída jazyka C++. Můžete například psát desktopovou aplikaci WinUI 3 založenou na Microsoft::UI::Xaml::Application.
Pokud je na váš typ odkazováno z uživatelského rozhraní XAML, pak skutečně musí jít o modul runtime, i když je ve stejném projektu jako XAML. V takovém případě si přečtěte část Pokud vytvoříte třídu modulu runtime, na kterou se má odkazovat v uživatelském rozhraní XAML.
Note
Informace o instalaci a použití rozšíření C++/WinRT Visual Studio (VSIX) a balíčku NuGet (které společně poskytují podporu šablony projektu a sestavení) najdete v tématu Visual Studio podpora pro C++/WinRT.
V Visual Studio znázorňuje šablona projektu Blank App, Packaged (WinUI 3 in Desktop) pro C++ vzor aplikace WinUI 3. Třída aplikace je odvozena z Microsoft::UI::Xaml::Application a vstupní bod volá metodu Start.
#include "App.xaml.h"
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
winrt::init_apartment();
::winrt::Microsoft::UI::Xaml::Application::Start(
[](auto&&) { ::winrt::make<App>(); });
}
Třída aplikace vytvoří Microsoft::UI::Xaml::Window a aktivuje ji v onLaunched.
// App.xaml.h
struct App : AppT<App>
{
App();
void OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&);
private:
winrt::Microsoft::UI::Xaml::Window window{ nullptr };
};
// App.xaml.cpp
void App::OnLaunched(LaunchActivatedEventArgs const&)
{
window = make<MainWindow>();
window.Activate();
}
C++/WinRT má základní šablonu struktury winrt::implementuje , aby bylo snadné implementovat rozhraní (nebo několik) bez nutnosti použití programování ve stylu MODELU COM. Jednoduše odvodíte typ z implementací a pak implementujete funkce rozhraní. Tady je příklad, který implementuje vlastní rozhraní.
struct MyType : implements<MyType, IStringable>
{
hstring ToString()
{
return L"MyType";
}
};
Pokud v komponentě prostředí Windows Runtime vytváříte běhovou třídu
Pokud je váš typ zabalený v komponentě prostředí Windows Runtime pro použití v jiném binárním souboru (tím druhým binárním souborem je obvykle aplikace), musí být vaším typem třída prostředí runtime. Deklarujete třídu runtime v souboru Microsoft Interface Definition Language (IDL) (.idl) (viz Rozdělení tříd runtime do souborů MIDL (.idl)).
Z každého souboru IDL vznikne soubor .winmd a Visual Studio všechny tyto soubory sloučí do jediného souboru se stejným názvem jako váš kořenový obor názvů. Tento konečný .winmd soubor bude ten, na který budou odkazovat příjemci vaší komponenty.
Tady je příklad deklarování třídy runtime v souboru IDL.
// 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;
}
}
Tento soubor IDL definuje třídu prostředí prostředí Windows Runtime. Třída runtime je typ, který lze aktivovat a používat prostřednictvím moderních rozhraní COM, typicky napříč hranicemi spustitelných souborů. Když do projektu přidáte soubor IDL a sestavíte, sada nástrojů C++/WinRT (midl.exe a cppwinrt.exe) vygeneruje typ implementace za vás. Ukázku použití pracovního postupu souboru IDL v praxi najdete v článku Ovládací prvky XAML; vytvoření vazby na vlastnost C++/WinRT.
Na základě výše uvedeného příkladu IDL je typem implementace stub struktury C++ s názvem winrt::MyProject::implementation::MyRuntimeClass ve zdrojových souborech s názvy \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h a MyRuntimeClass.cpp.
Typ implementace vypadá takto.
// 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.
Všimněte si, že se zde používá vzor F-vázaného polymorfismu (MyRuntimeClass používá samo sebe jako argument šablony pro svou základní třídu MyRuntimeClassT). To se také označuje jako podivně opakující se vzor šablony (CRTP). Pokud budete postupovat po řetězci dědičnosti směrem vzhůru, narazíte na MyRuntimeClass_base.
Implementaci jednoduchých vlastností můžete zjednodušit pomocí knihoven Windows implementace (WIL). Postupujte následovně:
// MyRuntimeClass.h
...
namespace winrt::MyProject::implementation
{
struct MyRuntimeClass : MyRuntimeClassT<MyRuntimeClass>
{
MyRuntimeClass() = default;
wil::single_threaded_rw_property<winrt::hstring> Name;
};
}
template <typename D, typename... I>
struct MyRuntimeClass_base : implements<D, MyProject::IMyRuntimeClass, I...>
V tomto scénáři je tedy opět v kořeni hierarchie dědičnosti základní šablona struktury winrt::implements.
Další informace, kód a podrobný návod k vytváření rozhraní API v komponentě prostředí Windows Runtime najdete v tématech Komponenty prostředí Windows Runtime pomocí C++/WinRT a Vytváření událostí v C++/WinRT.
Pokud vytvoříte třídu modulu runtime, na kterou se má odkazovat v uživatelském rozhraní XAML
Pokud vaše uživatelské rozhraní XAML odkazuje na váš typ, musí jít o třídu runtime, i když je ve stejném projektu jako XAML. I když jsou obvykle aktivovány přes hranice spustitelného souboru, lze místo toho použít třídu runtime v rámci kompilační jednotky, která ji implementuje.
V tomto scénáři pracujete na vytváření i používání rozhraní API. Postup implementace třídy runtime je v podstatě stejný jako pro prostředí Windows Runtime komponentu. Podívejte se tedy na předchozí oddíl – Pokud v komponentě prostředí Windows Runtime vytváříte třídu runtime. Jedinými podrobnostmi, které se liší, je, že od IDL generuje sada nástrojů C++/WinRT nejen typ implementace, ale také projektovaný typ. Je důležité si uvědomit, že pokud v tomto scénáři uvedete jen "MyRuntimeClass", může to být nejednoznačné; existuje několik prvků s tímto názvem, a to různých druhů.
- MyRuntimeClass je název třídy modulu runtime. Ale to je opravdu abstrakce: deklarována v IDL a implementována v některém programovacím jazyce.
-
MyRuntimeClass je název struktury C++ winrt::MyProject::implementation::MyRuntimeClass, což je implementace C++/WinRT třídy runtime. Jak jsme viděli, pokud existují samostatné implementace a využívání projektů, pak tato struktura existuje pouze v prováděcím projektu. Toto je typ implementace nebo implementace. Tento typ je generován (
cppwinrt.exenástrojem) v souborech\MyProject\MyProject\Generated Files\sources\MyRuntimeClass.haMyRuntimeClass.cpp. -
MyRuntimeClass je název projektovaného typu ve formě struktury C++ winrt::MyProject::MyRuntimeClass. Pokud existují samostatné implementace a využívání projektů, tato struktura existuje pouze v projektu využívajícím. To je promítnutý typ neboli projekce. Tento typ je generován (podle
cppwinrt.exe) v souboru\MyProject\MyProject\Generated Files\winrt\impl\MyProject.2.h.
Tady jsou části předpokládaného typu, které jsou relevantní pro toto téma.
// MyProject.2.h
...
namespace winrt::MyProject
{
struct MyRuntimeClass : MyProject::IMyRuntimeClass
{
MyRuntimeClass(std::nullptr_t) noexcept {}
MyRuntimeClass();
};
}
Ukázku postupu implementace rozhraní INotifyPropertyChanged ve třídě runtime najdete v tématu Ovládací prvky XAML; vazba na vlastnost C++/WinRT.
Postup použití vaší třídy modulu runtime v tomto scénáři je popsán v tématu Používání rozhraní API s C++/WinRT.
Vyčlenění tříd runtime do souborů MIDL (.idl)
Šablony projektu a položek Visual Studio vytvoří samostatný soubor IDL pro každou třídu modulu runtime. To dává logickou korespondenci mezi souborem IDL a jeho vygenerovanými zdrojovými soubory kódu.
Pokud ale sloučit všechny třídy modulu runtime projektu do jednoho souboru IDL, může to výrazně zlepšit dobu sestavení. Pokud byste mezi nimi jinak měli složité (nebo kruhové) import závislosti, může být sloučení skutečně nezbytné. A pokud jsou třídy modulu runtime společně, můžete je snadněji vytvářet a kontrolovat.
Konstruktory tříd modulu runtime
Zde je několik poznatků, které si lze odnést z výpisů uvedených výše.
- Každý konstruktor, který deklarujete v IDL, způsobí, že konstruktor se vygeneruje jak pro váš typ implementace, tak pro projektovaný typ. Konstruktory deklarované IDL se používají k využívání třídy runtime z jiné kompilační jednotky.
- Bez ohledu na to, zda máte konstruktory deklarované v IDL, nebo ne, pro váš promítnutý typ se vygeneruje přetížení konstruktoru, které přijímá std::nullptr_t. Volání konstruktoru std::nullptr_t je prvním ze dvou kroků při použití běhové třídy ze stejné kompilační jednotky. Další podrobnosti a příklad kódu najdete v tématu Využití rozhraní API s C++/WinRT.
- Pokud používáte třídu runtime ze stejné kompilační jednotky, můžete také implementovat nevýchozí konstruktory přímo v typu implementace (který je, nezapomeňte, v
MyRuntimeClass.h).
Note
Pokud očekáváte, že vaše runtime třída bude používána z jiné kompilační jednotky (což je běžné), zahrňte do IDL konstruktor(y) (alespoň výchozí konstruktor). Tím také získáte factory implementaci vedle svého implementačního typu.
Pokud chcete třídu runtime vytvářet a používat pouze v rámci téže jednotky kompilace, pak v IDL nedeklarujte žádné konstruktory. Nepotřebujete implementaci továrny a ani se žádná nevygeneruje. Výchozí konstruktor vašeho typu implementace se odstraní, ale místo toho ho můžete snadno upravit a nastavit jako výchozí.
Pokud chcete vytvořit a využívat třídu runtime pouze ve stejné kompilační jednotce a potřebujete parametry konstruktoru, pak vytvořte konstruktory, které potřebujete přímo ve svém typu implementace.
Metody, vlastnosti a události třídy runtime
Viděli jsme, že pracovní postup je takový, že pomocí IDL deklarujete svou běhovou třídu a její členy a nástroje pak pro vás vygenerují prototypy a stub implementace. Stejně jako u těch automaticky vygenerovaných prototypů pro členy třídy runtime je můžete upravit tak, aby předávaly různé typy z typů, které deklarujete v IDL. Můžete to ale provést pouze za předpokladu, že typ, který deklarujete v IDL, lze předat do typu, který deklarujete v implementované verzi.
Tady je několik příkladů.
- Můžete uvolnit typy parametrů. Pokud například v IDL vaše metoda přebírá SomeClass, pak byste se mohli rozhodnout změnit tuto možnost na IInspectable ve vaší implementaci. To funguje, protože jakýkoli objekt typu SomeClass lze převést na IInspectable (obráceně by to samozřejmě nefungovalo).
- Kopírovatelný parametr můžete přijmout hodnotou namísto referencí. Například změňte
SomeClass const&naSomeClass. To je nezbytné v případě, že potřebujete zabránit zachycení odkazu do korutiny (viz předávání parametrů). - Návratovou hodnotu můžete uvolnit. Můžete například změnit void na winrt::fire_and_forget.
Poslední dvě jsou velmi užitečné, když píšete asynchronní obsluhu události.
Vytváření instancí a vracení typů implementací a rozhraní
V této části si vezměme jako příklad typ implementace s názvem MyType, který implementuje rozhraní IStringable a IClosable .
MyType můžete odvodit přímo z winrt::implements (nejedná se o třídu runtime).
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
struct MyType : implements<MyType, IStringable, IClosable>
{
winrt::hstring ToString(){ ... }
void Close(){}
};
Nebo ho můžete vygenerovat z IDL (je to třída modulu runtime).
// MyType.idl
namespace MyProject
{
runtimeclass MyType: Windows.Foundation.IStringable, Windows.Foundation.IClosable
{
MyType();
}
}
Nemůžete přímo přidělit typ implementace.
MyType myimpl; // error C2259: 'MyType': cannot instantiate abstract class
Můžete ale přejít z MyType na IStringable nebo IClosable objekt, který můžete použít nebo vrátit jako součást projekce voláním winrt::make function template. make vrátí výchozí rozhraní typu implementace.
IStringable istringable = winrt::make<MyType>();
Note
Pokud ale odkazujete na typ z uživatelského rozhraní XAML, bude existovat typ implementace i projektovaný typ ve stejném projektu. V takovém případě make vrátí instanci projektovaného typu. Příklad kódu tohoto scénáře najdete v tématu Ovládací prvky XAML; vazba na vlastnost C++/WinRT.
Můžeme použít istringable (v příkladu kódu výše) pouze k volání členů rozhraní IStringable . Rozhraní C++/WinRT (což je projektované rozhraní) je však odvozeno z winrt::Windows::Foundation::IUnknown. Můžete tedy volat IUnknown::as (nebo IUnknown::try_as) a dotazovat se na jiné projektované typy nebo rozhraní, které můžete použít nebo vrátit.
Návod
Scénář, ve kterém byste neměli volat as ani try_as, je odvozování tříd za běhu („komponovatelné třídy“). Pokud typ implementace skládá jinou třídu, nevolejte as ani try_as, abyste provedli nekontrolované nebo kontrolované QueryInterface skládané třídy. Místo toho přistupujte k datovému členu (this->) m_inner a na něm zavolejte as nebo try_as. Další informace naleznete v tématu Odvození třídy modulu runtime v tomto tématu.
istringable.ToString();
IClosable iclosable = istringable.as<IClosable>();
iclosable.Close();
Pokud potřebujete získat přístup ke všem členům implementace a později vrátit rozhraní volajícímu, použijte šablonu funkce winrt::make_self . make_self vrátí winrt::com_ptr, která obaluje typ implementace. Můžete přistupovat ke členům všech jeho rozhraní (pomocí operátoru šipky), můžete ho vrátit volajícímu beze změny nebo pro něj můžete zavolat as a vrátit volajícímu výsledný objekt rozhraní.
winrt::com_ptr<MyType> myimpl = winrt::make_self<MyType>();
myimpl->ToString();
myimpl->Close();
IClosable iclosable = myimpl.as<IClosable>();
iclosable.Close();
MyType třída není součástí projekce; je to implementace. Tímto způsobem ale můžete přímo volat metody jeho implementace bez režie volání virtuální funkce. V předchozím příkladu, i když MyType::ToString používá stejný podpis jako projektovaná metoda na IStringable, voláme ne-virtuální metodu přímo bez překročení binárního rozhraní aplikace (ABI).
Com_ptr jednoduše obsahuje ukazatel na strukturu MyType, takže můžete také přistupovat k jakýmkoli dalším interním podrobnostem MyType prostřednictvím myimpl proměnné a operátoru šipky.
V případě, že máte objekt rozhraní a víte, že se jedná o rozhraní ve vaší implementaci, můžete se k implementaci vrátit pomocí šablony funkce winrt::get_self . Jde opět o techniku, která se vyhýbá volání virtuálních funkcí a umožňuje přímý přístup k implementaci.
Note
Pokud jste nenainstalovali sadu Windows SDK verze 10.0.17763.0 (Windows 10, verze 1809) nebo novější, musíte volat winrt::from_abi místo winrt::get_self.
Tady je příklad. Další příklad najdete v Implementaci třídy vlastního ovládacího prvku BgLabelControl.
void ImplFromIClosable(IClosable const& from)
{
MyType* myimpl = winrt::get_self<MyType>(from);
myimpl->ToString();
myimpl->Close();
}
Ale pouze původní objekt rozhraní uchovává odkaz. Pokud si ho chcete ponechat, můžete zavolat 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.
Samotný typ implementace není odvozen od winrt::Windows::Foundation::IUnknown, takže nemá žádnou funkci. I tak, jak vidíte ve výše uvedené funkci ImplFromIClosable , můžete přistupovat ke členům všech jeho rozhraní. Pokud to ale uděláte, pak nevracejte volajícímu nezpracovanou instanci typu implementace. Místo toho použijte některou z již zobrazených technik a vraťte projektované rozhraní nebo com_ptr.
Pokud máte instanci typu implementace a potřebujete ji předat funkci, která očekává odpovídající projektovaný typ, můžete to udělat, jak je znázorněno v příkladu kódu níže. U vašeho typu implementace existuje operátor převodu (za předpokladu cppwinrt.exe , že nástroj vygeneroval typ implementace), který to umožňuje. Hodnotu typu implementace můžete předat přímo metodě, která očekává hodnotu odpovídajícího projektovaného typu. Z členské funkce typu implementace můžete předat *this metodě, která očekává hodnotu odpovídajícího projektovaného typu.
// 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);
}
...
Odvození třídy modulu runtime
Můžete vytvořit třídu modulu runtime, která je odvozena z jiné třídy modulu runtime za předpokladu, že je základní třída deklarována jako nezapečetěná. prostředí Windows Runtime termín pro odvození třídy je "kompozibilní třídy". Kód pro implementaci odvozené třídy závisí na tom, zda je základní třída poskytována jinou komponentou nebo stejnou komponentou. Naštěstí se tato pravidla nemusíte učit – stačí zkopírovat ukázkové implementace z sources výstupní složky vytvořené kompilátorem cppwinrt.exe .
Podívejte se na tento příklad.
// MyProject.idl
namespace MyProject
{
[default_interface]
runtimeclass MyButton : Microsoft.UI.Xaml.Controls.Button
{
MyButton();
}
unsealed runtimeclass MyBase
{
MyBase();
overridable Int32 MethodOverride();
}
[default_interface]
runtimeclass MyDerived : MyBase
{
MyDerived();
}
}
Ve výše uvedeném příkladu je MyButton odvozen z ovládacího prvku XAML Button , který je poskytován jinou komponentou. V takovém případě implementace vypadá stejně jako implementace nekompozibilní třídy:
namespace winrt::MyProject::implementation
{
struct MyButton : MyButtonT<MyButton>
{
};
}
namespace winrt::MyProject::factory_implementation
{
struct MyButton : MyButtonT<MyButton, implementation::MyButton>
{
};
}
Na druhou stranu v předchozím příkladu je MyDerived odvozen z jiné třídy ve stejné komponentě. V tomto případě implementace vyžaduje další parametr šablony určující třídu implementace pro základní třídu.
namespace winrt::MyProject::implementation
{
struct MyDerived : MyDerivedT<MyDerived, implementation::MyBase>
{ // ^^^^^^^^^^^^^^^^^^^^^^
};
}
namespace winrt::MyProject::factory_implementation
{
struct MyDerived : MyDerivedT<MyDerived, implementation::MyDerived>
{
};
}
V obou případech může vaše implementace volat metodu základní třídy tak, že ji kvalifikuje aliasem typu 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();
}
};
}
Návod
Pokud typ implementace skládá jinou třídu, nevolejte as ani try_as, abyste provedli nekontrolované nebo kontrolované QueryInterface skládané třídy. Místo toho přistupujte k datovému členu (this->) m_inner a na něm zavolejte as nebo try_as.
Odvození z typu, který má jiný než výchozí konstruktor
ToggleButtonAutomationPeer::ToggleButtonAutomationPeer(ToggleButton) je příkladem jiného než výchozího konstruktoru. Neexistuje žádný výchozí konstruktor, takže abyste vytvořili instanci ToggleButtonAutomationPeer, musíte předat parametr owner. Pokud tedy dědíte z ToggleButtonAutomationPeer, musíte poskytnout konstruktor, který přijímá parametr owner a předá ho základní třídě. Pojďme se podívat, jak to vypadá v praxi.
// MySpecializedToggleButton.idl
namespace MyNamespace
{
runtimeclass MySpecializedToggleButton :
Microsoft.UI.Xaml.Controls.Primitives.ToggleButton
{
...
};
}
// MySpecializedToggleButtonAutomationPeer.idl
namespace MyNamespace
{
runtimeclass MySpecializedToggleButtonAutomationPeer :
Microsoft.UI.Xaml.Automation.Peers.ToggleButtonAutomationPeer
{
MySpecializedToggleButtonAutomationPeer(MySpecializedToggleButton owner);
};
}
Vygenerovaný konstruktor pro váš typ implementace vypadá takto.
// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
(MyNamespace::MySpecializedToggleButton const& owner)
{
...
}
...
Jedinou chybějící součástí je, že tento parametr konstruktoru je potřeba předat do základní třídy. Pamatujete si vzor polymorfismu vázaného na F, který jsme zmínili výše? Jakmile se seznámíte s podrobnostmi tohoto vzoru tak, jak ho používá C++/WinRT, můžete zjistit, jak se jmenuje vaše základní třída (nebo se prostě podívat do hlavičkového souboru vaší implementační třídy). Takto se v tomto případě volá konstruktor základní třídy.
// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
(MyNamespace::MySpecializedToggleButton const& owner) :
MySpecializedToggleButtonAutomationPeerT<MySpecializedToggleButtonAutomationPeer>(owner)
{
...
}
...
Konstruktor základní třídy očekává ToggleButton. A MySpecializedToggleButtonjeToggleButton.
Dokud neprovedete výše popsanou úpravu (abyste tento parametr konstruktoru předali základní třídě), kompilátor označí váš konstruktor jako chybný a upozorní na to, že u typu s názvem (v tomto případě) MySpecializedToggleButtonAutomationPeer_base<MySpecializedToggleButtonAutomationPeer> není k dispozici žádný odpovídající výchozí konstruktor. To je vlastně základní třída basové třídy vašeho typu implementace.
Obory názvů: projektované typy, typy implementace a továrny
Jak jste viděli dříve v tomto tématu, třída modulu runtime C++/WinRT existuje ve formě více než jedné třídy C++ ve více než jednom oboru názvů. Název MyRuntimeClass má tedy jeden význam v winrt::MyProject oboru názvů a jiný význam v winrt::MyProject::implementace oboru názvů. Mějte na paměti, který obor názvů aktuálně máte v kontextu, a pokud potřebujete název z jiného oboru názvů, použijte předpony oboru názvů. Pojďme se podrobněji podívat na příslušné obory názvů.
- winrt::MyProject. Tento obor názvů obsahuje projektované typy. Objekt promítaného typu je proxy; v podstatě jde o inteligentní ukazatel na podkladový objekt, přičemž tento podkladový objekt může být implementován zde ve vašem projektu, nebo může být implementován v jiné kompilační jednotce.
- winrt::MyProject::implementation. Tento obor názvů obsahuje typy implementace. Objekt typu implementace není ukazatel; je to hodnota – úplný objekt zásobníku jazyka C++. Nekonstruujte přímo typ implementace; místo toho zavolejte winrt::make a předejte typ implementace jako parametr šablony. V tomto tématu jsme již dříve ukázali příklady použití winrt::make a další příklad najdete v tématu Ovládací prvky XAML; vazba na vlastnost C++/WinRT. Viz také Diagnostika přímých alokací.
- winrt::MyProject::factory_implementation. Tento obor názvů obsahuje továrny. Objekt v tomto oboru názvů podporuje IActivationFactory.
Tato tabulka uvádí minimální kvalifikátor oboru názvů, který je třeba použít v různých kontextech.
| Jmenný prostor v aktuálním kontextu | Určení předpokládaného typu | Určení typu implementace |
|---|---|---|
| winrt::MyProject | MyRuntimeClass |
implementation::MyRuntimeClass |
| winrt::MyProject::implementation | MyProject::MyRuntimeClass |
MyRuntimeClass |
Důležité
Pokud chcete ve své implementaci vrátit promítaný typ, dávejte pozor, abyste nevytvořili instanci implementačního typu tak, že napíšete MyRuntimeClass myRuntimeClass;. Správné techniky a kód pro tento scénář jsou uvedeny dříve v tomto tématu v části Vytváření instancí a vracení typů implementace a rozhraní.
Problém v MyRuntimeClass myRuntimeClass; tomto scénáři spočívá v tom, že vytvoří winrt::MyProject::implementation::MyRuntimeClass objekt v zásobníku. Tento objekt (typu implementace) se chová jako projektovaný typ několika způsoby – můžete na něj vyvolat metody stejným způsobem; a dokonce se převede na projektovaný typ. Ale objekt se podle běžných pravidel jazyka C++ zničí při opuštění oboru platnosti. Pokud jste tedy vrátili projektovaný typ (inteligentní ukazatel) odkazující na tento objekt, pak je tento ukazatel nyní visící.
Tento typ poškození paměti je obtížné diagnostikovat. Takže v ladicích sestaveních vám assert C++/WinRT pomůže odhalit tento omyl pomocí detektoru zásobníku. Ale korutiny se alokují na haldě, takže pokud tuto chybu uděláte uvnitř korutiny, nic vás na ni neupozorní. Další informace najdete v tématu Diagnostika přímých přidělení.
Použití projektovaných typů a typů implementace s různými funkcemi C++/WinRT
Tady jsou různá místa, kde funkce C++/WinRT očekávají typ a jaký typ očekává (projektovaný typ, typ implementace nebo obojí).
| funkce | Přijímá | Notes |
|---|---|---|
T (představující inteligentní ukazatel) |
Předpokládané | Podívejte se na upozornění v oborech názvů: předpokládané typy, typy implementace a továrny o použití typu implementace omylem. |
agile_ref<T> |
Both | Pokud použijete typ implementace, pak musí být com_ptr<T>argument konstruktoru . |
com_ptr<T> |
Implementation | Při použití projektovaného typu se vygeneruje chyba: 'Release' is not a member of 'T'. |
default_interface<T> |
Both | Pokud použijete typ implementace, vrátí se první implementované rozhraní. |
get_self<T> |
Implementation | Při použití projektovaného typu se vygeneruje chyba: '_abi_TrustLevel': is not a member of 'T'. |
guid_of<T>() |
Both | Vrátí identifikátor GUID výchozího rozhraní. |
IWinRTTemplateInterface<T> |
Projektováno | Použití implementačního typu se sice zkompiluje, ale je to chyba — viz upozornění v Obory názvů: projektované typy, implementační typy a továrny. |
make<T> |
Implementation | Při použití projektovaného typu se vygeneruje chyba: 'implements_type': is not a member of any direct or indirect base class of 'T' |
make_agile(T const&) |
Both | Pokud použijete typ implementace, pak argument musí být com_ptr<T>. |
make_self<T> |
Implementation | Při použití projektovaného typu se vygeneruje chyba: 'Release': is not a member of any direct or indirect base class of 'T' |
name_of<T> |
Předpokládané | Pokud použijete typ implementace, získáte řetězecifikovaný identifikátor GUID výchozího rozhraní. |
weak_ref<T> |
Both | Pokud použijete typ implementace, pak musí být com_ptr<T>argument konstruktoru . |
Přihlaste se k jednotné konstrukci a přímému přístupu k implementaci
Tato část popisuje funkci C++/WinRT 2.0, která má výslovný souhlas, i když je ve výchozím nastavení povolená pro nové projekty. U existujícího projektu se budete muset přihlásit konfigurací cppwinrt.exe nástroje. V Visual Studio nastavte vlastnost projektu Common Properties>C++/WinRT>Optimized na Ano. To má vliv na přidání <CppWinRTOptimized>true</CppWinRTOptimized> do souboru projektu. A má stejný účinek jako přidání přepínače při vyvolání cppwinrt.exe z příkazového řádku.
Přepínač -opt[imize] umožňuje to, co se často označuje jako jednotná konstrukce. Při použití jednotné (nebo unifikované) konstrukce používáte přímo jazykovou projekci C++/WinRT k efektivnímu vytváření a používání svých implementačních typů (typů implementovaných vaší komponentou a určených pro využití aplikacemi), a to bez jakýchkoli potíží se zavaděčem.
Než popíšeme tuto funkci, nejprve si ukážeme situaci bez jednotné konstrukce. Pro ilustraci začneme s tímto příkladem prostředí Windows Runtime třídy.
// MyClass.idl
namespace MyProject
{
runtimeclass MyClass
{
MyClass();
void Method();
static void StaticMethod();
}
}
Jako vývojář C++ obeznámený s používáním knihovny C++/WinRT můžete chtít použít tuto třídu.
using namespace winrt::MyProject;
MyClass c;
c.Method();
MyClass::StaticMethod();
A to by bylo naprosto rozumné za předpokladu, že se zobrazený spotřebující kód nenacházal ve stejné komponentě, která implementuje tuto třídu. Jako jazyková projekce vás C++/WinRT jako vývojáře odstíní od ABI (aplikačního binárního rozhraní založeného na modelu COM, které definuje prostředí Windows Runtime). C++/WinRT nevolá přímo implementaci; volání probíhá přes ABI.
V důsledku toho projekce C++/WinRT na řádku kódu, na kterém vytváříte objekt MyClass (MyClass c;), volá funkci RoGetActivationFactory, aby získala třídu nebo aktivační továrnu, a poté tuto továrnu použije k vytvoření objektu. Poslední řádek stejně tak využívá továrnu k vytvoření toho, co se jeví jako volání statické metody. To vše vyžaduje, aby vaše třída byla zaregistrována a že váš modul implementuje vstupní bod DllGetActivationFactory . C++/WinRT má velmi rychlou mezipaměť továrny, takže žádná z těchto možností nezpůsobuje problém pro aplikaci, která vaši komponentu využívá. Problém spočívá v tom, že jste ve vaší komponentě právě udělali něco, co je trochu problematické.
Za prvé, bez ohledu na to, jak rychlá je mezipaměť objektu factory v C++/WinRT, bude volání prostřednictvím RoGetActivationFactory (nebo i následná volání přes mezipaměť objektu factory) vždy pomalejší než přímé volání implementace. Volání RoGetActivationFactory následované IActivationFactory::ActivateInstance následované QueryInterface zřejmě nebude tak efektivní jako použití výrazu C++ new pro místně definovaný typ. V důsledku toho jsou vývojáři C++/WinRT zvyklí používat winrt::make nebo winrt::make_self pomocné funkce při vytváření objektů v rámci komponenty.
// MyClass c;
MyProject::MyClass c{ winrt::make<implementation::MyClass>() };
Ale jak vidíte, není to skoro tak pohodlné ani stručné. K vytvoření objektu je nutné použít pomocnou funkci a musíte také jednoznačně určit mezi typem implementace a projektovaným typem.
Za druhé, použití projekce pro vytvoření třídy znamená, že její aktivační továrna bude uložena v mezipaměti. Normálně je to to, co chcete, ale pokud se factory objekt nachází ve stejném modulu (DLL), ze kterého se volání provádí, pak jste DLL fakticky zablokovali a zabránili tomu, aby se někdy uvolnila. V mnoha případech nezáleží na tom; některé systémové součásti však musí podporovat uvolňování.
To je místo, kde přichází termín jednotné konstrukce . Bez ohledu na to, jestli se kód vytvoření nachází v projektu, který pouze využívá třídu, nebo zda se nachází v projektu, který skutečně implementuje třídu, můžete k vytvoření objektu volně použít stejnou syntaxi.
// MyProject::MyClass c{ winrt::make<implementation::MyClass>() };
MyClass c;
Když sestavíte projekt komponenty s přepínačem -opt[imize], volání prostřednictvím jazykové projekce se přeloží na stejné efektivní volání funkce winrt::make, která přímo vytvoří implementační typ. Díky tomu je syntaxe jednoduchá a předvídatelná, nedochází k žádné režii výkonu při volání přes továrnu a zároveň se tím předejde připnutí komponenty. Kromě projektů komponent je to také užitečné pro aplikace XAML. Obcházení RoGetActivationFactory pro třídy implementované ve stejné aplikaci umožňuje vytvořit je (bez nutnosti registrace) všemi stejnými způsoby, jako by mohly být mimo vaši komponentu.
Jednotná konstrukce platí pro každé volání, které obsluhuje továrna pod kapucí. Prakticky to znamená, že optimalizace obsluhuje konstruktory i statické členy. Tady je znovu původní příklad.
MyClass c;
c.Method();
MyClass::StaticMethod();
Bez -opt[imize], první a poslední příkazy vyžadují volání prostřednictvím objektu továrny.
S-opt[imize] to nedělá ani jeden z nich. A tato volání se kompilují přímo s implementací a dokonce mohou být vložena inline. To souvisí s dalším termínem, který se často používá v souvislosti s -opt[imize], konkrétně přímým přístupem k implementaci.
Jazykové projekce jsou pohodlné, ale pokud máte přímý přístup k implementaci, můžete a měli byste využít výhod, které vám umožní vytvořit nejúčinnější možný kód. C++/WinRT to může udělat za vás, aniž byste museli opustit bezpečnost a produktivitu projekce.
Jedná se o zásadní změnu, protože komponenta musí spolupracovat, aby projekce jazyka mohla proniknout dovnitř a přímo přistupovat k typům své implementace. Protože C++/WinRT je knihovna jen pro hlavičky, můžete se podívat dovnitř a zjistit, co se děje. Bez -opt[imize] jsou konstruktor třídy MyClass a člen StaticMethod definovány projekcí takto.
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(); });
}
}
Není nutné postupovat podle všech výše uvedených pokynů; záměrem je ukázat, že obě volání zahrnují volání funkce s názvem call_factory. To je známka toho, že tato volání pracují s mezipamětí factory a nepřistupují přímo k implementaci.
S-opt[imize] tímto tytéž funkce vůbec nejsou definovány. Místo toho jsou deklarovány projekcí a jejich definice zůstanou v komponentě.
Komponenta pak může poskytnout definice, které přímo odkazují na implementaci. Teď jsme přišli na zásadní změnu. Tyto definice se vygenerují za vás, když použijete obojí -component a -opt[imize]a zobrazí se v souboru s názvem Type.g.cpp, kde Type je název třídy runtime, která se implementuje. Proto můžete při prvním povolení -opt[imize] v existujícím projektu narazit na různá chybová hlášení linkeru. Aby se věci shodovaly, musíte do implementace zahrnout tento vygenerovaný soubor.
V našem příkladu by MyClass.h mohlo vypadat takto (bez ohledu na to, zda se -opt[imize] používá).
// 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>
{
};
}
Tvůj MyClass.cpp je místo, kde se vše spojuje.
#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()
{
}
}
Pokud tedy chcete použít jednotnou konstrukci v existujícím projektu, musíte upravit soubor každé implementace .cpp tak, abyste po #include <Sub/Namespace/Type.g.cpp> zahrnutí (a definici) třídy implementace. Tento soubor poskytuje definice těchto funkcí, které projekce opustila nedefinovaná. Tady je, jak tyto definice vypadají uvnitř MyClass.g.cpp souboru.
namespace winrt::MyProject
{
MyClass::MyClass() :
MyClass(make<MyProject::implementation::MyClass>())
{
}
void MyClass::StaticMethod()
{
return MyProject::implementation::MyClass::StaticMethod();
}
}
A tím se projekce elegantně dokončí pomocí efektivních volání přímo do implementace, bez volání do mezipaměti továrny, a zároveň vyhoví linkeru.
Poslední věcí, která -opt[imize] pro vás dělá, je změnit implementaci projektu module.g.cpp (soubor, který vám pomůže implementovat dll DllGetActivationFactory a DllCanUnloadNow exporty), tak, aby přírůstkové sestavení bylo mnohem rychlejší tím, že eliminuje vazby silného typu vyžadované C++/WinRT 1.0. To se často označuje jako továrny s vymazaným typem. Bez -opt[imize] soubor module.g.cpp, který se pro vaši komponentu vygeneruje, začíná zahrnutím definic všech vašich implementačních tříd – v tomto příkladu MyClass.h. Pak přímo vytvoří implementační továrnu pro každou třídu, jako je tato.
if (requal(name, L"MyProject.MyClass"))
{
return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}
Opět nemusíte sledovat všechny podrobnosti. Co je užitečné vidět, je, že to vyžaduje úplnou definici pro všechny třídy implementované vaší komponentou. To může mít dramatický dopad na váš inner loop, protože jakákoli změna v jediné implementaci způsobí, že se module.g.cpp znovu zkompiluje. S -opt[imize], to už není případ. Místo toho se vygenerovanému module.g.cpp souboru stanou dvě věci. První je, že již neobsahuje žádné třídy implementace. V tomto příkladu nebude obsahovat MyClass.h vůbec. Místo toho vytvoří implementační továrny bez jakýchkoli znalostí o jejich implementaci.
void* winrt_make_MyProject_MyClass();
if (requal(name, L"MyProject.MyClass"))
{
return winrt_make_MyProject_MyClass();
}
Samozřejmě není nutné zahrnovat jejich definice a je na linkeru, aby vyřešil definici funkce winrt_make_Component_Class. Samozřejmě, že o tom nemusíte přemýšlet, protože MyClass.g.cpp soubor, který se pro vás vygeneruje (a který jste dříve zahrnuli za účelem podpory jednotné konstrukce), definuje tuto funkci. Zde je celý MyClass.g.cpp soubor, který se pro tento příklad vygeneruje.
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();
}
}
Jak vidíte, funkce winrt_make_MyProject_MyClass přímo vytvoří továrnu vaší implementace. To vše znamená, že můžete bez obav měnit libovolnou implementaci a module.g.cpp není vůbec nutné znovu kompilovat. Pouze když přidáte nebo odeberete třídy rozhraní prostředí Windows Runtime, aktualizuje se module.g.cpp a je nutné ho znovu zkompilovat.
Přepsání virtuálních metod základní třídy
Odvozená třída může mít problémy s virtuálními metodami, pokud základní i odvozená třída jsou třídy definované aplikací, ale virtuální metoda je definována v grandparentní prostředí Windows Runtime třídě. V praxi k tomu dochází v případě, že odvozujete z tříd XAML. Zbytek této části pokračuje z příkladu v odvozených třídách.
namespace winrt::MyNamespace::implementation
{
struct BasePage : BasePageT<BasePage>
{
void OnNavigatedFrom(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
struct DerivedPage : DerivedPageT<DerivedPage>
{
void OnNavigatedFrom(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
}
Hierarchie je: Microsoft::UI::Xaml::Controls::Page<- BasePage<- DerivedPage. BasePage::OnNavigatedFrom metoda správně přepíše Page::OnNavigatedFrom, ale DerivedPage::OnNavigatedFrom nepřepíše BasePage::OnNavigatedFrom.
Zde DerivedPage znovu používá virtuální tabulku IPageOverrides z BasePage, což znamená, že nepřepisuje metodu IPageOverrides::OnNavigatedFrom. Jedno z možných řešení vyžaduje, aby BasePage sama o sobě byla šablonová třída a aby její implementace byla celá v hlavičkovém souboru, ale to věci nepřijatelně komplikuje.
Jako alternativní řešení deklarujte metodu OnNavigatedFrom jako explicitně virtuální v základní třídě. Takto, když položka vtable pro DerivedPage::IPageOverrides::OnNavigatedFrom volá BasePage::IPageOverrides::OnNavigatedFrom, generovaný kód zavolá BasePage::OnNavigatedFrom, což se kvůli virtuálnímu dispatchingu nakonec projeví jako volání DerivedPage::OnNavigatedFrom.
namespace winrt::MyNamespace::implementation
{
struct BasePage : BasePageT<BasePage>
{
// Note the `virtual` keyword here.
virtual void OnNavigatedFrom(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
struct DerivedPage : DerivedPageT<DerivedPage>
{
void OnNavigatedFrom(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
}
To vyžaduje, aby všichni členové hierarchie tříd souhlasí s návratovými hodnotami a typy parametrů OnNavigatedFrom metoda. Pokud nesouhlasí, měli byste jako virtuální metodu použít výše uvedenou verzi a zabalit alternativy.
Note
Vaše IDL nemusí deklarovat přepsanou metodu. Další podrobnosti najdete v tématu Implementace metod, které lze přepsat.
Důležitá rozhraní API
- winrt::com_ptr šablona struktury
- Funkce winrt::com_ptr::copy_from
- šablona funkce winrt::from_abi
- šablona funkce winrt::get_self
- winrt::implements šablona typu struct
- šablona funkce winrt::make
- Šablona funkce winrt::make_self
- funkce winrt::Windows::Foundation::IUnknown::as
- winrt::Windows::Foundation::IUnknown::try_as function
Příbuzná témata
Windows developer