Sdílet prostřednictvím


Vytváření rozhraní API pomocí C++/WinRT

Toto téma ukazuje, jak vytvářet rozhraní API C++/WinRT pomocí winrt::implements základní struktury buď přímo, nebo nepřímo. Synonyma pro autora v tomto kontextu jsou vytvářetnebo implementovat. Toto téma popisuje následující scénáře implementace rozhraní API pro typ C++/WinRT v tomto pořadí.

Poznámka:

Toto téma se týká tématu komponent prostředí Windows Runtime, ale pouze v kontextu C++/WinRT. Pokud hledáte obsah o součástech prostředí Windows Runtime, které pokrývají všechny jazyky prostředí Windows Runtime, podívejte se na součásti prostředí Windows Runtime.

  • Nejste autorem třídy Windows Runtime; chcete pouze implementovat jedno nebo více rozhraní Windows Runtime pro místní použití ve vaší aplikaci. Přímo odvozujete z winrt::implements v tomto případě a implementujete funkce.
  • Vy vytváříte třídu modulu 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 od 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 Použití API s C++/WinRT.

Pokud nevytváříte třídu runtime

Nejjednodušší scénář nastává, když váš typ implementuje rozhraní Windows Runtime a kdy tento typ budete využívat v téže aplikaci. 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 aplikaci založenou na CoreApplication.

Pokud na váš typ odkazuje uživatelské rozhraní XAML, musí být třída modulu runtime, i když je ve stejném projektu jako XAML. V takovém případě se podívejte na část Pokud vytvoříte třídu modulu runtime, na kterou se má odkazovat v uživatelském rozhraní XAML.

Poznámka:

Informace o instalaci a používání rozšíření C++/WinRT Visual Studio (VSIX) a balíčku NuGet (které společně poskytují podporu šablony projektu a sestavení), najdete v tématu podpora sady Visual Studio pro C++/WinRT.

V sadě Visual Studio znázorňuje šablona projektu Visual C++univerzální aplikace windowsCore (C++/WinRT) model CoreApplication. Vzor začíná předáním implementace Windows::ApplicationModel::Core::IFrameworkViewSource do CoreApplication::Run.

using namespace Windows::ApplicationModel::Core;
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    IFrameworkViewSource source = ...
    CoreApplication::Run(source);
}

CoreApplication používá rozhraní k vytvoření prvního zobrazení aplikace. Koncepčně IFrameworkViewSource vypadá takto.

struct IFrameworkViewSource : IInspectable
{
    IFrameworkView CreateView();
};

Znovu koncepčně platí, že implementace CoreApplication::Run to dělá.

void Run(IFrameworkViewSource viewSource) const
{
    IFrameworkView view = viewSource.CreateView();
    ...
}

Takže jako vývojář implementujete IFrameworkViewSource rozhraní. C++/WinRT má základní šablonu struktury winrt::implementuje, aby bylo snadné implementovat rozhraní (nebo několik) bez nutnosti programování ve stylu MODELU COM. Jednoduše odvozujete svůj typ z implementujícía pak implementujete funkce rozhraní. Tady je postup.

// App.cpp
...
struct App : implements<App, IFrameworkViewSource>
{
    IFrameworkView CreateView()
    {
        return ...
    }
}
...

To je zařízeno IFrameworkViewSource. Dalším krokem je vrácení objektu, který implementuje IFrameworkView rozhraní. Toto rozhraní můžete implementovat také na App. Tento další příklad kódu představuje minimální aplikaci, která alespoň zprovozní okno na ploše.

// 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() {}
};
...

Vzhledem k tomu, že typ aplikace aplikace jeIFrameworkViewSource, můžete Spustitpředat jen jeden .

using namespace Windows::ApplicationModel::Core;
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    CoreApplication::Run(winrt::make<App>());
}

Pokud píšete třídu modulu runtime v komponentě Windows Runtime

Pokud je váš typ zabalený v komponentě Windows Runtime pro využití jiným binárním souborem (druhý binární soubor je obvykle aplikace), pak váš typ musí být třída modulu pro běhové prostředí. Deklarujete běhovou třídu v souboru IDL (Microsoft Interface Definition Language) (.idl) (viz Factoring běhových tříd do souborů Midl (.idl)).

Každý soubor IDL vede k vytvoření souboru .winmd a Visual Studio sloučí všechny tyto soubory do jednoho souboru se stejným názvem jako váš kořenový prostor názvů. Poslední .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 IDL deklaruje třídu prostředí Windows Runtime (runtime). Třída runtime je typ, který lze aktivovat a využívat prostřednictvím moderních rozhraní COM, obvykle přes hranice mezi aplikacemi. Když do projektu přidáte soubor IDL a sestavíte ho, sada nástrojů C++/WinRT (midl.exe a cppwinrt.exe) vygeneruje za vás typ implementace. Příklad pracovního postupu souboru IDL v akci najdete v tématu ovládacích prvků XAML; bind to a C++/WinRT property.

Pomocí příkladu IDL výše je typ implementace zástupný kód C++ s názvem winrt::MyProject::implementation::MyRuntimeClass v souborech zdrojového kódu s názvem \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 použitý vzor polymorfismu vázaného na F (MyRuntimeClass se používá jako argument šablony k základu MyRuntimeClassT). To se také označuje jako podivně opakující se vzor šablony (CRTP). Pokud se řídíte řetězem dědičnosti směrem nahoru, setkáte se s MyRuntimeClass_base.

Implementaci jednoduchých vlastností můžete zjednodušit pomocí knihoven pro implementaci systému Windows (WIL). Tady je postup:

// MyRuntimeClass.h
...
namespace winrt::MyProject::implementation
{
    struct MyRuntimeClass : MyRuntimeClassT<MyRuntimeClass>
    {
        MyRuntimeClass() = default;

        wil::single_threaded_rw_property<winrt::hstring> Name;
    };
}

Viz Jednoduché vlastnosti.

template <typename D, typename... I>
struct MyRuntimeClass_base : implements<D, MyProject::IMyRuntimeClass, I...>

V tomto scénáři je tedy na vrcholu hierarchie dědičnosti winrt::implements základní šablona struktury.

Další podrobnosti, kód a návod k vytváření rozhraní API v komponentě Prostředí Windows Runtime najdete v tématu součásti prostředí Windows Runtime s komponentami C++/WinRT a Vytváření událostí v prostředí C++/WinRT.

Pokud vytvoříte třídu modulu runtime, na kterou se má odkazovat v uživatelském rozhraní XAML

Pokud na váš datový typ odkazuje uživatelské rozhraní XAML, musí to být běhová třída, 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 vytváříte a využíváte rozhraní API a. Postup implementace třídy runtime je v podstatě stejný jako pro komponentu prostředí Windows Runtime. Podívejte se tedy na předchozí část –Pokud vytváříte třídu za běhu ve Windows Runtime komponentě. 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 pouhé uvedení názvu "MyRuntimeClass" může být v tomto scénáři nejednoznačné; existuje několik entit s tímto názvem, různých druhů.

  • MyRuntimeClass je název třídy 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. To je typ implementace, nebo implementace. Tento typ je generován (nástrojem cppwinrt.exe) v souborech \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h a MyRuntimeClass.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 předpokládaný typ, nebo projekce. Tento typ je generován (cppwinrt.exe) v souboru \MyProject\MyProject\Generated Files\winrt\impl\MyProject.2.h.

typ projektu a typ implementace

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();
    };
}

Příklad průvodce implementací rozhraní INotifyPropertyChanged na runtime třídu najdete v tématu ovládací prvky XAML; navázání na vlastnost C++/WinRT.

Postup pro využívání třídy runtime v tomto scénáři je popsán v Využití rozhraní API s C++/WinRT.

Strukturování tříd za běhu do souborů Midl (.idl)

Šablony projektů a položek sady Visual Studio vytvářejí 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 jinak měli složité (nebo kruhové) import závislosti mezi nimi, může být skutečně nezbytné sloučení. A pokud jsou třídy modulu runtime společně, můžete je snadněji vytvářet a kontrolovat.

Konstruktory tříd modulu runtime

Tady jsou některé body, které si můžeme odnést z výpisů, které jsme viděli 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é prostřednictvím IDL či nikoli, je pro váš projektovaný typ generováno přetížení konstruktoru, které přebírá std::nullptr_t. Volání konstruktoru std::nullptr_t je první ze dvou kroků při využívání třídy modulu runtime z stejné kompilační jednotky. Další podrobnosti a příklad kódu najdete v tématu Použití rozhraní API pomocí C++/WinRT.
  • Pokud používáte třídu runtime z stejné kompilační jednotce, můžete také implementovat nenástavbové konstruktory přímo na typ implementace (což je nezapomeňte, že je v MyRuntimeClass.h).

Poznámka:

Pokud očekáváte, že vaše běhová třída bude spotřebována z jiné kompilační jednotky (což je běžné), zahrňte do svého IDL konstruktor (alespoň výchozí konstruktor). Tímto způsobem získáte také implementaci továrny společně s typem implementace.

Pokud chcete vytvořit a využívat třídu běhového prostředí pouze v rámci stejné jednotky kompilace, nedeclarujte v IDL žádné konstruktory. Nepotřebujete implementaci továrny a ta se negeneruje. 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 spočívá v použití IDL k deklarování třídy běhového prostředí a jejích členů, načež nástroje generují prototypy a zástupné implementace. Stejně jako u těch automaticky vygenerovaných prototypů pro členy třídy runtime můžete je 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, že to změníte na IInspectable ve vaší implementaci. To funguje, protože jakákoliv SomeClass je možné předávat na IInspectable (samozřejmě by to opačně nefungovalo).
  • Parametr, který lze kopírovat, můžete přijmout podle hodnoty, namísto odkazu. Například změňte SomeClass const& na SomeClass. To je nezbytné, když potřebujete zabránit zachycení odkazu v korutině (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é při psaní asynchronního obslužného prvku.

Vytváření instancí a návrat implementačních typů 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 runtime třídu).

#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

Ale můžete převést z MyType na IStringable nebo IClosable objekt, který můžete použít nebo vrátit jako součást projekce voláním funkční šablony winrt::make. vrací výchozí rozhraní typu implementace.

IStringable istringable = winrt::make<MyType>();

Poznámka:

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ě způsobí, že vrátí instanci projektovaného typu. Příklad kódu tohoto scénáře najdete v tématu ovládací prvky XAML: navázání 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) k dotazování na jiné předpokládané typy nebo rozhraní, které můžete použít nebo vrátit.

Návod

Scénář, ve kterém byste neměli použít jako nebo try_as je odvození třídy za běhu ("kompozitní třídy"). Pokud typ implementace vytvoří jinou třídu, nevolejte jako nebo try_as , aby bylo možné provést nezaškrtnutou nebo zaškrtnutou vlastnost QueryInterface třídy, která se skládá. Je lepší přistupovat k datovému členu (this->) m_inner a použít funkce as nebo try_as na něm. 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ý zabalí implementační typ. Můžete přistupovat ke členům všech jeho rozhraní (pomocí operátoru šipky). Můžete ho vrátit volajícímu as-is, nebo ho použít k volání jako a vrátit výsledný objekt rozhraní zpět volajícímu.

winrt::com_ptr<MyType> myimpl = winrt::make_self<MyType>();
myimpl->ToString();
myimpl->Close();
IClosable iclosable = myimpl.as<IClosable>();
iclosable.Close();

Třída MyType není součástí projekce; je to implementace. Tímto způsobem můžete metody jeho implementace volat přímo, bez nákladů spojených s voláním 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 proměnné myimpl a operátoru šipky.

V případě, že máte objekt rozhraní a víte, že se jedná o rozhraní pro vaši implementaci, můžete se k implementaci vrátit pomocí šablony funkce winrt::get_self. Opět se jedná o techniku, která zabraňuje volání virtuálních funkcí a umožňuje přístup přímo k implementaci.

Poznámka:

Pokud jste nenainstalovali sadu Windows SDK verze 10.0.17763.0 (Windows 10 verze 1809) nebo novější, musíte místo winrt::from_abi volat winrt::get_self.

Tady je příklad. Existuje další příklad v Implementovat BgLabelControl vlastní třídy ovládacího prvku.

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 to chcete ponechat, můžete zavolat na 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á žádné jako funkce. I tak, jak můžete vidět ve výše uvedené funkci ImplFromIClosable, můžete přistupovat k členům všech jeho rozhraní. Pokud to ale uděláte, nevracejte volajícímu instanci surového 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. Operátor převodu existuje u vašeho typu implementace (za předpokladu, že typ implementace byl generován nástrojem cppwinrt.exe), 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 funkce člena typu implementace můžete předat *this metodě, která očekává hodnotu 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á. Termín prostředí Windows Runtime 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 : Windows.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 tím, že ji specifikuje pomocí typu aliasu 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 vytvoří jinou třídu, nevolejte jako nebo try_as , aby bylo možné provést nezaškrtnutou nebo zaškrtnutou vlastnost QueryInterface třídy, která se skládá. Je lepší přistupovat k datovému členu (this->) m_inner a použít funkce as nebo try_as na něm.

Odvození z typu, který má jiný než výchozí konstruktor

ToggleButtonAutomationPeer::ToggleButtonAutomationPeer(ToggleButton) je příkladem konstruktoru, který není výchozí. Neexistuje žádný výchozí konstruktor, takže k vytvoření ToggleButtonAutomationPeermusíte předat vlastníka. V důsledku toho, pokud jste odvozeni z ToggleButtonAutomationPeer, pak musíte poskytnout konstruktor, který vezme vlastníka a předá ho do základu. Pojďme se podívat, jak to vypadá v praxi.

// 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);
    };
}

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 třeba předat do základní třídy. Pamatujete si vzor polymorfismu vázaného na F, který jsme zmínili výše? Jakmile znáte podrobnosti o daném vzoru, který používá C++/WinRT, můžete zjistit, jak se jmenuje vaše základní třída (nebo se můžete podívat jen do souboru hlavičky 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 úpravu popsanou výše (předání parametru konstruktoru základní třídě), kompilátor označí váš konstruktor jako chybný a upozorní na to, že pro typ zvaný (v tomto případě) MySpecializedToggleButtonAutomationPeer_base<MySpecializedToggleButtonAutomationPeer>není k dispozici žádný vhodný výchozí konstruktor. To je vlastně základní třída basové třídy vašeho typu implementace.

Jmenné prostory: projektované typy, typy implementace a faktory

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ů. Proto má název MyRuntimeClass jeden význam v oboru názvů winrt::MyProject a jiný význam v oboru názvů winrt::MyProject::implementation. 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; je to v podstatě inteligentní ukazatel na podkladový objekt, který může být implementován buď zde ve vašem projektu, nebo 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++. Neimplementujte přímo typ; místo toho zavolejte winrt::makea použijte váš typ implementace jako parametr šablony. Ukázali jsme příklady winrt::make v akci již dříve v tomto tématu a v ovládacích prvcích XAML je další příklad, který se váže k vlastnosti C++/WinRT. Viz také Diagnostika přímých přidělení.
  • winrt::MyProject::factory_implementation. Tento obor názvů obsahuje továrny. Objekt v tomto oboru názvů podporuje IActivationFactory.

Tato tabulka ukazuje minimální kvalifikaci jmenného prostoru, kterou potřebujete použít v různých kontextech.

Obor názvů, který je v 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 vrátit projektovaný typ z implementace, dávejte pozor, abyste nemuseli vytvořit instanci typu implementace tím, ž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 Vytvoření instance a vrácení typů implementace a rozhraní.

Problém s MyRuntimeClass myRuntimeClass; v 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 zničí podle normálních pravidel jazyka C++, když se obor ukončí. Pokud jste tedy vrátili pro tento objekt projektovaný typ (inteligentní ukazatel), znamená to, že tento ukazatel je nyní neplatný.

Tento typ poškození paměti je obtížné diagnostikovat. U ladicích sestavení vám tedy aserce C++/WinRT pomůže zachytit tuto chybu pomocí detektoru zásobníku. Ale korutiny jsou přiděleny na haldě, takže pokud uděláte chybu uvnitř korutiny, nedostanete pomoc s její nápravou. Další informace najdete v části 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í).

Vlastnost Přijímá Poznámky
T (představující inteligentní ukazatel) Předpokládaný Podívejte se na upozornění v Obory názvů: projekční typy, implementační typy a továrny týkající se omylného použití implementačního typu.
agile_ref<T> Oboje Pokud používáte typ implementace, pak musí být argument konstruktoru com_ptr<T>.
com_ptr<T> Implementace Při použití projektovaného typu se vygeneruje chyba: 'Release' is not a member of 'T'.
default_interface<T> Oboje Pokud použijete typ implementace, vrátí se první implementované rozhraní.
get_self<T> Implementace Při použití projektovaného typu se vygeneruje chyba: '_abi_TrustLevel': is not a member of 'T'.
guid_of<T>() Oboje Vrátí identifikátor GUID výchozího rozhraní.
IWinRTTemplateInterface<T>
Předpokládaný Použití typu implementace se zkompiluje, ale je to chyba – podívejte se na upozornění v Obory názvů: odhadované typy, implementační typy a továrny.
make<T> Implementace Použití projektovaného typu vygeneruje chybu: 'implements_type': is not a member of any direct or indirect base class of 'T'
make_agile(T const&amp;) Oboje Pokud použijete typ implementace, musí být argument com_ptr<T>.
make_self<T> Implementace Použití projektovaného typu vygeneruje chybu: '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> Oboje Pokud používáte typ implementace, pak musí být argument konstruktoru com_ptr<T>.

Zapojit se do jednotné výstavby a přímého přístupu k realizaci

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í nástroje cppwinrt.exe. Ve Visual Studio nastavte vlastnost projektu Společné vlastnosti>C++/WinRT>Optimalizováno na Ano. To přidá <CppWinRTOptimized>true</CppWinRTOptimized> do vašeho projektového souboru. 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] aktivuje často nazývanou jednotnou konstrukci. Díky jednotné (nebo sjednocené) konstrukci využijete projekci jazyka C++/WinRT sama o sobě k efektivnímu vytváření a používání implementačních typů (typů implementovaných vaší komponentou, pro použití v aplikacích) a při tom bez potíží se zavaděčem.

Než funkci popíšeme, ukážeme si nejprve případ bez jednotné konstrukce. Pro ilustraci začneme s touto ukázkovou třídou prostředí Windows Runtime.

// 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. C++/WinRT jako jazyková projekce chrání vývojáře před ABÍ (aplikace binární rozhraní založené na modelu COM, které je definováno prostředím Windows Runtime). C++/WinRT nevolá přímo do implementace; místo toho prochází prostřednictvím ABI.

V důsledku toho na řádku kódu, kde vytváříte objekt MyClass (MyClass c;), volání projekce C++/WinRT RoGetActivationFactory načtení třídy nebo aktivační továrny a pak tuto továrnu použije k vytvoření objektu. Poslední řádek podobně využívá továrnu k provedení volání statické metody. To vše vyžaduje, aby vaše třída byla zaregistrována a váš modul implementoval vstupní bod DllGetActivationFactory. C++/WinRT má velmi rychlou mezipaměť továrny, takže to nezpůsobuje žádný 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é.

Především, bez ohledu na to, jak rychlá je tovární mezipaměť C++/WinRT, volání prostřednictvím RoGetActivationFactory (nebo dokonce i následná volání prostřednictvím tovární mezipaměti) bude vždy pomalejší než přímé volání implementace. Volání RoGetActivationFactory, následované IActivationFactory::ActivateInstance a poté QueryInterface, určitě nebude tak efektivní jako použití výrazu new jazyka C++ 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 k vytvoření třídy znamená, že její aktivační továrna bude uložena do mezipaměti. To je obvykle to, co chcete, ale pokud se továrna nachází ve stejném modulu (DLL), který provádí volání, pak jste efektivně připnuli knihovnu DLL a zabránili jejímu trvalému uvolnění. V mnoha případech nezáleží na tom; ale některé systémové komponenty musí podporovat uvolňování.

Právě zde vstupuje do hry 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ý třídu skutečně implementuje, můžete k vytvoření objektu volně použít stejnou syntaxi.

// MyProject::MyClass c{ winrt::make<implementation::MyClass>() };
MyClass c;

Když sestavíte projekt komponent pomocí přepínače -opt[imize], volání prostřednictvím projekce jazyka se zkompiluje do stejného efektivního volání winrt::make funkce, která přímo vytvoří typ implementace. To činí vaši syntaxi jednoduchou a předvídatelnou, vyhýbá se případným problémům s výkonem při volání přes továrnu a zabraňuje zafixování komponenty v procesu. Kromě projektů komponent je to také užitečné pro aplikace XAML. Obcházení RoGetActivationFactory pro třídy implementované ve stejné aplikaci vám umožňuje vytvořit je bez nutnosti registrace stejnými způsoby, jako byste mohli, kdyby byly mimo vaši komponentu.

Jednotná konstrukce se vztahuje na jakékoli volání, které obsluhuje továrna v zákulisí. 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], ani jeden z nich to neudělá. Tato volání se kompilují přímo vůči implementaci a dokonce mohou být vložena do kódu. Jedná se o další termín, který se často používá, když se hovoří o -opt[imize], konkrétně o přímém implementačním přístupu.

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 přímo přistupovat k jejím typům 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 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 vaše nápověda, že tyto hovory zahrnují cache továrny a nepřistupují přímo k implementaci. S-opt[imize]nejsou vůbec definovány tyto stejné funkce. Místo toho jsou deklarovány projekcí a jejich definice zůstanou v komponentě.

Komponenta pak může poskytnout definice, které volají přímo do implementace. Teď jsme přišli na zásadní změnu. Tyto definice se pro vás vygenerují, když použijete -component i -opt[imize]a zobrazí se v souboru s názvem Type.g.cpp, kde Typ je název implementované třídy modulu runtime. Proto se mohou při prvním povolení -opt[imize] v existujícím projektu vyskytnout různé chyby linkeru. Aby se věci shodovaly, musíte do implementace zahrnout tento vygenerovaný soubor.

V našem příkladu může MyClass.h vypadat takto (bez ohledu na to, jestli se používá -opt[imize]).

// 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>
    {
    };
}

Vaše MyClass.cpp je místo, kde se všechno 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 uniformní konstrukci v existujícím projektu, musíte upravit soubor .cpp každé implementace tak, abyste #include <Sub/Namespace/Type.g.cpp> po zahrnutí (a definici) třídy implementace. Tento soubor poskytuje definice těchto funkcí, které projekce opustila nedefinovaná. Tady je, jak tyto definice vypadají uvnitř souboru MyClass.g.cpp.

namespace winrt::MyProject
{
    MyClass::MyClass() :
        MyClass(make<MyProject::implementation::MyClass>())
    {
    }
    void MyClass::StaticMethod()
    {
        return MyProject::implementation::MyClass::StaticMethod();
    }
}

A to pěkně dokončí projekci efektivními voláními přímo do implementace, čímž se vyhne volání mezipaměti továrny a splní požadavky linkeru.

Poslední věcí, kterou -opt[imize] pro vás dělá, je změnit implementaci module.g.cpp projektu (soubor, pomocí kterého implementujete exporty knihovny DLL DllGetActivationFactory a DllCanUnloadNow) tak, aby přírůstkové sestavení byla mnohem rychlejší tím, že eliminují silné typové vazby, které byly vyžadovány C++/WinRT 1.0. To se často označuje jako typově vymazané továrny. Bez -opt[imize]začne module.g.cpp soubor generovaný pro vaši komponentu zahrnutím definic všech tříd implementace – MyClass.hv tomto příkladu. 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ý vliv na vaši vnitřní smyčku, protože jakákoli změna libovolné implementace způsobí, že module.g.cpp bude znovu zkompilován. U -opt[imize]už to neplatí. Místo toho se vygenerovanému souboru module.g.cpp stane dvě věci. První je, že již neobsahuje žádné třídy implementace. V tomto příkladu nebude obsahovat vůbec MyClass.h. 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();
}

Není samozřejmě potřeba zahrnout jejich definice, a je na definici funkce winrt_make_Component_Class, aby ji vyřešil linker. 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. Tady je celý soubor MyClass.g.cpp vygenerovaný pro tento příklad.

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 pro vaši implementaci. To vše znamená, že můžete šťastně změnit jakoukoli danou implementaci a module.g.cpp se vůbec nemusí rekompilovat. Je to jen v případě, že přidáte nebo odeberete třídy prostředí Windows Runtime, kdy bude module.g.cpp aktualizováno a je potřeba, aby bylo znovu zkompilováno.

Přepsání virtuálních metod základní třídy

Vaše 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í třídě prostředí Windows Runtime. V praxi k tomu dochází v případě, že odvozujete z tříd XAML. Zbytek této části navazuje na příklad v Odvozené třídy.

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);
    };
}

Hierarchie je Windows::UI::Xaml::Controls::Page<- BasePage<- DerivedPage. Metoda BasePage::OnNavigatedFrom správně přepíše Page::OnNavigatedFrom, ale DerivedPage::OnNavigatedFrom nepřepíše BasePage::OnNavigatedFrom.

Tady DerivedPage znovu používá vtable IPageOverrides z BasePage, což znamená, že metoda IPageOverrides::OnNavigatedFrom není přepsána. Jedním z potenciálních řešení je, aby BasePage byla samotná šablonová třída a měla svou implementaci zcela v hlavičkovém souboru, ale to dělá věci nepřijatelně složité.

Jako alternativní řešení deklarujte metodu OnNavigatedFrom jako explicitně virtuální v základní třídě. Tímto způsobem, když položka vtable pro DerivedPage::IPageOverrides::OnNavigatedFrom volá BasePage::IPageOverrides::OnNavigatedFrom, producent volá BasePage::OnNavigatedFrom, což (z důvodu jeho virtuální), skončí volání 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);
    };
}

To vyžaduje, aby všichni členové hierarchie tříd souhlasili s návratovými hodnotami a typy parametrů metody OnNavigatedFrom. Pokud nesouhlasí, měli byste jako virtuální metodu použít výše uvedenou verzi a zabalit alternativy.

Poznámka:

Váš IDL nemusí deklarovat přepsanou metodu. Další podrobnosti najdete v tématu Implementace přepisovatelných metod.

Důležitá rozhraní API