API-k létrehozása C++/WinRT használatával

Ez a témakör bemutatja, hogyan írhat C++/WinRT API-kat a winrt::implements alapszerkezet közvetlen vagy közvetett használatával. Ebben a kontextusban a létrehoz szinonimái a előállít vagy az implementál. Ez a témakör a következő forgatókönyveket ismerteti az API-k C++/WinRT típusú implementálásához ebben a sorrendben.

Note

Ez a témakör Windows-futtatókörnyezet összetevők témájával foglalkozik, de csak a C++/WinRT kontextusában. Ha olyan tartalmat keres Windows-futtatókörnyezet összetevőkről, amelyek az összes Windows-futtatókörnyezet nyelvet lefedik, tekintse meg Windows-futtatókörnyezet összetevőket.

  • Nem Windows-futtatókörnyezet osztályt (futtatókörnyezeti osztályt) hoz létre; csak egy vagy több Windows-futtatókörnyezet felületet szeretne implementálni helyi felhasználás céljából az alkalmazásban. Ebben az esetben közvetlenül a winrt::implements osztályból származtat, és függvényeket valósít meg.
  • Ön egy futásidejű osztályt hoz létre. Előfordulhat, hogy egy alkalmazásból felhasználandó összetevőt hoz létre. Vagy egy XAML felhasználói felületről (UI) felhasználandó típust ír, és ebben az esetben egy futtatókörnyezeti osztályt is implementál és használ ugyanabban a fordítási egységben. Ezekben az esetekben lehetővé teszi, hogy az eszközök olyan osztályokat hozzanak létre, amelyek a winrt:implementálásból származnak.

Mindkét esetben a C++/WinRT API-kat implementáló típust implementálási típusnak nevezzük.

Fontos

Fontos megkülönböztetni a megvalósítási típus fogalmát a tervezett típustól. A tervezett típust a C++/WinRT api-k használata című témakörben ismertetjük.

Ha nem futtatókörnyezeti osztályt hoz létre

A legegyszerűbb forgatókönyv az, amikor a típus implementál egy Windows-futtatókörnyezet felületet, és ezt a típust fogja használni ugyanabban az alkalmazásban. Ebben az esetben a típusnak nem kell futtatókörnyezeti osztálynak lennie; csak egy átlagos C++ osztály. Például előfordulhat, hogy egy Microsoft::UI::Xaml::Application köré épülő WinUI 3 asztali alkalmazást ír.

Ha a típusra az XAML felhasználói felülete hivatkozik, akkor futtatókörnyezeti osztálynak kell lennie, annak ellenére, hogy ugyanabban a projektben van , mint az XAML. Ebben az esetben tekintse meg az XAML felhasználói felületén hivatkozni kívánt futtatókörnyezeti osztály létrehozásakor című szakaszt.

Note

A C++/WinRT Visual Studio bővítmény (VSIX) és a NuGet-csomag telepítésével és használatával kapcsolatos információkért (amelyek együttesen nyújtanak projektsablont és buildtámogatást) lásd Visual Studio C++/WinRT támogatását.

Az Visual Studio A C++-hoz készült Blank App, Packaged (WinUI 3 in Desktop) projektsablon a WinUI 3 alkalmazásmintát szemlélteti. Az alkalmazásosztálya Microsoft::UI::Xaml::Application függvényből származik, és a belépési pont meghívja a Start metódust.

#include "App.xaml.h"

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    winrt::init_apartment();
    ::winrt::Microsoft::UI::Xaml::Application::Start(
        [](auto&&) { ::winrt::make<App>(); });
}

Az alkalmazásosztály létrehoz egy Microsoft::UI::Xaml::Window fájlt, és aktiválja az OnLaunchedben.

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

A C++/WinRT alapstruktúrasablon winrt::implementálásával egyszerűen implementálható egy felület (vagy több) com-stílusú programozás nélkül. A típust egyszerűen a(z) implements-ből származtatja, majd megvalósítja az interfész függvényeit. Az alábbi példa egy egyéni felületet implementál.

struct MyType : implements<MyType, IStringable>
{
    hstring ToString()
    {
        return L"MyType";
    }
};

Ha futtatásidejű osztályt ír egy Windows-futtatókörnyezet-összetevőben

Ha a típus egy Windows-futtatókörnyezet összetevőbe van csomagolva egy másik binárisból való felhasználás céljából (a másik bináris általában egy alkalmazás), akkor a típusnak futtatókörnyezeti osztálynak kell lennie. Futtatókörnyezeti osztályt deklarál egy Microsoft Interface Definition Language (IDL) (.idl) fájlban (lásd: Factoring runtime classes into Midl files (.idl)).

Minden IDL-fájl egy .winmd fájlt eredményez, és Visual Studio az összeset egyetlen fájlba egyesíti, ugyanazzal a névvel, mint a gyökérnévtér. A végső .winmd fájl lesz az, amelyet az összetevő felhasználói hivatkozni fognak.

Íme egy példa egy futtatókörnyezeti osztály deklarálására egy IDL-fájlban.

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

Ez az IDL egy Windows-futtatókörnyezet-osztályt deklarál. A futtatókörnyezeti osztály olyan típus, amely modern COM-interfészeken keresztül aktiválható és használható, jellemzően végrehajtható fájlok közötti határokon át. Amikor egy IDL-fájlt ad hozzá a projekthez, és felépíti, a C++/WinRT eszközlánc (midl.exe és cppwinrt.exe) létrehoz egy implementációs típust. Példa az IDL-fájl munkafolyamatára működés közben: XAML-vezérlők; kötés egy C++/WinRT tulajdonsághoz.

A fenti példa IDL-jét használva a megvalósítás típusa egy winrt::MyProject::implementáció::MyRuntimeClass nevű C++ struct stub a forráskódfájlokban \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h és MyRuntimeClass.cpp.

A megvalósítás típusa így néz ki.

// 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.

Figyelje meg, hogy a használt F-korlátos polimorfizmus mintájában a MyRuntimeClass önmagát használja sablonparaméterként az ősosztálya, a MyRuntimeClassT számára. Ezt a szokatlanul ismétlődő sablonmintának (CRTP) is nevezik. Ha felfelé követi az öröklési láncot, eljut a MyRuntimeClass_base osztályhoz.

Egyszerűbbé teheti az egyszerű tulajdonságok implementálását Windows implementációs kódtárak (WIL) használatával. Ennek módja az alábbi:

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

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

Lásd : Egyszerű tulajdonságok.

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

Ebben a forgatókönyvben tehát az öröklési hierarchia gyökerénél található a winrt::implementálja ismét az alapstruktúrasablont .

További részletekért, kódpéldákért, valamint a Windows-futtatókörnyezet-összetevőkben történő API-létrehozás ismertetéséért lásd: Windows-futtatókörnyezet-összetevők C++/WinRT használatával és Események létrehozása C++/WinRT-ben.

Ha az XAML felhasználói felületén hivatkozni kívánt futtatókörnyezeti osztályt hoz létre

Ha a típusra az XAML felhasználói felülete hivatkozik, akkor futtatókörnyezeti osztálynak kell lennie, annak ellenére, hogy ugyanabban a projektben van, mint az XAML. Bár általában végrehajtható határokon keresztül aktiválódnak, a futtatókörnyezeti osztály ehelyett az azt megvalósító fordítási egységen belül használható.

Ebben a forgatókönyvben te hozod létre és használod is az API-kat. A futtatókörnyezeti osztály implementálásának eljárása lényegében ugyanaz, mint egy Windows-futtatókörnyezet összetevő esetében. Lásd az előző szakaszt– Ha futtatókörnyezeti osztályt hoz létre egy Windows-futtatókörnyezet összetevőben. Az egyetlen különbség az, hogy az IDL-től a C++/WinRT eszközlánc nem csak implementálási típust, hanem előrejelzett típust is létrehoz. Fontos értékelni, hogy ebben a forgatókönyvben csak a "MyRuntimeClass" kifejezés nem egyértelmű; több ilyen nevű entitás létezik, különböző típusúak.

  • A MyRuntimeClass egy futtatókörnyezeti osztály neve. De ez valójában absztrakció: idL-ben deklarálva, és valamilyen programozási nyelven implementálva.
  • MyRuntimeClass a C++-os winrt::MyProject::implementation::MyRuntimeClass struktúra neve, amely a futásidejű osztály C++/WinRT implementációja. Ahogy láttuk, ha külön implementálási és felhasználású projektek vannak, akkor ez a szerkezet csak a megvalósító projektben létezik. Ez a megvalósítás típusa vagy implementációja. Ezt a típust a(z) cppwinrt.exe eszköz generálja a(z) \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h és MyRuntimeClass.cpp fájlokban.
  • A MyRuntimeClass a tervezett típus neve a C++ struct winrt::MyProject::MyRuntimeClass formában. Ha külön implementálási és felhasználású projektek vannak, akkor ez a szerkezet csak a fogyasztó projektben létezik. Ez a kivetített típus vagy a kivetítés. Ez a típus (by cppwinrt.exe) jön létre a fájlban \MyProject\MyProject\Generated Files\winrt\impl\MyProject.2.h.

Tervezett típus és implementáció típusa

Az alábbiakban a projekttípusnak a jelen témakörhöz kapcsolódó részeit találja.

// MyProject.2.h
...
namespace winrt::MyProject
{
    struct MyRuntimeClass : MyProject::IMyRuntimeClass
    {
        MyRuntimeClass(std::nullptr_t) noexcept {}
        MyRuntimeClass();
    };
}

Az INotifyPropertyChanged interfész futásidejű osztályon való implementálásának példáiért lásd: XAML-vezérlők; kötés egy C++/WinRT tulajdonsághoz.

Ebben a forgatókönyvben a futtatókörnyezeti osztály fogyasztásának eljárását a C++/WinRT api-k használata című cikk ismerteti.

Futtatókörnyezeti osztályok faktorálása Midl-fájlokba (.idl)

A Visual Studio projekt- és elemsablonok minden futtatókörnyezeti osztályhoz külön IDL-fájlt hoznak létre. Ez logikai megfeleltetéseket biztosít egy IDL-fájl és a létrehozott forráskódfájlok között.

Ha azonban a projekt összes futtatókörnyezeti osztályát egyetlen IDL-fájlba összesíti, az jelentősen javíthatja a buildelési időt. Ha egyébként összetett (vagy körkörös) import függőségek vannak közöttük, akkor a konszolidálás valóban szükséges lehet. És lehet, hogy könnyebb lesz megírnia és átnéznie a futásidejű osztályokat, ha egy helyen vannak.

Futásidejű osztály konstruktorai

Íme néhány pont, amelyet el lehet venni a fent látható listaelemektől.

  • Az IDL-ben deklarált konstruktorok mindegyike konstruktort hoz létre a megvalósítási típuson és a tervezett típuson is. Az IDL-deklarált konstruktorok egy másik fordítási egység futtatókörnyezeti osztályának felhasználására szolgálnak.
  • Függetlenül attól, hogy IDL-deklarált konstruktor(ok) vannak-e vagy sem, a konstruktor túlterhelése std::nullptr_t jön létre a tervezett típuson. Az std::nullptr_t konstruktor meghívása a futtatókörnyezeti osztály ugyanazon összeállítási egységből való felhasználásának két lépése közül az első. További részletekért és egy kódpéldáért lásd: API-k felhasználása C++/WinRT használatával.
  • Ha ugyanabból a kompilációs egységből használja a futásidejű osztályt, akkor a nem alapértelmezett konstruktorokat közvetlenül a megvalósítási típuson is definiálhatja (amely, ne feledje, a(z) MyRuntimeClass.h elemben van).

Note

Ha arra számít, hogy a runtime osztályt más fordítási egységből fogják használni (ami gyakori), akkor vegye fel a konstruktort/konstruktorokat az IDL-ben (legalább egy alapértelmezett konstruktort). Így az implementációs típusod mellé egy gyári implementációt is kapni fogsz.

Ha csak ugyanabban a fordítási egységben szeretné létrehozni és felhasználni a futtatókörnyezeti osztályt, akkor ne deklaráljon konstruktorokat az IDL-ben. Nincs szükség gyári implementációra, és a rendszer nem fog létrehozni egyet. A rendszer törli a megvalósítási típus alapértelmezett konstruktorát, de egyszerűen szerkesztheti és alapértelmezés szerint beállíthatja.

Ha csak ugyanabban a fordítási egységben szeretné létrehozni és felhasználni a futtatókörnyezeti osztályt, és konstruktorparaméterekre van szüksége, akkor közvetlenül a megvalósítási típuson hozza létre a szükséges konstruktor(ok)t.

Futtatókörnyezeti osztály metódusai, tulajdonságai és eseményei

Láttuk, hogy a munkafolyamat az IDL használatával deklarálja a futtatókörnyezeti osztályt és annak tagjait, majd az eszközök prototípusokat és csonk-implementációkat hoznak létre Önnek. Ami a runtime-osztály tagjai számára automatikusan létrehozott prototípusokat illeti, módosíthatja őket úgy, hogy az IDL-ben deklaráltaktól eltérő típusokat adjanak át. Ezt azonban csak addig teheti meg, amíg az IDL-ben deklarált típus a implementált verzióban deklarált típusra továbbítható.

Íme néhány példa.

  • A paramétertípusok lazíthatók. Ha például az IDL-ben a metódus egy SomeClass-ot használ, akkor a implementációban módosíthatja azt IInspectable-ra . Ez azért működik, mert bármely SomeClass továbbítható az IInspectable-ba (ennek fordítottja természetesen nem működik).
  • A másolható paramétert a hivatkozás helyett érték alapján is elfogadhatja. Például váltson a SomeClass const& következőre SomeClass: . Erre akkor van szükség, ha el kell kerülnie a hivatkozás koroutinba való rögzítését (lásd : Paraméterátadás).
  • Enyhítheti a visszatérési értékre vonatkozó megkötéseket. Például módosíthatja a void típust erre: winrt::fire_and_forget.

Az utolsó kettő nagyon hasznos, ha aszinkron eseménykezelőt ír.

Implementációs típusok és felületek példányosítása és visszaküldése

Ebben a szakaszban vegyünk példaként egy MyType nevű implementációtípust, amely implementálja az IStringable és az IClosable interfészeket.

A MyType közvetlenül a winrt::implements függvényből származtatható (ez nem futtatókörnyezeti osztály).

#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;

struct MyType : implements<MyType, IStringable, IClosable>
{
    winrt::hstring ToString(){ ... }
    void Close(){}
};

Vagy létrehozhatja az IDL-ből (ez egy futtatókörnyezeti osztály).

// MyType.idl
namespace MyProject
{
    runtimeclass MyType: Windows.Foundation.IStringable, Windows.Foundation.IClosable
    {
        MyType();
    }
}

A megvalósítási típus nem foglalható le közvetlenül.

MyType myimpl; // error C2259: 'MyType': cannot instantiate abstract class

A MyType-ból azonban egy olyan IStringable vagy IClosable objektumba léphet, amelyet használhat vagy visszatérhet a vetítés részeként a winrt::make függvénysablon meghívásával. make visszaadja a megvalósítási típus alapértelmezett interfészét.

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

Note

Ha azonban az XAML felhasználói felületéről hivatkozik a típusra, akkor egy implementációtípus és egy előrejelzett típus is lesz ugyanabban a projektben. Ebben az esetben a make a kivetített típus egy példányát adja vissza. Erre a forgatókönyvre példakód: XAML-vezérlők; kötés egy C++/WinRT tulajdonsághoz.

A istringable csak az IStringable interfész tagjainak meghívására használhatjuk (a fenti kódpéldában). A C++/WinRT felület (amely egy előre jelzett felület) azonban a winrt::Windows::Foundation::IUnknown felületből származik. Így meghívhatja az IUnknown::as (vagy IUnknown::try_as) függvényt, hogy más előre jelzett típusokat vagy interfészeket kérdezz le, amelyeket használhat vagy vissza is adhat.

Tipp

Az az eset, amikor nem szabad hívni az as vagy a try_as függvényt, a futásidejű osztályszármaztatás („composable classes”). Ha egy implementációs típus egy másik osztályt foglal magában, ne hívja meg a as vagy try_as függvényt azért, hogy a magában foglalt osztályon nem ellenőrzött vagy ellenőrzött QueryInterface műveletet hajtson végre. Ehelyett érje el a (this->) m_inner adatagot, és azon hívja meg a as vagy try_as metódust. További információt a témakör Futásidejű osztályszármaztatás című részében talál.

istringable.ToString();
IClosable iclosable = istringable.as<IClosable>();
iclosable.Close();

Ha hozzá kell férnie az implementáció összes tagjához, majd később vissza kell adnia egy felületet egy hívónak, használja a winrt::make_self függvénysablont. A(z) make_self a megvalósítási típust becsomagoló winrt::com_ptr objektumot adja vissza. Hozzáférhet az összes interfészének tagjaihoz (a nyíloperátor használatával), változatlan formában visszaadhatja a hívónak, vagy meghívhatja rajta a as-t, és az eredményül kapott interfészobjektumot adhatja vissza a hívónak.

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

A MyType osztály nem része a kivetítésnek; ez a megvalósítás. Így azonban közvetlenül hívhatja meg a megvalósítási metódusokat, a virtuális függvényhívások terhelése nélkül. A fenti példában annak ellenére, hogy a MyType::ToString ugyanazt az aláírást használja, mint az IStringable előrejelzett metódusa, közvetlenül hívjuk meg a nem virtuális metódust anélkül, hogy átlépnénk az alkalmazás bináris felületét (ABI). A com_ptr egyszerűen a MyType szerkezetre mutató mutatót tartalmaz, így a változón és a nyíl operátoron keresztül is hozzáférhet a myimpl egyéb belső részleteihez.

Abban az esetben, ha van egy interfészobjektuma, és történetesen tudja, hogy az az Ön implementációjához tartozó interfész, akkor a winrt::get_self függvénysablon használatával visszajuthat az implementációhoz. Ez egy olyan technika, amely elkerüli a virtuális függvényhívásokat, és lehetővé teszi, hogy közvetlenül a megvalósításhoz jusson.

Note

Ha még nem telepítette a Windows SDK 10.0.17763.0-s verzióját (Windows 10, 1809-es vagy újabb verzió), akkor winrt::from_abi kell meghívnia a winrt::get_self helyett.

Íme egy példa. Van egy másik példa a BgLabelControl egyéni vezérlőosztály implementálására.

void ImplFromIClosable(IClosable const& from)
{
    MyType* myimpl = winrt::get_self<MyType>(from);
    myimpl->ToString();
    myimpl->Close();
}

De csak az eredeti illesztőobjektum tartalmaz egy hivatkozást. Ha Ön meg szeretné tartani, akkor meghívhatja a com_ptr::copy_from függvényt.

winrt::com_ptr<MyType> impl;
impl.copy_from(winrt::get_self<MyType>(from));
// com_ptr::copy_from ensures that AddRef is called.

A megvalósítás típusa önmagában nem a winrt::Windows::Foundation::IUnknown típusból származik, ezért nincs as függvénye. Ennek ellenére, ahogy a fenti ImplFromIClosable függvényben látható, az összes felületének tagjaihoz hozzáférhet. Ha azonban ezt teszi, akkor ne adja vissza a nyers implementációs típuspéldányt a hívónak. Ehelyett használja a már bemutatott technikák egyikét, és adjon vissza egy előre jelzett felületet vagy egy com_ptr.

Ha rendelkezik a megvalósítási típus egy példányával, és egy olyan függvénynek kell átadnia, amely a megfelelő előrejelzett típust várja, akkor ezt megteheti az alábbi kód példában látható módon. A megvalósítási típuson létezik konverziós operátor (feltéve, hogy az eszköz hozta létre a cppwinrt.exe megvalósítási típust), amely lehetővé teszi ezt. Egy implementációtípus értékét közvetlenül egy olyan metódusnak adhatja át, amely a megfelelő előrejelzett típus értékét várja. Egy implementációtípus tagfüggvényéből átadhatja a *this értéket egy olyan metódusnak, amely a megfelelő leképezett típus értékét várja.

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

Futtatókörnyezeti osztály származtatása

Létrehozhat egy futásidejű osztályt, amely egy másik futásidejű osztályból származik, feltéve, hogy az alaposztály „unsealed”-ként van deklarálva. Az osztály származtatásának Windows-futtatókörnyezet kifejezése "összeállítható osztályok". A származtatott osztály implementálásának kódja attól függ, hogy az alaposztályt egy másik összetevő vagy ugyanaz az összetevő biztosítja-e. Szerencsére nem kell elsajátítania ezeket a szabályokat – egyszerűen átmásolhatja a minta implementációkat a sourcescppwinrt.exe fordító által létrehozott kimeneti mappából.

Vegyük ezt a példát.

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

A fenti példában a MyButton az XAML gomb vezérlőelemből származik, amelyet egy másik összetevő biztosít. Ebben az esetben a megvalósítás ugyanúgy néz ki, mint egy nem írható osztály implementálása:

namespace winrt::MyProject::implementation
{
    struct MyButton : MyButtonT<MyButton>
    {
    };
}

namespace winrt::MyProject::factory_implementation
{
    struct MyButton : MyButtonT<MyButton, implementation::MyButton>
    {
    };
}

Másrészt a fenti példában a MyDerived egy másik osztályból származik ugyanabban az összetevőben. Ebben az esetben a megvalósításhoz egy további sablonparaméterre van szükség, amely megadja az alaposztály implementációs osztályát.

namespace winrt::MyProject::implementation
{
    struct MyDerived : MyDerivedT<MyDerived, implementation::MyBase>
    {                                     // ^^^^^^^^^^^^^^^^^^^^^^
    };
}

namespace winrt::MyProject::factory_implementation
{
    struct MyDerived : MyDerivedT<MyDerived, implementation::MyDerived>
    {
    };
}

Mindkét esetben az implementáció meghívhat egy metódust az ősosztályból, ha azt a base_type típusaliasszal minősíti:

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

Tipp

Ha egy implementációs típus egy másik osztályt foglal magában, ne hívja meg a as vagy try_as függvényt azért, hogy a magában foglalt osztályon nem ellenőrzött vagy ellenőrzött QueryInterface műveletet hajtson végre. Ehelyett érje el a (this->) m_inner adatagot, és azon hívja meg a as vagy try_as metódust.

Nem alapértelmezett konstruktort tartalmazó típusból származik

ToggleButtonAutomationPeer::ToggleButtonAutomationPeer(ToggleButton) egy nem alapértelmezett konstruktor példája. Nincs alapértelmezett konstruktor, ezért egy ToggleButtonAutomationPeer létrehozásához meg kell adni egy owner paramétert. Ennek megfelelően, ha a ToggleButtonAutomationPeer osztályból származtat, akkor meg kell adnia egy konstruktort, amely egy owner paramétert fogad el, és azt átadja az ősosztálynak. Nézzük meg, hogy néz ki a gyakorlatban.

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

A megvalósítási típushoz létrehozott konstruktor így néz ki.

// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
    (MyNamespace::MySpecializedToggleButton const& owner)
{
    ...
}
...

Az egyetlen hiányzó elem az, hogy át kell adnia a konstruktorparamétert az alaposztálynak. Emlékszel a fent említett F-kötött polimorfizmusi mintára? Ha már ismeri a C++/WinRT által használt minta részleteit, megtudhatja, hogy mi az alaposztály neve (vagy egyszerűen megtekintheti a implementációs osztály fejlécfájlját). Ebben az esetben így hívhatja meg az alaposztály-konstruktort.

// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
    (MyNamespace::MySpecializedToggleButton const& owner) :
    MySpecializedToggleButtonAutomationPeerT<MySpecializedToggleButtonAutomationPeer>(owner)
{
    ...
}
...

Az alaposztály konstruktora ToggleButtont vár. És MySpecializedToggleButtonegyToggleButton.

Amíg el nem végzi a fent leírt szerkesztést (hogy a konstruktor paramétert továbbítsa az alaposztálynak), a fordító megjelöli a konstruktort, és rámutat arra, hogy a MySpecializedToggleButtonAutomationPeer< MySpecializedToggleButtonAutomationPeer_base nevű típuson> nem érhető el megfelelő alapértelmezett konstruktor. Ez valójában az implementálási típus basszusosztályának alaposztálya.

Névterek: előre jelzett típusok, megvalósítási típusok és gyárak

Ahogy azt korábban már láthatta ebben a témakörben, egy C++/WinRT futtatókörnyezeti osztály több C++ osztály formájában létezik több névtérben. A MyRuntimeClass névnek tehát van egy jelentése a winrt::MyProject névtérben, és egy másik jelentéssel a winrt::MyProject::implementációs névtérben. Ügyeljen arra, hogy a környezetben jelenleg melyik névtérrel rendelkezik, majd használjon névtérelőtagokat, ha egy másik névtérből származó névre van szüksége. Nézzük meg közelebbről a szóban forgó névtereket.

  • winrt::MyProject. Ez a névtér előre jelzett típusokat tartalmaz. A leképezett típusú objektum egy proxy; lényegében egy intelligens mutató egy mögöttes objektumra, amelynek megvalósítása történhet itt, a projektben, vagy egy másik fordítási egységben.
  • winrt::MyProject::implementáció. Ez a névtér implementációs típusokat tartalmaz. A megvalósítási típus egy objektuma nem mutató, hanem érték – egy teljes értékű, a stacken lévő C++ objektum. Ne hozzon létre közvetlenül implementációs típust; ehelyett hívja meg a winrt::make parancsot, és adja át a megvalósítási típust sablonparaméterként. Korábban ebben a témakörben már bemutattunk példákat a winrt::make működés közbeni használatára, és egy másik példa a XAML-vezérlők; kötés C++/WinRT-tulajdonsághoz című témakörben található. Lásd még a közvetlen foglalások diagnosztizálása című témakört.
  • winrt::MyProject::factory_implementation. Ez a névtér gyárakat tartalmaz. A névtérben lévő objektumok támogatják az IActivationFactory-t.

Ez a táblázat azt a minimális névtér-minősítést mutatja be, amelyet különböző környezetekben kell használnia.

A kontextusban lévő névtér A tervezett típus megadása A megvalósítás típusának megadása
winrt::MyProject MyRuntimeClass implementation::MyRuntimeClass
winrt::MyProject::implementáció MyProject::MyRuntimeClass MyRuntimeClass

Fontos

Ha az implementációból egy kivetített típust szeretne visszaadni, ügyeljen arra, hogy a(z) MyRuntimeClass myRuntimeClass; megírásával ne példányosítsa az implementációs típust. Az adott forgatókönyvhöz tartozó megfelelő technikákat és kódot a jelen témakörben korábban a megvalósítási típusok és felületek példányosítása és visszaküldése című szakaszban találja.

A probléma az adott forgatókönyvben a MyRuntimeClass myRuntimeClass; esetén az, hogy a veremen létrehoz egy winrt::MyProject::implementation::MyRuntimeClass objektumot. Ez az objektum (implementálási típus) bizonyos módokon ugyanúgy viselkedik, mint a tervezett típus – a metódusok ugyanúgy hívhatók meg rajta; és még egy előrejelzett típussá is konvertálja. Az objektum azonban a normál C++ szabályok szerint destrukál, amikor a hatókör kilép. Ha tehát egy előrejelzett típust (intelligens mutatót) adott vissza az objektumhoz, akkor ez a mutató most már dangling.

Ezt a memóriasérülési típusú hibát nehéz diagnosztizálni. A hibakeresési buildek esetében tehát a C++/WinRT-állítás segít elkapni ezt a hibát egy veremérzékelő használatával. De a korutinok a heapen allokálódnak, így nem kap segítséget ennek a hibának a felismeréséhez, ha ezt a hibát egy korutinon belül követi el. További információkért lásd: A közvetlen lefoglalások diagnosztizálása.

Előre jelzett típusok és implementációtípusok használata különböző C++/WinRT-funkciókkal

Íme néhány hely, ahol a C++/WinRT-funkciók típust várnak, és hogy milyen típust várnak el (előrejelzett típus, implementálás típusa vagy mindkettő).

Funkció Elfogadja Notes
T (intelligens mutatót jelöl) Tervezett Tekintse meg a névterekre vonatkozó figyelmeztetést: előre jelzett típusok, implementálási típusok és gyárak a megvalósítási típus tévedésből történő használatával kapcsolatban.
agile_ref<T> Both Ha a megvalósítási típust használja, akkor a konstruktor argumentumnak kell lennie com_ptr<T>.
com_ptr<T> Implementation Az előrejelzett típus használata a következő hibát eredményezi: 'Release' is not a member of 'T'.
default_interface<T> Both Ha a megvalósítási típust használja, a rendszer visszaadja az első implementált felületet.
get_self<T> Implementation Az előrejelzett típus használata a következő hibát eredményezi: '_abi_TrustLevel': is not a member of 'T'.
guid_of<T>() Both Az alapértelmezett felület GUID azonosítóját adja vissza.
IWinRTTemplateInterface<T>
Tervezett Az implementálási típus használata ugyan lefordul, de ez hiba – lásd a Névterek: leképezett típusok, implementálási típusok és gyárak című témakör figyelmeztetését.
make<T> Implementation Az előrejelzett típus használata a következő hibát eredményezi: 'implements_type': is not a member of any direct or indirect base class of 'T'
make_agile(T const&amp;) Both Ha a megvalósítási típust használja, az argumentumnak meg kell lennie com_ptr<T>.
make_self<T> Implementation Az előrejelzett típus használata a következő hibát eredményezi: 'Release': is not a member of any direct or indirect base class of 'T'
name_of<T> Tervezett Ha a megvalósítási típust használja, az alapértelmezett felület sztringezett GUID-azonosítóját kapja meg.
weak_ref<T> Both Ha a megvalósítási típust használja, akkor a konstruktor argumentumnak kell lennie com_ptr<T>.

Egységes építés és közvetlen megvalósítási hozzáférés engedélyezése

Ez a szakasz egy olyan C++/WinRT 2.0 funkciót ismertet, amely engedélyezve van, bár alapértelmezés szerint engedélyezve van az új projektekhez. Meglévő projekt esetén a cppwinrt.exe eszköz konfigurálásával kell bekapcsolnia. A Visual Studióban állítsa a projekt Common Properties>C++/WinRT>Optimized tulajdonságát Igen értékre. Ennek az a hatása, hogy hozzáadja a(z) <CppWinRTOptimized>true</CppWinRTOptimized> elemet a projektfájlhoz. És ugyanolyan hatással van, mint a kapcsoló hozzáadása a parancssorból való cppwinrt.exe meghíváskor.

A -opt[imize] kapcsoló lehetővé teszi az úgynevezett egységes szerkezetet. Egységes (vagy egységes) konstrukció esetén a C++/WinRT nyelvi kivetítéssel hatékonyan és terhelési nehézségek nélkül hozhatja létre és használhatja implementálási típusait (az összetevő által implementált típusok, alkalmazások általi felhasználás céljából).

A funkció leírása előtt először mutassuk meg a helyzetet egységes szerkezet nélkül . A szemléltetés érdekében ezzel a példával kezdjük Windows-futtatókörnyezet osztályt.

// MyClass.idl
namespace MyProject
{
    runtimeclass MyClass
    {
        MyClass();
        void Method();
        static void StaticMethod();
    }
}

C++ fejlesztőként, aki ismeri a C++/WinRT kódtárat, érdemes lehet az ilyen osztályt használni.

using namespace winrt::MyProject;

MyClass c;
c.Method();
MyClass::StaticMethod();

És ez teljesen ésszerű lenne, feltéve, hogy a megjelenített fogyasztó kód nem ugyanabban az összetevőben található, amely ezt az osztályt implementálja. Nyelvi kivetítésként a C++/WinRT az ABI -tól (a COM-alapú alkalmazás bináris interfészétől) védi Önt fejlesztőként, amelyet a Windows-futtatókörnyezet definiál. A C++/WinRT nem hív meg közvetlenül a megvalósításba; az ABI-n keresztül halad át.

Következésképpen azon a kódsoron, ahol myClass objektumot (MyClass c;) hoz létre, a C++/WinRT-vetítés meghívja a RoGetActivationFactoryt az osztály vagy aktiválási előállító lekéréséhez, majd ezt a gyárat használja az objektum létrehozásához. Az utolsó sor ugyanígy a gyárat használja arra, hogy olyan valamit hozzon létre, ami statikus metódushívásnak tűnik. Ehhez regisztrálni kell az osztályt, és a modul implementálja a DllGetActivationFactory belépési pontot. A C++/WinRT nagyon gyors gyári gyorsítótárral rendelkezik, ezért egyik sem okoz problémát az összetevőt használó alkalmazásoknál. A gond csak az, hogy a komponenseden belül csináltál valamit, ami kissé problémás.

Először is, függetlenül attól, hogy milyen gyors a C++/WinRT gyári gyorsítótár, a RoGetActivationFactory (vagy akár a gyári gyorsítótáron keresztüli későbbi hívások) hívása mindig lassabb lesz, mint közvetlenül a megvalósításba való hívás. A RoGetActivationFactory és az IActivationFactory::ActivateInstance és a QueryInterface hívása nyilvánvalóan nem lesz olyan hatékony, mint egy C++ new kifejezés használata helyileg meghatározott típushoz. Ennek következtében a tapasztalt C++/WinRT-fejlesztők hozzászoktak a winrt::make vagy winrt::make_self segédfüggvények használatához, amikor objektumokat hoznak létre egy összetevőn belül.

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

De, mint látható, ez közel sem olyan kényelmes, sem tömör. Az objektum létrehozásához segédfüggvényt kell használnia, és egyértelműsítenie kell a megvalósítási típus és az előrejelzett típus között is.

Másodszor, ha a leképezést használja az osztály létrehozásához, az azt jelenti, hogy az aktiválási gyára gyorsítótárba kerül. Általában ezt szeretné, de ha a gyár ugyanabban a modulban (DLL-ben) található, amely a hívást intézi, akkor gyakorlatilag rögzítette a DLL-t, és megakadályozta, hogy valaha is eltávolítsa. Sok esetben ez nem számít; de egyes rendszerösszetevőknek támogatniuk kell a kiürítést.

Itt jön létre az egységes szerkezet kifejezés. Függetlenül attól, hogy a létrehozási kód egy olyan projektben található-e, amely csak az osztályt használja, vagy az osztályt ténylegesen megvalósító projektben található, szabadon használhatja ugyanazt a szintaxist az objektum létrehozásához.

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

Amikor az összetevőprojektet a -opt[imize] kapcsolóval hozza létre, a nyelvi kivetítésen keresztüli hívás ugyanarra a hatékony hívásra lesz lefordítva a winrt::make függvényre, amely közvetlenül létrehozza a megvalósítás típusát. Ez egyszerűvé és kiszámíthatóvá teszi a szintaxist, elkerüli a factoryn keresztüli hívás miatti teljesítménycsökkenést, és közben azt is, hogy rögzíteni kelljen az összetevőt. Az összetevőprojektek mellett ez az XAML-alkalmazások esetében is hasznos. Ha megkerüli a RoGetActivationFactory-t az ugyanabban az alkalmazásban implementált osztályokhoz, akkor azokat (regisztráció nélkül) ugyanúgy hozhatja létre, mint az összetevőn kívül.

Az egységes konstrukció minden olyan hívásra vonatkozik, amelyet a gyár kiszolgál a motorháztető alatt. Ez gyakorlatilag azt jelenti, hogy az optimalizálás konstruktorokat és statikus tagokat egyaránt kiszolgál. Íme az eredeti példa.

MyClass c;
c.Method();
MyClass::StaticMethod();

A(z) -opt[imize] nélkül az első és az utolsó utasítást a factoryobjektumon keresztül kell meghívni. Vele-opt[imize] egyikük sem. Ezek a hívások közvetlenül a megvalósításhoz vannak fordítva, és akár beágyazottak is lehetnek. Ez rámutat a másik, gyakran használt kifejezésre is, amikor a(z) -opt[imize] fogalmáról beszélünk, nevezetesen a közvetlen implementációs hozzáférésre.

A nyelvi előrejelzések kényelmesek, de ha közvetlenül hozzáférhet a megvalósításhoz, ezt kihasználva a lehető leghatékonyabb kódot hozhatja létre. A C++/WinRT ezt is meg tudja tenni Önnek, anélkül hogy arra kényszerítené, hogy lemondjon a leképezés nyújtotta biztonságról és hatékonyságról.

Ez kompatibilitástörő változás, mert az összetevőnek együtt kell működnie ahhoz, hogy a nyelvi leképezés közvetlenül hozzáférhessen az implementációs típusaihoz. Mivel a C++/WinRT csak fejléces kódtár, betekinthet, és megnézheti, mi történik. -opt[imize] nélkül a MyClass konstruktor és a StaticMethod tag definícióját a leképezés így határozza meg.

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

Nem szükséges a fentiek mindegyikét követni; a szándék az, hogy mindkét hívás egy call_factory nevű függvény hívását foglalja magában. Ebből lehet tudni, hogy ezek a hívások a gyári gyorsítótárhoz kapcsolódnak, és nem közvetlenül a megvalósítást érik el. Ezzel-opt[imize] ugyanezek a függvények egyáltalán nincsenek definiálva. Ehelyett a projekció deklarálja őket, a meghatározásuk pedig az összetevőre van bízva.

Az összetevő ezután olyan definíciókat adhat meg, amelyek közvetlenül a megvalósításba hívhatók be. Most megérkeztünk a törés változáshoz. Ezek a definíciók automatikusan létrejönnek, amikor a -component és a -opt[imize] elemet is használja, és a Type.g.cpp nevű fájlban jelennek meg, ahol a Type az implementált futtatókörnyezeti osztály neve. Ezért különböző linkerhibákat tapasztalhat, amikor először engedélyezi -opt[imize] egy meglévő projektben. A létrehozott fájlt bele kell foglalnia a megvalósításba, hogy összefűzhesse a dolgokat.

A példánkban így nézhet ki (függetlenül attól, MyClass.h hogy használatban van-e -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>
    {
    };
}

Itt áll össze minden: MyClass.cpp.

#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()
    {
    }
}

Tehát ahhoz, hogy egy meglévő projektben egységes konstrukciót használjon, szerkesztenie kell az egyes implementációk .cpp fájlját úgy, hogy #include <Sub/Namespace/Type.g.cpp> a megvalósítási osztály felvétele (és definiálása) után. Ez a fájl azoknak a függvényeknek a definícióit tartalmazza, amelyeket a vetítés nem definiált. Így néznek ki ezek a definíciók a MyClass.g.cpp fájlban.

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

És ezzel szépen teljessé válik a leképezés: hatékony hívások közvetlenül a megvalósításhoz, a gyári gyorsítótár hívásainak elkerülésével, miközben a linker követelményeinek is megfelel.

Az utolsó dolog, amit a -opt[imize] elvégez Ön helyett, az a projekt module.g.cpp implementációjának olyan módosítása (vagyis annak a fájlnak a módosítása, amely segít megvalósítani a DLL DllGetActivationFactory és DllCanUnloadNow exportjait), hogy a növekményes fordítások jellemzően sokkal gyorsabbak legyenek a C++/WinRT 1.0 által megkövetelt szoros típuskötés megszüntetésével. Ezt gyakran nevezik típustörlésű gyáraknak. A -opt[imize] nélkül az összetevőhöz létrehozott module.g.cpp fájl kezdetben az összes megvalósítási osztály definícióját tartalmazza – ebben a példában a MyClass.h definícióját. Ezután közvetlenül létrehozza az implementációs gyárat minden osztályhoz, mint ez.

if (requal(name, L"MyProject.MyClass"))
{
    return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}

Nem kell minden részletet követnie. Hasznos látni, hogy ehhez az összetevő által implementált összes osztály teljes definíciója szükséges. Ez drámai hatással lehet a belső ciklusodra, mivel egyetlen implementáció bármilyen módosítása a(z) module.g.cpp újrafordítását eredményezi. Ezzel -opt[imize]már nem ez a helyzet. Ehelyett két dolog történik a létrehozott module.g.cpp fájllal. Az első az, hogy már nem tartalmaz implementációs osztályokat. Ebben a példában egyáltalán nem fog szerepelni MyClass.h . Ehelyett a megvalósítási gyárakat a megvalósításuk ismerete nélkül hozza létre.

void* winrt_make_MyProject_MyClass();

if (requal(name, L"MyProject.MyClass"))
{
    return winrt_make_MyProject_MyClass();
}

Nyilvánvaló, hogy nincs szükség a definícióik belefoglalására, és a linker feladata a winrt_make_Component_Class függvény definíciójának feloldása. Természetesen nem kell ezen gondolkodnia, mert az Ön számára létrehozott MyClass.g.cpp fájl (amelyet korábban az egységes konstrukció támogatása érdekében belefoglalt) szintén meghatározza ezt a függvényt. Íme a példához létrehozott fájl teljes egésze MyClass.g.cpp .

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

Mint látható, a winrt_make_MyProject_MyClass függvény közvetlenül létrehozza a megvalósítás gyárát. Mindez azt jelenti, hogy nyugodtan módosíthat bármely implementációt, és a(z) module.g.cpp egyáltalán nem kell újrafordítani. A(z) module.g.cpp csak akkor frissül, és csak akkor kell újra fordítani, ha Windows-futtatókörnyezet-osztályokat ad hozzá vagy távolít el.

Alaposztályú virtuális metódusok felülírása

A származtatott osztálynak lehetnek problémái a virtuális metódusokkal, ha az alap és a származtatott osztály is alkalmazásalapú osztály, de a virtuális metódus egy nagyszülő Windows-futtatókörnyezet osztályban van definiálva. A gyakorlatban erre akkor kerül sor, ha XAML-osztályokból származtat. A szakasz többi része a Származtatott osztályokban szereplő példából folytatódik.

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

A hierarchia a következő: Microsoft::UI::Xaml::Controls::Page<- BasePage<- DerivedPage. A BasePage::OnNavigatedFrom metódus helyesen felülbírálja a Page::OnNavigatedFrom, de DerivedPage::OnNavigatedFrom nem bírálja felül a BasePage::OnNavigatedFrom parancsot.

Itt a DerivedPage újra felhasználja az IPageOverrides virtuális táblát a BasePage-ből, ami azt jelenti, hogy nem írja felül az IPageOverrides::OnNavigatedFrom metódust . Az egyik lehetséges megoldáshoz a BasePage-nek sablonosztálynak kell lennie, és teljes mértékben egy fejlécfájlban kell lennie, de ez elfogadhatatlanul bonyolulttá teszi a dolgokat.

Kerülő megoldásként deklarálja az OnNavigatedFrom metódust explicit virtuálisként az alaposztályban. Így, amikor a DerivedPage::IPageOverrides::OnNavigatedFrom vtable-bejegyzése meghívja a BasePage::IPageOverrides::OnNavigatedFrom elemet, a producer a BasePage::OnNavigatedFrom metódust hívja meg, amely a virtuális volta miatt végül a DerivedPage::OnNavigatedFrom metódust hívja meg.

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

Ehhez az osztályhierarchia minden tagjának meg kell egyeznie az OnNavigatedFrom metódus visszatérési értékével és paramétertípusaival. Ha nem értenek egyet, akkor a fenti verziót kell virtuális metódusként használni, és az alternatív változatokat burkolóba kell foglalni.

Note

Az IDL-nek nem kell deklarálnia a felülírt metódust. További részletekért lásd: Felülírható módszerek implementálása.

Fontos API-k