Udostępnij przez


Tworzenie interfejsów API za pomocą języka C++/WinRT

W tym temacie pokazano, jak utworzyć interfejsy API C++/WinRT przy użyciu podstawowej struktury winrt::implements, bezpośrednio lub pośrednio. Synonimy dla autor w tym kontekście to produkowaćlub wdrożyć. W tym temacie opisano następujące scenariusze implementowania interfejsów API w typie C++/WinRT w tej kolejności.

Uwaga / Notatka

W tym temacie omówiono temat składników środowiska uruchomieniowego systemu Windows, ale tylko w kontekście języka C++/WinRT. Jeśli szukasz zawartości składników środowiska uruchomieniowego systemu Windows, które obejmują wszystkie języki środowiska uruchomieniowego systemu Windows, zobacz składniki środowiska uruchomieniowego systemu Windows.

  • Nie tworzysz klasy środowiska uruchomieniowego systemu Windows (klasy środowiska uruchomieniowego); chcesz tylko zaimplementować jeden lub więcej interfejsów środowiska uruchomieniowego systemu Windows do lokalnego wykorzystania w aplikacji. W tym przypadku bezpośrednio pochodzisz z winrt::implements i implementujesz funkcje.
  • jesteś autorem klasy środowiska uruchomieniowego. Możesz utworzyć składnik, który ma być używany z aplikacji. Możesz też utworzyć typ do użycia w interfejsie użytkownika XAML, a w takim przypadku zarówno implementujesz, jak i używasz klasy środowiska uruchomieniowego w ramach tej samej jednostki kompilacji. W takich przypadkach można zezwolić narzędziom na generowanie klas, które pochodzą z winrt::implements.

W obu przypadkach typ implementujący interfejsy API języka C++/WinRT jest nazywany typem implementacji .

Ważne

Ważne jest, aby odróżnić koncepcję typu implementacji od typu przewidywanego. Przewidywany typ jest opisany w Korzystanie z interfejsów API za pomocą języka C++/WinRT.

Jeśli nie tworzysz klasy środowiska uruchomieniowego

Najprostszym scenariuszem jest, gdy twój typ implementuje interfejs środowiska uruchomieniowego systemu Windows i będziesz używał tego typu w tej samej aplikacji. W takim przypadku typ nie musi być klasą środowiska uruchomieniowego; tylko zwykła klasa C++. Na przykład możesz napisać aplikację opartą na CoreApplication.

Jeśli typ jest przywołytyny przez interfejs użytkownika XAML, musi być klasą środowiska uruchomieniowego, mimo że znajduje się w tym samym projekcie co XAML. W takim przypadku, zobacz sekcję , jeśli tworzysz klasę uruchomieniową do odwołania w interfejsie użytkownika XAML.

Uwaga / Notatka

Aby uzyskać informacje na temat instalowania i używania rozszerzenia C++/WinRT Visual Studio (VSIX) i pakietu NuGet (które razem zapewniają obsługę szablonu projektu i kompilacji), zobacz obsługa programu Visual Studio dla języka C++/WinRT.

W programie Visual Studio szablon projektu Core App (C++/WinRT) Visual C++Core App (C++/WinRT) ilustruje wzorzec CoreApplic ation. Wzorzec zaczyna się od przekazania implementacji 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 używa interfejsu do utworzenia pierwszego widoku aplikacji. Koncepcyjnie IFrameworkViewSource wygląda następująco.

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

Koncepcyjnie ponownie, implementacja CoreApplication::Run wykonuje to.

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

Dlatego jako deweloper implementujesz interfejs IFrameworkViewSource. Język C++/WinRT ma szablon struktury podstawowej winrt::implements, aby ułatwić implementację interfejsu (lub kilku) bez uciekania się do programowania w stylu COM. Po prostu wyprowadzasz swój typ z , który implementuje, a następnie implementujesz funkcje interfejsu. Oto jak to zrobić.

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

To zajmie się IFrameworkViewSource. Następnym krokiem jest zwrócenie obiektu, który implementuje interfejs IFrameworkView. Możesz też zaimplementować ten interfejs w usłudze App. Ten następny przykładowy kod reprezentuje minimalną aplikację, która będzie przynajmniej uruchamiać okno na pulpicie.

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

Ponieważ twój typ aplikacjitoIFrameworkViewSource, możesz po prostu przekazać go do Run.

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

Jeśli tworzysz klasę środowiska uruchomieniowego w składniku środowiska uruchomieniowego systemu Windows

Jeśli typ jest spakowany w składniku środowiska uruchomieniowego systemu Windows do użycia z innego pliku binarnego (drugi plik binarny jest zazwyczaj aplikacją), typ musi być klasą środowiska uruchomieniowego. Zadeklarujesz klasę wykonawczą w pliku IDL (.idl) (patrz rozbijanie na pliki Midl (.idl)).

Każdy plik IDL prowadzi do utworzenia pliku .winmd, a następnie program Visual Studio scala wszystkie te pliki w jeden plik o tej samej nazwie co przestrzeń nazw główna. Ten ostatni plik .winmd będzie tym, do którego będą odwoływać się użytkownicy składnika.

Oto przykład deklarowania klasy środowiska uruchomieniowego w pliku 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;
    }
}

Ten protokół IDL deklaruje klasę Środowiska uruchomieniowego (runtime) systemu Windows. Klasa uruchomieniowa to typ, który można aktywować i używać za pośrednictwem nowoczesnych interfejsów COM, zazwyczaj pomiędzy granicami procesów wykonywalnych. Podczas dodawania pliku IDL do projektu i kompilowania łańcuch narzędzi C++/WinRT (midl.exe i cppwinrt.exe) generuje typ implementacji. Aby zapoznać się z przykładem działania przepływu pracy pliku IDL, zobacz kontrolki XAML ; połącz z właściwością C++/WinRT.

Korzystając z powyższego przykładowego kodu IDL, typ implementacji to szkic struktury C++ o nazwie winrt::MyProject::implementation::MyRuntimeClass w plikach kodu źródłowego o nazwie \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h i MyRuntimeClass.cpp.

Typ implementacji wygląda następująco.

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

Zwróć uwagę, że używany wzorzec polimorfizmu związany z F (MyRuntimeClass używa się jako argument szablonu w bazie MyRuntimeClassT). Jest to również nazywane ciekawie powtarzającym się wzorcem szablonu (CRTP). Jeśli obserwujesz łańcuch dziedziczenia w górę, napotkasz MyRuntimeClass_base.

Implementację prostych właściwości można uprościć przy użyciu bibliotek implementacji systemu Windows (WIL). Oto, jak to zrobić:

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

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

Zobacz Proste właściwości.

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

Dlatego w tym scenariuszu głównym elementem hierarchii dziedziczenia jest winrt::implementuje szablon struktury podstawowej po raz kolejny.

Aby uzyskać więcej informacji, kod i przewodnik tworzenia interfejsów API w składniku środowiska uruchomieniowego systemu Windows, zobacz składniki środowiska uruchomieniowego systemu Windows za pomocą języka C++/WinRT i Tworzenie zdarzeń w języku C++/WinRT.

Jeśli tworzysz klasę środowiska uruchomieniowego, której można się odwołać w interfejsie użytkownika XAML

Jeśli typ jest przywołytywalny przez interfejs użytkownika XAML, musi być klasą środowiska uruchomieniowego, mimo że znajduje się w tym samym projekcie co XAML. Mimo że są one zwykle aktywowane przez granice wykonywalne, klasa środowiska uruchomieniowego może zamiast tego być używana w jednostce kompilacji, która ją implementuje.

W tym scenariuszu zarówno tworzysz, jak i korzystasz z interfejsów API i. Procedura implementowania klasy środowiska uruchomieniowego jest zasadniczo taka sama jak w przypadku składnika środowiska uruchomieniowego systemu Windows. Zobacz więc poprzednią sekcję —Jeśli tworzysz klasę środowiska uruchomieniowego w składniku środowiska uruchomieniowego systemu Windows. Jedynym szczegółem, który się różni, jest to, że od IDL łańcuch narzędzi C++/WinRT generuje nie tylko typ implementacji, ale także przewidywany typ. Ważne jest, aby docenić, że powiedzenie tylko "MyRuntimeClass" w tym scenariuszu może być niejednoznaczne; istnieje kilka jednostek o tej nazwie, o różnych rodzajach.

  • MyRuntimeClass jest nazwą klasy środowiska uruchomieniowego. Ale to jest naprawdę abstrakcja: zadeklarowana w języku IDL i zaimplementowana w pewnym języku programowania.
  • MyRuntimeClass to nazwa struktury języka C++ winrt::MyProject::implementation::MyRuntimeClass, czyli implementacji języka C++/WinRT klasy środowiska uruchomieniowego. Jak widzieliśmy, jeśli istnieją oddzielne projekty implementowania i używania, ta struktura istnieje tylko w projekcie implementowania. Jest to typ implementacjilub implementacji. Ten typ jest generowany (przez narzędzie cppwinrt.exe) w plikach \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h i MyRuntimeClass.cpp.
  • MyRuntimeClass jest nazwą przewidywanego typu w postaci struktury C++ winrt::MyProject::MyRuntimeClass. Jeśli istnieją oddzielne projekty implementowania i używania, ta struktura istnieje tylko w projekcie korzystającym. Jest to przewidywany typlub projekcja. Ten typ jest generowany (przez cppwinrt.exe) w pliku \MyProject\MyProject\Generated Files\winrt\impl\MyProject.2.h.

typ projektu i typ implementacji

Poniżej przedstawiono części przewidywanego typu, które są istotne dla tego tematu.

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

Aby zapoznać się z przykładowym przewodnikiem implementacji interfejsu INotifyPropertyChanged w klasie środowiska uruchomieniowego, zobacz kontrolki XAML; wiązanie z właściwością C++/WinRT.

W tym scenariuszu procedura korzystania z klasy środowiska uruchomieniowego jest opisana w Korzystanie z interfejsów API za pomocą języka C++/WinRT.

Rozbijanie klas środowiska uruchomieniowego na pliki Midl (.idl)

Szablony projektów i elementów programu Visual Studio tworzą oddzielny plik IDL dla każdej klasy środowiska uruchomieniowego. Zapewnia to logiczną korespondencję między plikiem IDL a wygenerowanymi plikami kodu źródłowego.

Jeśli jednak skonsolidujesz wszystkie klasy środowiska uruchomieniowego projektu w jeden plik IDL, może to znacznie poprawić czas kompilacji. Jeśli w przeciwnym razie istnieją złożone (lub cykliczne) zależności import między nimi, konsolidacja może być rzeczywiście konieczna. Możesz też łatwiej tworzyć i przeglądać klasy środowiska uruchomieniowego, jeśli są razem.

Konstruktory klas środowiska uruchomieniowego

Oto kilka punktów, które warto zapamiętać z powyższego zestawienia.

  • Każdy konstruktor zadeklarowany w języku IDL powoduje wygenerowanie konstruktora zarówno dla typu implementacji, jak i dla przewidywanego typu. Konstruktory zadeklarowane za pomocą języka IDL służą do korzystania z klasy środowiska uruchomieniowego z innej jednostki kompilacji.
  • Niezależnie od tego, czy masz konstruktory zadeklarowane za pomocą języka IDL, czy nie, przeciążenie konstruktora, które przyjmuje std::nullptr_t jest generowane dla przewidywanego typu. Wywołanie konstruktora std::nullptr_t jest pierwszym z dwóch kroków w użyciu klasy środowiska uruchomieniowego z tej samej jednostki kompilacji. Aby uzyskać więcej szczegółów i przykład kodu, zobacz Consume APIs with C++/WinRT(Korzystanie z interfejsów API w języku C++/WinRT).
  • Jeśli używasz klasy środowiska uruchomieniowego z tej samej jednostki kompilacji, możesz również zaimplementować konstruktory inne niż domyślne bezpośrednio na typ implementacji (co należy pamiętać, znajduje się w MyRuntimeClass.h).

Uwaga / Notatka

Jeśli oczekujesz, że klasa środowiska uruchomieniowego będzie zużywana z innej jednostki kompilacji (która jest powszechna), dołącz konstruktory w języku IDL (co najmniej konstruktor domyślny). Dzięki temu uzyskasz również implementację fabryki wraz z typem implementacji.

Jeśli chcesz tworzyć i korzystać z klasy środowiska uruchomieniowego tylko w ramach tej samej jednostki kompilacji, nie deklaruj żadnych konstruktorów w języku IDL. Nie potrzebujesz implementacji fabryki i nie zostanie wygenerowana. Domyślny konstruktor typu implementacji zostanie usunięty, ale można go łatwo edytować i zamiast tego ustawić jako domyślny.

Jeśli chcesz utworzyć klasę środowiska uruchomieniowego i korzystać z nich tylko w ramach tej samej jednostki kompilacji i potrzebujesz parametrów konstruktora, utwórz konstruktory, których potrzebujesz bezpośrednio w typie implementacji.

Metody, właściwości i zdarzenia klas środowiska uruchomieniowego

Widzieliśmy, że przepływ pracy polega na użyciu języka IDL w celu zadeklarowania klasy środowiska uruchomieniowego i jej składowych, a następnie narzędzia generują prototypy i implementacje wycinków. Jeśli chodzi o te automatycznie wygenerowane prototypy dla elementów członkowskich klasy środowiska uruchomieniowego, można je edytować, aby przechodziły wokół różnych typów niż typy zadeklarowane w języku IDL. Można to jednak zrobić tylko tak długo, jak typ zadeklarowany w języku IDL można przekazać do typu zadeklarowanego w zaimplementowanej wersji.

Oto kilka przykładów.

  • Można poluzować typy parametrów. Jeśli na przykład w języku IDL metoda przyjmuje SomeClass, możesz zmienić to na IInspectable w swojej implementacji. Działa to, ponieważ dowolna SomeClass może być przekazana do IInspectable (odwrotnie, oczywiście, nie zadziała).
  • Możesz zaakceptować parametr możliwy do skopiowania według wartości, a nie przez odwołanie. Na przykład zmień SomeClass const& na SomeClass. Jest to konieczne, gdy należy unikać przechwytywania odwołania do kohroutyny (zobacz przekazywanie parametrów).
  • Można rozluźnić wartość zwracaną. Możesz na przykład zmienić void na winrt::fire_and_forget.

Ostatnie dwa są bardzo przydatne podczas pisania asynchronicznej procedury obsługi zdarzeń.

Tworzenie wystąpienia i zwracanie typów implementacji i interfejsów

W tej sekcji weźmy jako przykład typ implementacji o nazwie MyType, który implementuje interfejsy IStringable i IClosable.

Można uzyskać MyType bezpośrednio z winrt::implements (nie jest to klasa środowiska uruchomieniowego).

#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;

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

Możesz też wygenerować go na podstawie języka IDL (jest to klasa środowiska uruchomieniowego).

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

Nie można bezpośrednio przydzielić typu implementacji.

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

Można jednak przejść z MyType do obiektu IStringable lub IClosable, którego można użyć lub zwrócić w ramach projekcji, wywołując szablon funkcji winrt::make. Polecenie make zwraca domyślny interfejs typu implementacji.

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

Uwaga / Notatka

Jeśli jednak odwołujesz się do typu z interfejsu użytkownika XAML, w tym samym projekcie będzie istnieć zarówno typ implementacji, jak i typ przewidywany. W takim przypadku make zwraca wystąpienie typu projekcji. Aby zapoznać się z przykładem kodu tego scenariusza, zobacz kontrolek XAML; powiązanie z właściwością C++/WinRT.

Możemy użyć istringable (w powyższym przykładzie kodu) tylko do wywoływania członków interfejsu IStringable. Jednak interfejs C++/WinRT (który jest przewidywanym interfejsem) pochodzi z winrt::Windows::Foundation::IUnknown. W związku z tym można wywołać IUnknown::as (lub IUnknown::try_as) w celu wykonywania zapytań o inne przewidywane typy lub interfejsy, których można również użyć lub zwrócić.

Wskazówka

Scenariusz, w którym nie należy wywoływać as lub try_as jest dziedziczenie klas środowiska uruchomieniowego ("klasy komponowalne"). Gdy typ implementacji komponuje inną klasę, nie wywołuj as ani try_as, aby wykonać niezaznaczone lub zaznaczone QueryInterface tej klasy. Zamiast tego uzyskaj dostęp do elementu członkowskiego danych (this->) m_inner i wywołaj metody as lub try_as na nim. Aby uzyskać więcej informacji, zobacz Wyprowadzanie klas środowiska uruchomieniowego w tym temacie.

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

Jeśli musisz uzyskać dostęp do wszystkich członków implementacji, a następnie zwrócić interfejs wywołującemu, użyj szablonu funkcji winrt::make_self. make_self zwraca winrt::com_ptr opakowujący typ implementacji. Możesz uzyskać dostęp do elementów członkowskich wszystkich jego interfejsów (przy użyciu operatora strzałki), możesz zwrócić go do obiektu wywołującego as-islub wywołać jako i zwrócić wynikowy obiekt interfejsu do obiektu wywołującego.

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

Klasa MyType nie jest częścią projekcji; jest to implementacja. Jednak w ten sposób można bezpośrednio wywołać jej metody implementacji bez dodatkowych kosztów związanych z wywołaniem funkcji wirtualnej. W powyższym przykładzie, mimo że MyType::ToString używa tego samego podpisu co projektowana metoda w IStringable, wywołujemy metodę inną niż wirtualna bezpośrednio bez przekraczania interfejsu binarnego aplikacji (ABI). com_ptr po prostu przechowuje wskaźnik do struktury MyType, dzięki czemu można również uzyskać dostęp do innych wewnętrznych szczegółów MyType za pośrednictwem zmiennej myimpl i operatora strzałki.

W przypadku, gdy masz obiekt interfejsu i wiesz, że jest to interfejs implementacji, możesz wrócić do implementacji przy użyciu szablonu funkcji winrt::get_self. Ponownie jest to technika, która pozwala uniknąć wywołań funkcji wirtualnych i umożliwia bezpośrednie uzyskanie dostępu do implementacji.

Uwaga / Notatka

Jeśli nie zainstalowano zestawu Windows SDK w wersji 10.0.17763.0 (Windows 10, wersja 1809) lub nowszej, należy wywołać winrt::from_abi zamiast winrt::get_self.

Oto przykład. W Implement jest kolejny przykład niestandardowej klasy kontrolki BgLabelControl.

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

Jednak tylko oryginalny obiekt interfejsu trzyma się odwołania. Jeśli chcesz to zachować, możesz wywołać 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.

Sam typ implementacji nie pochodzi z winrt::Windows::Foundation::IUnknown, więc nie ma jako funkcji. Mimo to, jak można zobaczyć w powyższej funkcji ImplFromIClosable, można uzyskać dostęp do członków wszystkich jego interfejsów. Jeśli jednak to zrobisz, nie zwracaj nieprzetworzonego wystąpienia typu implementacji wywołującemu. Zamiast tego należy użyć jednej z przedstawionych wcześniej technik i zwrócić projektowany interfejs lub com_ptr.

Jeśli masz instancję swojego typu implementacji i musisz przekazać ją do funkcji, która oczekuje odpowiadającego typu prognozowanego, możesz to zrobić, co pokazano na poniższym przykładzie kodu. Operator konwersji istnieje dla typu implementacji (pod warunkiem, że typ implementacji został wygenerowany przez narzędzie cppwinrt.exe), co to umożliwia. Wartość typu implementacji można przekazać bezpośrednio do metody, która oczekuje wartości odpowiedniego projektowanego typu. Z funkcji składowej typu implementacji można przekazać *this do metody, która oczekuje wartości odpowiedniego typu przewidywanego.

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

Wyprowadzanie klas środowiska uruchomieniowego

Możesz utworzyć klasę środowiska uruchomieniowego, która pochodzi z innej klasy środowiska uruchomieniowego, pod warunkiem, że klasa bazowa jest zadeklarowana jako "niezaużytowana". Termin środowiska uruchomieniowego systemu Windows dla wyprowadzania klas to "klasy komponowalne". Kod implementujący klasę pochodną zależy od tego, czy klasa bazowa jest dostarczana przez inny składnik, czy przez ten sam składnik. Na szczęście nie musisz uczyć się tych reguł — wystarczy skopiować przykładowe implementacje z folderu wyjściowego sources utworzonego cppwinrt.exe przez kompilator.

Rozważ następujący przykład.

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

W powyższym przykładzie element MyButton pochodzi z kontrolki przycisków XAML, która jest dostarczana przez inny składnik. W takim przypadku implementacja wygląda tak samo, jak implementacja klasy niekomponowalnej:

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

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

Z drugiej strony, w powyższym przykładzie, MyDerived pochodzi z innej klasy w tym samym składniku. W takim przypadku implementacja wymaga dodatkowego parametru szablonu określającego klasę implementacji dla klasy bazowej.

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

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

W obu przypadkach implementacja może wywołać metodę z klasy bazowej, kwalifikując ją aliasem base_type typu:

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

Wskazówka

Gdy typ implementacji komponuje inną klasę, nie wywołuj as ani try_as, aby wykonać niezaznaczone lub zaznaczone QueryInterface tej klasy. Zamiast tego uzyskaj dostęp do elementu członkowskiego danych (this->) m_inner i wywołaj metody as lub try_as na nim.

Wyprowadzanie z typu, który ma konstruktor inny niż domyślny

ToggleButtonAutomationPeer::ToggleButtonAutomationPeer(ToggleButton) jest przykładem konstruktora innego niż domyślny. Nie ma więc konstruktora domyślnego, aby utworzyć ToggleButtonAutomationPeer, należy przekazać właściciela. W związku z tym, jeśli pochodzisz z ToggleButtonAutomationPeer, należy podać konstruktor, który przyjmuje właściciela i przekazuje go do bazy. Zobaczmy, jak to wygląda w praktyce.

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

Wygenerowany konstruktor dla typu implementacji wygląda następująco.

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

Jedynym brakującym elementem jest to, że należy przekazać ten parametr konstruktora do klasy bazowej. Pamiętasz wzorzec polimorfizmu związany z F, o którym wspomnieliśmy powyżej? Po zapoznaniu się ze szczegółami tego wzorca stosowanego w C++/WinRT, możesz dowiedzieć się, jak nazywa się twoja klasa bazowa (lub po prostu zajrzeć do pliku nagłówkowego swojej klasy implementacji). W tym przypadku należy wywołać konstruktor klasy bazowej.

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

Konstruktor klasy bazowej oczekuje ToggleButton. A MySpecializedToggleButtontoToggleButton.

Dopóki nie wprowadzisz edycji opisanej powyżej (aby przekazać ten parametr konstruktora do klasy bazowej), kompilator oznaczy konstruktor flagą i wskaże, że nie ma odpowiedniego domyślnego konstruktora dostępnego dla typu o nazwie (w tym przypadku) MySpecializedToggleButtonAutomationPeer_base<MySpecializedToggleButtonAutomationPeer>. To tak naprawdę klasa bazowa klasy basowej w Twoim typie implementacji.

Przestrzenie nazw: projektowane typy, typy implementacji i wytwórnie

Jak pokazano wcześniej w tym temacie, klasa środowiska uruchomieniowego C++/WinRT istnieje w postaci więcej niż jednej klasy języka C++ w więcej niż jednej przestrzeni nazw. Dlatego nazwa MyRuntimeClass ma jedno znaczenie w przestrzeni nazw winrt::MyProject i inne znaczenie w przestrzeni nazw winrt::MyProject::implementation. Należy pamiętać o tym, która przestrzeń nazw jest obecnie w kontekście, a następnie używać prefiksów przestrzeni nazw, jeśli potrzebujesz nazwy z innej przestrzeni nazw. Przyjrzyjmy się bliżej przestrzeniom nazw, o których mowa.

  • winrt::MyProject. Ta przestrzeń nazw zawiera przewidywane typy. Obiekt projektowanego typu jest proxy; jest to zasadniczo inteligentny wskaźnik do obiektu bazowego, który może zostać zaimplementowany w projekcie lub w innej jednostce kompilacji.
  • winrt::MyProject::implementation. Ta przestrzeń nazw zawiera typy implementacji. Obiekt typu implementacji nie jest wskaźnikiem; jest to wartość — pełny obiekt stosu C++. Nie konstruuj typu implementacji bezpośrednio; Zamiast tego wywołaj metodę winrt::make, przekazując typ implementacji jako parametr szablonu. Pokazaliśmy przykłady winrt::make w działaniu wcześniej w tym temacie i istnieje inny przykład w kontrolkach XAML; wiązanie z właściwością C++/WinRT. Zobacz również Diagnozowanie bezpośrednich alokacji.
  • winrt::MyProject::factory_implementation. Ta przestrzeń nazw zawiera fabryki. Obiekt w tej przestrzeni nazw obsługuje IActivationFactory.

W tej tabeli przedstawiono minimalną kwalifikację przestrzeni nazw, której należy użyć w różnych kontekstach.

Przestrzeń nazw w kontekście Aby określić przewidywany typ Aby określić typ implementacji
winrt::MyProject MyRuntimeClass implementation::MyRuntimeClass
winrt::MyProject::implementation MyProject::MyRuntimeClass MyRuntimeClass

Ważne

Jeśli chcesz zwrócić przewidywany typ z implementacji, należy zachować ostrożność, aby nie utworzyć wystąpienia typu implementacji, pisząc MyRuntimeClass myRuntimeClass;. Poprawne techniki i kod dla tego scenariusza są pokazane wcześniej w tym temacie w sekcji Instancjowanie i Zwracanie Typów Implementacji i Interfejsów.

Problem z MyRuntimeClass myRuntimeClass; w tym scenariuszu polega na tym, że tworzy obiekt winrt::MyProject::implementation::MyRuntimeClass na stosie. Ten obiekt (typu implementacji) zachowuje się jak typ przewidywany — można wywołać na nim metody w taki sam sposób; a nawet konwertuje na typ przewidywany. Obiekt ulega zniszczeniu, zgodnie z normalnymi regułami języka C++, gdy zakres wychodzi. Dlatego, jeśli zwróciłeś typ wskaźnikowy (inteligentny wskaźnik) do tego obiektu, to ten wskaźnik jest teraz wiszący.

Ten typ uszkodzenia pamięci jest trudny do zdiagnozowania. Dlatego w przypadku kompilacji debug, asercja C++/WinRT pomaga wychwycić ten błąd, korzystając z wykrywacza stosu. Ale korutyny są przydzielane na stercie, więc nie otrzymasz pomocy w przypadku tego błędu, jeśli popełnisz go wewnątrz korutyny. Aby uzyskać więcej informacji, zobacz Diagnozowanie bezpośrednich alokacji.

Używanie typów projekcjonowanych i typów implementacji z różnymi funkcjami języka C++/WinRT

Oto różne miejsca, w których cechy C++/WinRT oczekują typu i jaki rodzaj typu jest oczekiwany (typ projekcji, typ implementacji lub oba).

Funkcja Akceptuje Notatki
T (reprezentując inteligentny wskaźnik) Przewidywane Zwróć uwagę na ostrzeżenie w Przestrzenie nazw: przewidywane typy, typy implementacji i fabryki dotyczące niezamierzonego użycia typu implementacji.
agile_ref<T> Oboje Jeśli używasz typu implementacji, argument konstruktora musi być com_ptr<T>.
com_ptr<T> Implementacja Użycie przewidywanego typu generuje błąd: 'Release' is not a member of 'T'.
default_interface<T> Oboje Jeśli używasz typu implementacji, zwracany jest pierwszy zaimplementowany interfejs.
get_self<T> Implementacja Użycie przewidywanego typu generuje błąd: '_abi_TrustLevel': is not a member of 'T'.
guid_of<T>() Oboje Zwraca identyfikator GUID interfejsu domyślnego.
IWinRTTemplateInterface<T>
Przewidywane Użycie typu implementacji się kompiluje, ale jest to błąd — zobacz uwagę w Przestrzenie nazw: projektowane typy, typy implementacji i fabryki.
make<T> Implementacja Użycie przewidywanego typu generuje błąd: 'implements_type': is not a member of any direct or indirect base class of 'T'
make_agile(T const&amp;) Oboje Jeśli używasz typu implementacji, argument musi być com_ptr<T>.
make_self<T> Implementacja Użycie przewidywanego typu generuje błąd: 'Release': is not a member of any direct or indirect base class of 'T'
name_of<T> Przewidywane Jeśli używasz typu implementacji, otrzymasz GUID interfejsu domyślnego w formacie ciągu znaków.
weak_ref<T> Oboje Jeśli używasz typu implementacji, argument konstruktora musi być com_ptr<T>.

Wyrażanie zgody na jednolitą konstrukcję i bezpośredni dostęp do implementacji

W tej sekcji opisano funkcję języka C++/WinRT 2.0, która jest opcjonalnie dostępna (choć dla nowych projektów jest włączona domyślnie). W przypadku istniejącego projektu należy wyrazić zgodę, konfigurując narzędzie cppwinrt.exe. W programie Visual Studio ustaw właściwość projektu Wspólne właściwości>C++/WinRT>Zoptymalizowane na Tak. Ma to wpływ na dodanie <CppWinRTOptimized>true</CppWinRTOptimized> do pliku projektu. I ma taki sam efekt jak dodanie przełącznika podczas wywoływania cppwinrt.exe z wiersza polecenia.

Przełącznik -opt[imize] umożliwia to, co często nazywa się jednolitą konstrukcją. Z jednolitą (lub ujednoliconą) konstrukcją używasz projekcji języka C++/WinRT do efektywnego tworzenia i używania typów implementacyjnych (typów implementowanych przez składnik, do używania przez aplikacje) bez żadnych trudności z modułem ładującym.

Zanim opiszemy tę funkcję, najpierw pokażmy sytuację bez jednolitej konstrukcji. Aby zilustrować, zaczniemy od tej przykładowej klasy Środowiska uruchomieniowego systemu Windows.

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

Deweloper języka C++ zaznajomiony z używaniem biblioteki C++/WinRT może chcieć użyć takiej klasy.

using namespace winrt::MyProject;

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

Byłoby to całkowicie uzasadnione, pod warunkiem, że pokazany kod zużywający nie znajduje się w tym samym składniku, który implementuje tę klasę. Jako projekcja języka C++/WinRT chroni Ciebie jako dewelopera przed ABI (interfejsem binarnym aplikacji opartym na modelu COM, który definiuje środowisko uruchomieniowe systemu Windows). Język C++/WinRT nie wywołuje implementacji bezpośrednio; przechodzi przez ABI.

W związku z tym w wierszu kodu, w którym tworzysz obiekt MyClass (MyClass c;), projekcja C++/WinRT wywołuje RoGetActivationFactory w celu pobrania klasy lub fabryki aktywacji, a następnie używa tej fabryki do utworzenia obiektu. Ostatnia linia również używa fabryki do wykonania tego, co wydaje się być wywołaniem metody statycznej. Wszystko to wymaga zarejestrowania klasy i tego, aby moduł implementował punkt wejścia DllGetActivationFactory. C++/WinRT posiada bardzo szybką fabryczną pamięć podręczną, więc to nie powoduje problemów z aplikacją korzystającą z Twojego komponentu. Problem polega na tym, że w swoim komponencie zrobiłeś coś, co jest trochę problematyczne.

Po pierwsze, niezależnie od tego, jak szybka jest fabryczna pamięć podręczna C++/WinRT, wywoływanie za pośrednictwem RoGetActivationFactory (a nawet kolejnych wywołań za pośrednictwem fabrycznej pamięci podręcznej) zawsze będzie wolniejsze niż bezpośrednie wywołanie do implementacji. Wywołanie RoGetActivationFactory, a następnie IActivationFactory::ActivateInstance a następnie QueryInterface nie będzie tak wydajne, jak użycie wyrażenia new języka C++ dla typu zdefiniowanego lokalnie. W konsekwencji doświadczeni deweloperzy C++/WinRT są przyzwyczajeni do używania funkcji pomocniczych winrt::make lub winrt::make_self podczas tworzenia obiektów w składniku.

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

Ale, jak widać, nie jest to prawie tak wygodne, ani zwięzłe. Aby utworzyć obiekt, należy użyć funkcji pomocniczej, a także uściślać typ implementacji i typ przewidywany.

Po drugie, użycie projekcji do utworzenia klasy oznacza, że jej fabryka aktywacji zostanie zbuforowana. Zazwyczaj tego właśnie chcesz, ale jeśli fabryka znajduje się w tym samym module (DLL), który wykonuje wywołanie, to skutecznie przypinasz bibliotekę DLL, uniemożliwiając jej zwolnienie. W wielu przypadkach nie ma to znaczenia, ale niektóre składniki systemu muszą obsługiwać wyładunek.

W tym miejscu pojawia się termin jednolita konstrukcja. Niezależnie od tego, czy kod tworzenia znajduje się w projekcie korzystającym tylko z klasy, czy też znajduje się w projekcie, który rzeczywiście implementowaniu klasie, możesz swobodnie użyć tej samej składni, aby utworzyć obiekt.

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

Podczas kompilowania projektu składnika za pomocą przełącznika -opt[imize], wywołanie przez projekcję językową jest optymalizowane do tego samego wydajnego wywołania funkcji winrt::make, która bezpośrednio tworzy typ implementacji. Dzięki temu składnia jest prosta i przewidywalna, pozwala uniknąć jakiejkolwiek utraty wydajności związanej z wywołaniem przez fabrykę oraz unika unieruchomienia komponentu w procesie. Oprócz projektów składników jest to również przydatne w przypadku aplikacji XAML. Omijając RoGetActivationFactory dla klas zaimplementowanych w tej samej aplikacji, można je konstruować (bez potrzeby rejestracji) w taki sam sposób, jak gdyby znajdowały się poza tym składnikiem.

Jednolita konstrukcja ma zastosowanie do każdego wywołania, które jest obsługiwane przez fabrykę pod maską. Praktycznie oznacza to, że optymalizacja dotyczy zarówno konstruktorów, jak i statycznych członków. Oto ten oryginalny przykład ponownie.

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

Bez -opt[imize], pierwsze i ostatnie instrukcje wymagają wywołań poprzez obiekt fabryki. With-opt[imize], żaden z nich tego nie robi. Te wywołania są kompilowane bezpośrednio zgodnie z implementacją, a nawet mogą być wstawione. Co odnosi się do innego terminu często używanego podczas rozmowy o -opt[imize], a mianowicie o bezpośredniej implementacji dostępu.

Projekcje językowe są wygodne, ale gdy można bezpośrednio uzyskać dostęp do implementacji, możesz i należy wykorzystać je w celu uzyskania najbardziej wydajnego kodu. C++/WinRT może to zrobić dla Ciebie, nie zmuszając cię do opuszczenia bezpieczeństwa i produktywności projekcji.

To jest fundamentalna zmiana, ponieważ komponent musi współpracować, aby umożliwić projekcji językowej dostęp do wewnętrznych typów implementacyjnych i ich bezpośrednie użycie. Ponieważ C++/WinRT jest biblioteką składającą się wyłącznie z nagłówków, możesz sprawdzić jej wnętrze i zobaczyć, co się dzieje. Bez -opt[imize]konstruktor MyClass i element StaticMethod są definiowane przez projekcję w następujący sposób.

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

Nie trzeba przestrzegać wszystkich powyższych zasad; celem jest pokazanie, że oba wywołania polegają na wywołaniu funkcji o nazwie call_factory. To twoja wskazówka, że te wywołania obejmują fabryczną pamięć podręczną i nie uzyskują bezpośredniego dostępu do implementacji. Z-opt[imize]te same funkcje nie są w ogóle zdefiniowane. Zamiast tego są deklarowane przez projekcję, a ich definicje pozostają do składnika.

Składnik może następnie podać definicje, które są wywoływane bezpośrednio w implementacji. Teraz dotarliśmy do zmiany powodującej niezgodność. Te definicje są generowane w przypadku używania zarówno -component, jak i -opt[imize], i są one wyświetlane w pliku o nazwie Type.g.cpp, gdzie Type jest nazwą implementowanej klasy środowiska uruchomieniowego. Dlatego przy pierwszym włączeniu -opt[imize] w istniejącym projekcie można napotkać różne błędy linkera. Musisz dołączyć wygenerowany plik do swojej implementacji, aby połączyć elementy.

W naszym przykładzie MyClass.h może wyglądać następująco (niezależnie od tego, czy używa się -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>
    {
    };
}

Twój MyClass.cpp to miejsce, gdzie wszystko się jednoczy.

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

Aby zastosować spójną konstrukcję w istniejącym projekcie, należy zedytować plik .cpp w każdej implementacji, aby wykonać #include <Sub/Namespace/Type.g.cpp> po dołączeniu (i definicji) klasy implementacji. Ten plik zawiera definicje tych funkcji, które projekcja pozostawiła niezdefiniowana. Oto, jak te definicje wyglądają wewnątrz pliku MyClass.g.cpp.

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

To ładnie dopełnia projekcję poprzez wydajne wywołania bezpośrednio do implementacji, unikając wywołań do pamięci podręcznej fabryki i zadowalając konsolidator.

Ostatnią rzeczą, która -opt[imize] jest zmiana implementacji module.g.cpp projektu (plik, który pomaga zaimplementować biblioteki DLL DllGetActivationFactory i DllCanUnloadNow eksportów) w taki sposób, że kompilacje przyrostowe będą zwykle znacznie szybsze dzięki wyeliminowaniu silnego sprzężenia typu wymaganego przez C++/WinRT 1.0. Jest to często nazywane fabrykami z wymazanym typem. Bez -opt[imize]plik module.g.cpp wygenerowany dla składnika rozpoczyna się od definiowania wszystkich klas implementacji — MyClass.hw tym przykładzie. Następnie tworzy fabrykę implementacji dla każdej klasy w następujący sposób.

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

Ponownie nie musisz obserwować wszystkich szczegółów. Warto zobaczyć, że wymaga to pełnej definicji wszystkich klas zaimplementowanych przez składnik. Może to mieć dramatyczny wpływ na pętlę wewnętrzną, ponieważ każda zmiana w pojedynczej implementacji spowoduje ponowne skompilowanie module.g.cpp. W przypadku -opt[imize]nie jest to już tak. Zamiast tego dwie rzeczy stają się z wygenerowanym plikiem module.g.cpp. Po pierwsze, nie zawiera już żadnych klas implementacji. W tym przykładzie nie będzie on w ogóle zawierać MyClass.h. Zamiast tego tworzy fabryki implementacji bez znajomości ich implementacji.

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

Oczywiście nie ma potrzeby dołączania ich definicji, a to do linkera należy rozwiązanie definicji funkcji winrt_make_Component_Class. Oczywiście nie musisz o tym myśleć, ponieważ plik MyClass.g.cpp, który zostanie wygenerowany dla Ciebie (i który wcześniej został uwzględniony w celu obsługi jednolitej konstrukcji), definiuje również tę funkcję. Oto całość pliku MyClass.g.cpp, który jest generowany na potrzeby tego przykładu.

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 widać, funkcja winrt_make_MyProject_MyClass bezpośrednio tworzy fabrykę Twojej implementacji. Oznacza to, że można swobodnie zmienić daną implementację, a module.g.cpp nie trzeba w ogóle ponownie kompilować. Tylko wtedy, gdy dodasz lub usuniesz klasy Windows Runtime, module.g.cpp zostanie zaktualizowane i będzie wymagało ponownej kompilacji.

Nadpisywanie metod wirtualnych klasy bazowej

Klasa pochodna może mieć problemy z metodami wirtualnymi, jeśli zarówno klasa bazowa, jak i klasa pochodna są klasami zdefiniowanymi przez aplikację, ale metoda wirtualna jest zdefiniowana w klasie nadrzędnej Środowiska uruchomieniowego systemu Windows. W praktyce dzieje się to wtedy, gdy dziedziczysz z klas XAML. Pozostała część tej sekcji kontynuuje przykład z klas pochodnych.

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

Hierarchia jest windows::UI::Xaml::Controls::P age<— BasePage<— DerivedPage. Metoda BasePage::OnNavigatedFrom poprawnie zastępuje metodę Page::OnNavigatedFrom, ale DerivedPage::OnNavigatedFrom nie zastępuje BasePage::OnNavigatedFrom.

W tym miejscu DerivedPage ponownie używa IPageOverrides vtable z BasePage, co oznacza, że nie można zastąpić metody IPageOverrides::OnNavigatedFrom. Jedno z potencjalnych rozwiązań wymaga, aby BasePage sama była klasą szablonu i miała swoją implementację w całości w pliku nagłówka, ale to nadmiernie komplikuje sytuację.

Aby obejść ten problem, zadeklaruj metodę OnNavigatedFrom jako jawnie wirtualną w klasie bazowej. W ten sposób, gdy wpis vtable dla DerivedPage::IPageOverrides::OnNavigatedFrom wywołuje BasePage::IPageOverrides::OnNavigatedFrom, producent wywołuje BasePage::OnNavigatedFrom, co (ze względu na jego wirtualność) kończy się wywoływanie 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);
    };
}

Wymaga to, aby wszystkie elementy członkowskie hierarchii klas zgadzały się na zwracaną wartość i typy parametrów metody OnNavigatedFrom. Jeśli nie są zgodne, należy użyć powyższej wersji jako metody wirtualnej i zamknąć alternatywne wersje.

Uwaga / Notatka

IDL nie musi deklarować zastąpionej metody. Aby uzyskać więcej informacji, zobacz sekcję Implementowanie metod zastępowalnych.

Ważne interfejsy API