Membuat API dengan C++/WinRT

Topik ini menunjukkan cara menulis API C++/WinRT dengan menggunakan struktur dasar winrt::implements , baik secara langsung maupun tidak langsung. Sinonim untuk penulis dalam konteks ini dihasilkan, atau diterapkan. Topik ini mencakup skenario berikut untuk menerapkan API pada jenis C++/WinRT, dalam urutan ini.

Note

Topik ini menyentuh subjek komponen Windows Runtime, tetapi hanya dalam konteks C++/WinRT. Jika Anda mencari konten tentang komponen Windows Runtime yang mencakup semua bahasa Windows Runtime, lihat komponen Windows Runtime.

  • Anda tidak membuat kelas Windows Runtime (runtime class); Anda hanya ingin mengimplementasikan satu atau beberapa antarmuka Windows Runtime untuk penggunaan lokal di dalam aplikasi Anda. Dalam kasus ini, Anda diturunkan langsung dari winrt::implements dan mengimplementasikan fungsi.
  • Anda menulis kelas runtime. Anda mungkin membuat komponen untuk digunakan dalam aplikasi. Atau Anda mungkin menulis jenis yang akan digunakan dari antarmuka pengguna (UI) XAML, dan dalam hal ini Anda menerapkan dan menggunakan kelas runtime dalam unit kompilasi yang sama. Dalam kasus ini, Anda membiarkan alat menghasilkan kelas untuk Anda yang berasal dari winrt::implements.

Dalam kedua kasus, jenis yang mengimplementasikan API C++/WinRT Anda disebut jenis implementasi.

Important

Penting untuk membedakan konsep jenis implementasi dari jenis yang diproyeksikan. Tipe terproyeksi dijelaskan dalam Menggunakan API dengan C++/WinRT.

Jika Anda tidak membuat kelas runtime

Skenario paling sederhana adalah ketika tipe Anda mengimplementasikan antarmuka Windows Runtime, dan Anda akan menggunakan tipe tersebut di aplikasi yang sama. Jika demikian, tipe Anda tidak perlu berupa kelas runtime; cukup kelas C++ biasa. Misalnya, Anda mungkin menulis aplikasi desktop WinUI 3 berdasarkan Microsoft::UI::Xaml::Application.

Jika tipe Anda direferensikan oleh UI XAML, maka tipe tersebut memang harus merupakan kelas runtime, meskipun berada di proyek yang sama dengan XAML. Untuk kasus ini, lihat bagian Jika Anda menulis kelas runtime untuk direferensikan di antarmuka pengguna XAML Anda.

Note

Untuk informasi tentang menginstal dan menggunakan C++/WinRT Visual Studio Extension (VSIX) dan paket NuGet (yang bersama-sama menyediakan templat proyek dan dukungan build), lihat dukungan Visual Studio untuk C++/WinRT.

Dalam Visual Studio, templat proyek Aplikasi Kosong, Dikemas (WinUI 3 di Desktop) untuk C++ mengilustrasikan pola aplikasi WinUI 3. Kelas Aplikasi berasal dari Microsoft::UI::Xaml::Application, dan titik masuk memanggil metode Mulai.

#include "App.xaml.h"

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

Kelas Aplikasi membuat Microsoft::UI::Xaml::Window dan mengaktifkannya di OnLaunched.

// App.xaml.h
struct App : AppT<App>
{
    App();
    void OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&);

private:
    winrt::Microsoft::UI::Xaml::Window window{ nullptr };
};

// App.xaml.cpp
void App::OnLaunched(LaunchActivatedEventArgs const&)
{
    window = make<MainWindow>();
    window.Activate();
}

C++/WinRT memiliki templat struct dasar winrt::implements untuk memudahkan penerapan antarmuka (atau beberapa) tanpa menggunakan pemrograman gaya COM. Anda cukup menurunkan tipe Anda dari implements, lalu mengimplementasikan fungsi-fungsi antarmuka tersebut. Berikut adalah contoh yang mengimplementasikan antarmuka kustom.

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

Jika Anda menulis kelas runtime dalam komponen Windows Runtime

Jika tipe Anda dikemas dalam komponen Windows Runtime untuk digunakan oleh biner lain (biner lain tersebut biasanya berupa aplikasi), maka tipe Anda harus berupa kelas runtime. Anda mendeklarasikan kelas runtime dalam file Microsoft Interface Definition Language (IDL) (.idl) (lihat Memfaktorkan kelas runtime ke dalam file Midl (.idl)).

Setiap file IDL menghasilkan file .winmd, dan Visual Studio menggabungkan semuanya ke dalam satu file dengan nama yang sama seperti namespace akar Anda. File akhir .winmd tersebut akan menjadi salah satu yang akan dirujuk konsumen komponen Anda.

Berikut adalah contoh mendeklarasikan kelas runtime dalam file 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;
    }
}

IDL ini mendeklarasikan sebuah kelas Windows Runtime (runtime). Kelas runtime adalah sebuah jenis yang dapat diaktifkan dan digunakan melalui antarmuka COM modern, biasanya lintas batas antareksekusi. Saat Anda menambahkan file IDL ke proyek Anda, dan membangun, toolchain C++/WinRT (midl.exe dan cppwinrt.exe) menghasilkan jenis implementasi untuk Anda. Untuk contoh alur kerja file IDL yang sedang beraksi, lihat kontrol XAML; ikat ke properti C++/WinRT.

Menggunakan contoh IDL di atas, jenis implementasi adalah stub struct C++ bernama winrt::MyProject::implementation::MyRuntimeClass dalam file kode sumber bernama \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h dan MyRuntimeClass.cpp.

Jenis implementasi terlihat seperti ini.

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

Perhatikan bahwa pola polimorfisme terikat F yang digunakan (MyRuntimeClass menggunakan dirinya sebagai argumen templat ke basisnya, MyRuntimeClassT). Ini juga disebut pola templat berulang yang aneh (CRTP). Jika Anda mengikuti rantai warisan ke atas, Anda akan menemukan MyRuntimeClass_base.

Anda dapat menyederhanakan implementasi properti sederhana dengan menggunakan Windows Implementation Libraries (WIL). Berikut caranya:

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

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

Lihat Properti sederhana.

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

Jadi, dalam skenario ini, di akar hierarki pewarisan, sekali lagi, terdapat template struct dasar winrt::implements.

Untuk detail lebih lanjut, kode, dan panduan langkah demi langkah untuk menulis API dalam komponen Windows Runtime, lihat Komponen Windows Runtime dengan C++/WinRT dan Membuat event di C++/WinRT.

Jika Anda menulis kelas runtime yang akan direferensikan di antarmuka pengguna XAML Anda

Jika tipe Anda direferensikan oleh UI XAML Anda, maka tipe tersebut harus berupa kelas runtime, meskipun berada di proyek yang sama dengan XAML tersebut. Meskipun biasanya diaktifkan di seluruh batas yang dapat dieksekusi, kelas runtime dapat digunakan dalam unit kompilasi yang mengimplementasikannya.

Dalam skenario ini, Anda menulis dan menggunakan API. Prosedur untuk menerapkan kelas runtime Anda pada dasarnya sama dengan untuk komponen Windows Runtime. Jadi, lihat bagian sebelumnya—Jika Anda menulis kelas runtime dalam komponen Windows Runtime. Satu-satunya detail yang berbeda adalah bahwa, dari IDL, toolchain C++/WinRT tidak hanya menghasilkan jenis implementasi tetapi juga jenis yang diproyeksikan. Penting untuk dipahami bahwa hanya menyebut "MyRuntimeClass" dalam skenario ini bisa menimbulkan ambiguitas; karena ada beberapa entitas berbeda dengan nama tersebut, dari jenis yang berbeda-beda.

  • MyRuntimeClass adalah nama kelas runtime. Tetapi ini benar-benar abstraksi: dideklarasikan dalam IDL, dan diimplementasikan dalam beberapa bahasa pemrograman.
  • MyRuntimeClass adalah nama C++ struct winrt::MyProject::implementation::MyRuntimeClass, yang merupakan implementasi C++/WinRT dari kelas runtime. Seperti yang telah kita lihat, jika ada proyek penerapan dan penggunaan terpisah, maka struktur ini hanya ada dalam proyek pelaksana. Ini adalah jenis implementasi, atau implementasinya. Jenis ini dihasilkan (oleh cppwinrt.exe alat) dalam file \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h dan MyRuntimeClass.cpp.
  • MyRuntimeClass adalah nama jenis yang diproyeksikan dalam bentuk C++ struct winrt::MyProject::MyRuntimeClass. Jika ada proyek penerapan dan penggunaan terpisah, maka struktur ini hanya ada dalam proyek yang mengonsumsi. Ini adalah jenis yang diproyeksikan, atau proyeksi. Jenis ini dihasilkan (oleh cppwinrt.exe) dalam file \MyProject\MyProject\Generated Files\winrt\impl\MyProject.2.h.

Jenis dan jenis implementasi yang diproyeksikan

Berikut adalah bagian dari jenis yang diproyeksikan yang relevan dengan topik ini.

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

Untuk contoh panduan penerapan antarmuka INotifyPropertyChanged pada kelas runtime, lihat kontrol XAML; mengikat properti C++/WinRT.

Prosedur untuk menggunakan kelas runtime Anda dalam skenario ini dijelaskan dalam Menggunakan API dengan C++/WinRT.

Memisahkan kelas runtime ke dalam file MIDL (.idl)

Templat proyek dan item Visual Studio menghasilkan file IDL terpisah untuk setiap kelas runtime. Itu memberikan korespondensi logis antara file IDL dan file kode sumber yang dihasilkan.

Namun, jika Anda mengonsolidasikan semua kelas runtime proyek Anda ke dalam satu file IDL, maka itu dapat secara signifikan meningkatkan waktu build. Jika Anda sebaliknya memiliki dependensi yang kompleks (atau melingkar) import di antara mereka, maka konsolidasi mungkin benar-benar diperlukan. Dan Anda mungkin akan merasa lebih mudah untuk membuat dan meninjau kelas runtime Anda jika kelas-kelas tersebut berada di satu tempat.

Konstruktor kelas runtime

Berikut adalah beberapa poin untuk diambil dari daftar yang telah kita lihat di atas.

  • Setiap konstruktor yang Anda deklarasikan dalam IDL Anda akan menghasilkan konstruktor pada tipe implementasi Anda dan tipe terproyeksi Anda masing-masing. Konstruktor yang dideklarasikan IDL digunakan untuk mengonsumsi kelas runtime dari unit kompilasi yang berbeda .
  • Baik Anda memiliki konstruktor yang dideklarasikan dalam IDL maupun tidak, overload konstruktor yang menerima std::nullptr_t akan dihasilkan pada tipe proyeksi Anda. Memanggil konstruktor std::nullptr_t adalah langkah pertama dari dua langkah dalam mengonsumsi kelas runtime dari unit kompilasi yang sama . Untuk detail selengkapnya, dan contoh kode, lihat Menggunakan API dengan C++/WinRT.
  • Jika Anda menggunakan kelas runtime dari unit kompilasi yang sama , maka Anda juga dapat menerapkan konstruktor non-default langsung pada jenis implementasi (yang, ingat, ada di MyRuntimeClass.h).

Note

Jika Anda mengharapkan kelas runtime Anda dikonsumsi dari unit kompilasi yang berbeda (yang umum), maka sertakan konstruktor di IDL Anda (setidaknya konstruktor default). Dengan melakukan itu, Anda juga akan mendapatkan implementasi pabrik bersama jenis implementasi Anda.

Jika Anda ingin menulis dan menggunakan kelas runtime Anda hanya dalam unit kompilasi yang sama, maka jangan mendeklarasikan konstruktor apa pun di IDL Anda. Anda tidak memerlukan implementasi factory, dan implementasi tersebut juga tidak akan dibuat. Konstruktor default jenis implementasi Anda akan dihapus, tetapi Anda dapat dengan mudah mengeditnya dan default sebagai gantinya.

Jika Anda ingin menulis dan menggunakan kelas runtime Anda hanya dalam unit kompilasi yang sama, dan Anda memerlukan parameter konstruktor, maka tulis konstruktor yang Anda butuhkan langsung pada jenis implementasi Anda.

Metode, properti, dan peristiwa kelas runtime

Kita telah melihat bahwa alur kerjanya adalah menggunakan IDL untuk mendeklarasikan kelas runtime Anda beserta anggotanya, lalu perangkat bantu menghasilkan prototipe dan implementasi stub untuk Anda. Untuk prototipe yang dibuat secara otomatis untuk anggota kelas runtime Anda, Anda dapat mengeditnya agar meneruskan tipe yang berbeda dari yang Anda deklarasikan dalam IDL Anda. Tetapi Anda dapat melakukannya hanya selama jenis yang Anda nyatakan di IDL dapat diteruskan ke jenis yang Anda nyatakan dalam versi yang diimplementasikan.

Berikut adalah beberapa contoh.

  • Anda dapat melonggarkan jenis parameter. Misalnya, jika dalam IDL metode Anda mengambil SomeClass, maka Anda dapat memilih untuk mengubahnya menjadi IInspectable dalam implementasi Anda. Ini berfungsi karena SomeClass apa pun dapat diteruskan ke IInspectable (sebaliknya, tentu saja, tidak akan berfungsi).
  • Anda dapat menerima parameter yang dapat disalin berdasarkan nilai, bukan berdasarkan referensi. Misalnya, ubah SomeClass const& menjadi SomeClass. Itu diperlukan saat Anda perlu menghindari menyertakan referensi di dalam coroutine (lihat Parameter-passing).
  • Anda dapat melonggarkan nilai pengembalian. Misalnya, Anda dapat mengubah void menjadi winrt::fire_and_forget.

Dua terakhir sangat berguna ketika Anda menulis penanganan aktivitas asinkron.

Menginstansiasi dan mengembalikan tipe implementasi dan antarmuka

Untuk bagian ini, mari kita ambil sebagai contoh jenis implementasi bernama MyType, yang mengimplementasikan antarmuka IStringable dan IClosable .

Anda dapat memperoleh MyType langsung dari winrt::implements (ini bukan kelas runtime).

#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;

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

Atau Anda dapat menghasilkannya dari IDL (ini adalah kelas runtime).

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

Anda tidak dapat langsung mengalokasikan jenis implementasi Anda.

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

Tetapi Anda dapat pergi dari MyType ke objek IStringable atau IClosable yang dapat Anda gunakan atau kembalikan sebagai bagian dari proyeksi Anda dengan memanggil templat fungsi winrt::make . membuat mengembalikan antarmuka default jenis implementasi.

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

Note

Namun, jika Anda merujuk jenis Anda dari UI XAML Anda, maka akan ada jenis implementasi dan jenis yang diproyeksikan dalam proyek yang sama. Dalam kasus tersebut, make mengembalikan instance dari tipe hasil proyeksi. Untuk contoh kode skenario tersebut, lihat kontrol XAML; ikat ke properti C++/WinRT.

Kita dapat menggunakan istringable (dalam contoh kode di atas) hanya untuk memanggil anggota antarmuka IStringable . Tetapi antarmuka C++/WinRT (yang merupakan antarmuka yang diproyeksikan) berasal dari winrt::Windows::Foundation::IUnknown. Jadi, Anda dapat memanggil IUnknown::as (atau IUnknown::try_as) di dalamnya untuk meminta jenis atau antarmuka lain yang diproyeksikan, yang juga dapat Anda gunakan atau kembalikan.

Tip

Skenario di mana Anda tidak boleh memanggil as atau try_as adalah penurunan kelas saat runtime ("kelas yang dapat dikomposisikan"). Saat tipe implementasi mengomposisikan kelas lain, jangan panggil as atau try_as untuk melakukan QueryInterface yang diperiksa atau tidak diperiksa pada kelas yang sedang dikomposisikan. Sebaliknya, akses anggota data (this->) m_inner, lalu panggil as atau try_as padanya. Untuk informasi selengkapnya, lihat Turunan kelas Runtime dalam topik ini.

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

Jika Anda perlu mengakses semua anggota implementasi, dan kemudian mengembalikan antarmuka ke pemanggil, maka gunakan templat fungsi winrt::make_self . make_self mengembalikan winrt::com_ptr yang membungkus jenis implementasi. Anda dapat mengakses anggota semua antarmukanya (menggunakan operator panah), Anda dapat mengembalikannya ke pemanggil as-is, atau Anda dapat memanggil seperti di atasnya dan mengembalikan objek antarmuka yang dihasilkan ke pemanggil.

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

Kelas MyType bukan bagian dari proyeksi; ini adalah implementasinya. Namun, dengan cara ini Anda dapat memanggil metode implementasinya secara langsung, tanpa overhead dari pemanggilan fungsi virtual. Dalam contoh di atas, meskipun MyType::ToString menggunakan tanda tangan yang sama dengan metode yang diproyeksikan pada IStringable, kami memanggil metode non-virtual secara langsung, tanpa melewati antarmuka biner aplikasi (ABI). com_ptr hanya memegang pointer ke struktur MyType, sehingga Anda juga dapat mengakses detail internal MyType lainnya melalui myimpl variabel dan operator panah.

Dalam kasus di mana Anda memiliki objek antarmuka, dan Anda kebetulan tahu bahwa itu adalah antarmuka pada implementasi Anda, maka Anda dapat kembali ke implementasi menggunakan templat fungsi winrt::get_self . Sekali lagi, ini merupakan teknik yang menghindari panggilan fungsi virtual dan memungkinkan Anda langsung mengakses implementasinya.

Note

Jika Anda belum menginstal SDK Windows versi 10.0.17763.0 (Windows 10, versi 1809), atau yang lebih baru, maka Anda perlu memanggil winrt::from_abi alih-alih winrt::get_self.

Berikut ini contohnya. Ada contoh lain dalam Menerapkan kelas kontrol kustom BgLabelControl.

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

Tetapi hanya objek antarmuka asli yang memegang referensi. Jika Anda ingin menyimpannya, Anda dapat memanggil 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.

Tipe implementasinya sendiri tidak diturunkan dari winrt::Windows::Foundation::IUnknown, sehingga tidak memiliki fungsi as. Meskipun demikian, seperti yang Anda lihat dalam fungsi ImplFromIClosable di atas, Anda dapat mengakses anggota semua antarmukanya. Tetapi jika Anda melakukan itu, maka jangan mengembalikan instans jenis implementasi mentah ke pemanggil. Sebagai gantinya, gunakan salah satu teknik yang telah ditunjukkan, dan mengembalikan antarmuka terproyeksi, atau com_ptr.

Jika Anda memiliki instans jenis implementasi Anda, dan Anda perlu meneruskannya ke fungsi yang mengharapkan jenis yang diproyeksikan yang sesuai, maka Anda dapat melakukannya, seperti yang ditunjukkan pada contoh kode di bawah ini. Operator konversi ada pada jenis implementasi Anda (asalkan jenis implementasi dihasilkan oleh cppwinrt.exe alat) yang memungkinkan hal ini. Anda dapat meneruskan nilai jenis implementasi langsung ke metode yang mengharapkan nilai dari jenis proyeksi yang sesuai. Dari fungsi anggota tipe implementasi, Anda dapat memberikan *this ke metode yang mengharapkan nilai dari tipe proyeksi yang sesuai.

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

Derivasi kelas runtime

Anda dapat membuat kelas runtime yang berasal dari kelas runtime lain, asalkan kelas dasar dinyatakan sebagai "tidak tersegel". Istilah Windows Runtime untuk turunan kelas adalah "kelas yang dapat disusun". Kode untuk menerapkan kelas turunan tergantung pada apakah kelas dasar disediakan oleh komponen lain atau oleh komponen yang sama. Untungnya, Anda tidak perlu mempelajari aturan ini—Anda cukup menyalin implementasi sampel dari sources folder output yang dihasilkan oleh cppwinrt.exe pengkompilasi.

Pertimbangkan contoh ini.

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

Dalam contoh di atas, MyButton berasal dari kontrol Tombol XAML, yang disediakan oleh komponen lain. Dalam hal ini, implementasinya terlihat seperti implementasi kelas yang tidak dapat disusun:

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

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

Di sisi lain, dalam contoh di atas, MyDerived berasal dari kelas lain dalam komponen yang sama. Dalam hal ini, implementasi memerlukan parameter templat tambahan yang menentukan kelas implementasi untuk kelas dasar.

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

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

Dalam kedua kasus, implementasi Anda dapat memanggil metode dari kelas dasar dengan mengawalinya dengan alias tipe base_type:

namespace winrt::MyProject::implementation
{
    struct MyButton : MyButtonT<MyButton>
    {
        void OnApplyTemplate()
        {
            // Call base class method
            base_type::OnApplyTemplate();

            // Do more work after the base class method is done
            DoAdditionalWork();
        }
    };

    struct MyDerived : MyDerivedT<MyDerived, implementation::MyBase>
    {
        int MethodOverride()
        {
            // Return double what the base class returns
            return 2 * base_type::MethodOverride();
        }
    };
}

Tip

Saat tipe implementasi mengomposisikan kelas lain, jangan panggil as atau try_as untuk melakukan QueryInterface yang diperiksa atau tidak diperiksa pada kelas yang sedang dikomposisikan. Sebaliknya, akses anggota data (this->) m_inner, lalu panggil as atau try_as padanya.

Berasal dari jenis yang memiliki konstruktor non-default

ToggleButtonAutomationPeer::ToggleButtonAutomationPeer(ToggleButton) adalah contoh konstruktor non-default. Tidak ada konstruktor default, jadi untuk membuat ToggleButtonAutomationPeer, Anda perlu memberikan owner. Akibatnya, jika Anda mewarisi ToggleButtonAutomationPeer, Anda perlu menyediakan konstruktor yang menerima owner dan meneruskannya ke kelas dasar. Mari kita lihat seperti apa yang terlihat dalam praktik.

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

Konstruktor yang dihasilkan untuk jenis implementasi Anda terlihat seperti ini.

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

Satu-satunya bagian yang hilang adalah Anda perlu meneruskan parameter konstruktor tersebut ke kelas dasar. Ingat pola polimorfisme terikat F yang kami sebutkan di atas? Setelah Anda terbiasa dengan detail pola tersebut seperti yang digunakan oleh C++/WinRT, Anda dapat mencari tahu apa kelas dasar Anda dipanggil (atau Anda hanya dapat melihat dalam file header kelas implementasi Anda). Ini adalah cara memanggil konstruktor kelas dasar dalam kasus ini.

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

Konstruktor kelas dasar mengharapkan ToggleButton. Dan MySpecializedToggleButtonadalahToggleButton.

Hingga Anda membuat pengeditan yang dijelaskan di atas (untuk meneruskan parameter konstruktor tersebut ke kelas dasar), pengkompilasi akan menandai konstruktor Anda dan menunjukkan bahwa tidak ada konstruktor default yang sesuai yang tersedia pada jenis yang disebut (dalam hal ini) MySpecializedToggleButtonAutomationPeer_base<MySpecializedToggleButtonAutomationPeer>. Itu sebenarnya kelas dasar dari kelas bass pada tipe implementasi Anda.

Namespace: jenis proyeksi, jenis implementasi, dan pabrik

Seperti yang telah Anda lihat sebelumnya dalam topik ini, kelas runtime C++/WinRT hadir dalam bentuk lebih dari satu kelas C++ di lebih dari satu namespace. Jadi, nama MyRuntimeClass memiliki satu arti dalam namespace winrt::MyProject , dan arti yang berbeda dalam namespace winrt::MyProject::implementation . Perhatikan namespace mana yang saat ini aktif dalam konteks, lalu gunakan awalan namespace jika Anda memerlukan nama dari namespace yang berbeda. Mari kita lihat lebih dekat namespace yang dimaksud.

  • winrt::MyProject. Namespace ini berisi tipe yang diproyeksikan. Objek dari jenis yang diproyeksikan adalah proksi; ini pada dasarnya adalah penunjuk cerdas ke objek cadangan, di mana objek backing tersebut mungkin diimplementasikan di sini dalam proyek Anda, atau mungkin diimplementasikan di unit kompilasi lain.
  • winrt::MyProject::implementation. Namespace ini berisi jenis implementasi. Objek jenis implementasi bukan penunjuk; ini adalah nilai—objek tumpukan C++ lengkap. Jangan membangun jenis implementasi secara langsung; sebagai gantinya, panggil winrt::make, meneruskan jenis implementasi Anda sebagai parameter templat. Sebelumnya dalam topik ini, kami telah menunjukkan contoh penggunaan winrt::make, dan ada contoh lain dalam Kontrol XAML; melakukan pengikatan ke properti C++/WinRT. Lihat juga Mendiagnosis alokasi langsung.
  • winrt::MyProject::factory_implementation. Namespace ini berisi pabrik. Objek di namespace ini mendukung IActivationFactory.

Tabel ini memperlihatkan kualifikasi namespace minimum yang perlu Anda gunakan dalam konteks yang berbeda.

Namespace yang dalam konteks Untuk menentukan jenis yang diproyeksikan Untuk menentukan jenis implementasi
winrt::MyProject MyRuntimeClass implementation::MyRuntimeClass
winrt::MyProject::implementation MyProject::MyRuntimeClass MyRuntimeClass

Important

Saat Anda ingin mengembalikan tipe yang diproyeksikan dalam implementasi Anda, berhati-hatilah agar tidak membuat instance tipe implementasi dengan menulis MyRuntimeClass myRuntimeClass;. Teknik dan kode yang benar untuk skenario tersebut ditampilkan sebelumnya dalam topik ini di bagian Membuat instans dan mengembalikan jenis dan antarmuka implementasi.

Masalahnya dengan MyRuntimeClass myRuntimeClass; dalam skenario tersebut adalah membuat objek winrt::MyProject::implementation::MyRuntimeClass pada tumpukan. Objek itu (dari jenis implementasi) berperilaku seperti jenis yang diproyeksikan dalam beberapa cara—Anda dapat memanggil metode di atasnya dengan cara yang sama; dan bahkan mengonversi ke jenis yang diproyeksikan. Tetapi objek dihancurkan, sesuai aturan C++ pada umumnya, saat cakupan berakhir. Jadi, jika Anda mengembalikan tipe proyeksi (smart pointer) ke objek tersebut, pointer itu kini menjadi dangling pointer.

Jenis bug kerusakan memori ini sulit didiagnosis. Jadi, untuk build debug, asersi C++/WinRT membantu Anda mendeteksi kesalahan ini dengan menggunakan pendeteksi stack. Tetapi coroutine dialokasikan di heap, sehingga Anda tidak akan mendapat bantuan terkait kesalahan ini jika Anda melakukannya di dalam coroutine. Untuk informasi selengkapnya, lihat Mendiagnosis alokasi langsung.

Menggunakan jenis dan jenis implementasi yang diproyeksikan dengan berbagai fitur C++/WinRT

Berikut adalah berbagai konteks di mana fitur C++/WinRT mengharapkan suatu tipe, serta tipe seperti apa yang diharapkannya (tipe terproyeksi, tipe implementasi, atau keduanya).

Feature Menerima Notes
T (mewakili penunjuk cerdas) Diproyeksikan Lihat peringatan di Namespaces: tipe yang diproyeksikan, tipe implementasi, dan factory tentang kekeliruan penggunaan tipe implementasi.
agile_ref<T> Both Jika Anda menggunakan jenis implementasi, maka argumen konstruktor harus com_ptr<T>.
com_ptr<T> Implementation Menggunakan jenis yang diproyeksikan menghasilkan kesalahan: 'Release' is not a member of 'T'.
default_interface<T> Both Jika Anda menggunakan jenis implementasi, maka antarmuka pertama yang diimplementasikan dikembalikan.
get_self<T> Implementation Menggunakan jenis yang diproyeksikan menghasilkan kesalahan: '_abi_TrustLevel': is not a member of 'T'.
guid_of<T>() Both Mengembalikan GUID antarmuka default.
IWinRTTemplateInterface<T>
Diproyeksikan Menggunakan tipe implementasi memang dapat dikompilasi, tetapi itu adalah sebuah kesalahan—lihat peringatan di Namespaces: tipe terproyeksi, tipe implementasi, dan factory.
make<T> Implementation Menggunakan jenis yang diproyeksikan menghasilkan kesalahan: 'implements_type': is not a member of any direct or indirect base class of 'T'
make_agile(T const&amp;) Both Jika Anda menggunakan jenis implementasi, maka argumen harus com_ptr<T>.
make_self<T> Implementation Menggunakan jenis yang diproyeksikan menghasilkan kesalahan: 'Release': is not a member of any direct or indirect base class of 'T'
name_of<T> Diproyeksikan Jika Anda menggunakan tipe implementasi, Anda akan mendapatkan GUID dalam bentuk string dari antarmuka default.
weak_ref<T> Both Jika Anda menggunakan jenis implementasi, maka argumen konstruktor harus com_ptr<T>.

Pilih konstruksi yang seragam, dan akses langsung ke implementasi

Bagian ini menjelaskan fitur C++/WinRT 2.0 yang bersifat opsional, meskipun diaktifkan secara default untuk proyek baru. Untuk proyek yang sudah ada, Anda perlu mengaktifkannya dengan mengonfigurasi alat cppwinrt.exe. Di Visual Studio, atur properti proyek Properti> UmumC++/WinRT>Dioptimalkan ke Ya. Itu memiliki efek menambahkan <CppWinRTOptimized>true</CppWinRTOptimized> ke file proyek Anda. Dan memiliki efek yang sama seperti menambahkan sakelar saat memanggil cppwinrt.exe dari baris perintah.

Sakelar -opt[imize] memungkinkan apa yang sering disebut konstruksi seragam. Dengan konstruksi seragam (atau terpadu), Anda menggunakan proyeksi bahasa C++/WinRT itu sendiri untuk membuat dan menggunakan jenis implementasi Anda (jenis yang diimplementasikan oleh komponen Anda, untuk dikonsumsi oleh aplikasi) secara efisien dan tanpa kesulitan pemuat.

Sebelum menjelaskan fitur, mari kita pertama-tama tunjukkan situasi tanpa konstruksi seragam. Sebagai ilustrasi, kita akan mulai dengan contoh kelas Windows Runtime ini.

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

Sebagai pengembang C++ yang terbiasa menggunakan pustaka C++/WinRT, Anda mungkin ingin menggunakan kelas seperti ini.

using namespace winrt::MyProject;

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

Dan itu akan sangat masuk akal asalkan kode konsumsi yang ditampilkan tidak berada dalam komponen yang sama yang mengimplementasikan kelas ini. Sebagai proyeksi bahasa, C++/WinRT melindungi Anda sebagai pengembang dari ABI (antarmuka biner aplikasi berbasis COM yang ditentukan Windows Runtime). C++/WinRT tidak memanggil langsung ke implementasi; ia melakukan perjalanan melalui ABI.

Akibatnya, pada baris kode tempat Anda membuat objek MyClass (MyClass c;), proyeksi C++/WinRT memanggil RoGetActivationFactory untuk mengambil pabrik kelas atau aktivasi, lalu menggunakan pabrik tersebut untuk membuat objek. Baris terakhir juga menggunakan factory untuk menghasilkan sesuatu yang tampaknya berupa pemanggilan metode statis. Semua ini mengharuskan kelas Anda didaftarkan, dan modul Anda mengimplementasikan titik masuk DllGetActivationFactory . C++/WinRT memiliki cache pabrik yang sangat cepat, jadi tidak satu pun yang menyebabkan masalah bagi aplikasi yang mengonsumsi komponen Anda. Masalahnya adalah bahwa, dalam komponen Anda, Anda baru saja melakukan sesuatu yang sedikit bermasalah.

Pertama, tidak peduli seberapa cepat cache pabrik C++/WinRT, memanggil melalui RoGetActivationFactory (atau bahkan panggilan berikutnya melalui cache pabrik) akan selalu lebih lambat daripada memanggil langsung ke implementasi. Panggilan ke RoGetActivationFactory diikuti oleh IActivationFactory::ActivateInstance diikuti oleh QueryInterface jelas tidak akan seefisien menggunakan ekspresi C++ new untuk jenis yang ditentukan secara lokal. Sebagai akibatnya, pengembang C++/WinRT yang berpengalaman terbiasa menggunakan fungsi pembantu winrt::make atau winrt::make_self saat membuat objek dalam komponen.

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

Tapi, seperti yang anda lihat, itu tidak senyaman atau ringkas. Anda harus menggunakan fungsi pembantu untuk membuat objek, dan Anda juga harus membedakan antara jenis implementasi dan jenis yang diproyeksikan.

Kedua, menggunakan proyeksi tersebut untuk membuat kelas berarti bahwa factory aktivasinya akan disimpan dalam cache. Biasanya, itulah yang Anda inginkan, tetapi jika factory berada di modul (DLL) yang sama dengan yang membuat panggilan, maka secara efektif Anda telah mengunci DLL agar tetap termuat dan mencegah DLL tersebut untuk tidak pernah dibongkar dari memori. Dalam banyak kasus, itu tidak masalah; tetapi beberapa komponen sistem harus mendukung pembongkaran.

Di sinilah istilah konstruksi seragam datang. Terlepas dari apakah kode pembuatan berada dalam proyek yang hanya mengonsumsi kelas, atau apakah kode tersebut berada di proyek yang benar-benar mengimplementasikan kelas, Anda dapat dengan bebas menggunakan sintaks yang sama untuk membuat objek.

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

Saat Anda membangun proyek komponen dengan sakelar -opt[imize], pemanggilan melalui proyeksi bahasa akan dikompilasi menjadi pemanggilan efisien yang sama ke fungsi winrt::make yang secara langsung membuat tipe implementasi. Hal itu membuat sintaks Anda sederhana dan mudah diprediksi, menghindari penurunan kinerja akibat pemanggilan melalui factory, dan menghindari menyematkan komponen dalam prosesnya. Selain proyek komponen, ini juga berguna untuk aplikasi XAML. Melewatkan RoGetActivationFactory untuk kelas yang diimplementasikan dalam aplikasi yang sama memungkinkan Anda membuat instance kelas tersebut (tanpa perlu didaftarkan) dengan cara yang sama seperti jika kelas tersebut berada di luar komponen Anda.

Konstruksi seragam berlaku untuk panggilan apa pun yang dilayani oleh pabrik di bawah tenda. Praktis, itu berarti bahwa pengoptimalan melayani konstruktor dan anggota statis. Berikut contoh aslinya lagi.

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

Tanpa -opt[imize], pernyataan pertama dan terakhir memerlukan panggilan melalui objek pabrik. Dengan-opt[imize], tidak satu pun dari mereka lakukan. Dan panggilan tersebut dikompilasi langsung ke implementasinya, dan bahkan berpotensi untuk di-inline. Hal ini berkaitan dengan istilah lain yang sering digunakan ketika membahas -opt[imize], yaitu akses implementasi langsung.

Proyeksi bahasa nyaman tetapi, ketika Anda dapat langsung mengakses implementasi, Anda dapat dan harus memanfaatkannya untuk menghasilkan kode yang paling efisien. C++/WinRT dapat melakukannya untuk Anda, tanpa memaksa Anda untuk meninggalkan keamanan dan produktivitas proyeksi.

Ini adalah perubahan yang melanggar karena komponen harus bekerja sama agar proyeksi bahasa dapat menjangkau dan langsung mengakses jenis implementasinya. Karena C++/WinRT adalah pustaka header-only, Anda dapat melihat isinya dan memahami apa yang sedang terjadi. Tanpa -opt[imize], konstruktor MyClass , dan anggota StaticMethod , didefinisikan oleh proyeksi seperti ini.

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

Tidak perlu mengikuti semua hal di atas; niatnya adalah untuk menunjukkan bahwa kedua panggilan melibatkan panggilan ke fungsi bernama call_factory. Itulah petunjuk Anda bahwa panggilan ini melibatkan cache pabrik, dan mereka tidak secara langsung mengakses implementasi. Dengan-opt[imize], fungsi yang sama ini tidak didefinisikan sama sekali. Sebaliknya, mereka dideklarasikan oleh proyeksi, dan definisinya diserahkan ke komponen.

Komponen kemudian dapat memberikan definisi yang memanggil langsung ke dalam implementasi. Sekarang kita sampai pada perubahan yang merusak kompatibilitas. Definisi tersebut dihasilkan untuk Anda saat Anda menggunakan baik -component maupun -opt[imize], dan muncul di file bernama Type.g.cpp, di mana Type adalah nama kelas runtime yang sedang diimplementasikan. Itulah sebabnya Anda dapat mengalami berbagai kesalahan tautan saat pertama kali mengaktifkan -opt[imize] dalam proyek yang ada. Anda perlu memasukkan file yang dihasilkan tersebut ke dalam implementasi Anda agar semuanya terhubung dengan benar.

Dalam contoh kami, MyClass.h mungkin terlihat seperti ini (terlepas dari apakah -opt[imize] sedang digunakan).

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

MyClass.cpp Anda adalah tempat semuanya menyatu.

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

Jadi, untuk menggunakan konstruksi uniform dalam proyek yang sudah ada, Anda perlu mengedit file .cpp milik setiap implementasi agar Anda #include <Sub/Namespace/Type.g.cpp> setelah penyertaan (dan definisi) kelas implementasi. File tersebut menyediakan definisi fungsi-fungsi yang dibiarkan proyeksi tidak terdefinisi. Berikut tampilan definisi tersebut di dalam file MyClass.g.cpp.

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

Dan itu melengkapi proyeksi dengan baik dengan pemanggilan efisien langsung ke implementasi, menghindari pemanggilan ke cache factory, dan memenuhi kebutuhan linker.

Hal terakhir yang dilakukan -opt[imize] untuk Anda adalah mengubah implementasi module.g.cpp proyek Anda (file yang membantu Anda mengimplementasikan ekspor DllGetActivationFactory dan DllCanUnloadNow milik DLL Anda) sedemikian rupa sehingga build inkremental cenderung menjadi jauh lebih cepat dengan menghilangkan keterikatan tipe yang kuat yang diperlukan oleh C++/WinRT 1.0. Ini sering disebut sebagai pabrik yang dihapus jenis. Tanpa -opt[imize], module.g.cpp file yang dihasilkan untuk komponen Anda dimulai dengan menyertakan definisi semua kelas implementasi Anda— MyClass.h, dalam contoh ini. Kemudian secara langsung membuat pabrik implementasi untuk setiap kelas seperti ini.

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

Sekali lagi, Anda tidak perlu mengikuti semua detailnya. Yang berguna untuk dilihat adalah bahwa ini memerlukan definisi lengkap untuk setiap dan semua kelas yang diterapkan oleh komponen Anda. Ini dapat berdampak besar pada inner loop Anda, karena setiap perubahan pada satu implementasi saja akan menyebabkan module.g.cpp dikompilasi ulang. Dengan -opt[imize], ini tidak lagi terjadi. Sebaliknya, dua hal terjadi pada file yang dihasilkan module.g.cpp . Yang pertama adalah tidak lagi mencakup kelas implementasi apa pun. Dalam contoh ini, MyClass.h sama sekali tidak akan disertakan. Sebaliknya, ia menciptakan pabrik implementasi tanpa sepengetahuan implementasinya.

void* winrt_make_MyProject_MyClass();

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

Jelas, tidak perlu menyertakan definisi mereka, dan terserah linker untuk menyelesaikan definisi fungsi winrt_make_Component_Class . Tentu saja, Anda tidak perlu memikirkan hal ini, karena berkas MyClass.g.cpp yang dibuatkan untuk Anda (dan yang sebelumnya Anda sertakan agar mendukung konstruksi seragam) juga mendefinisikan fungsi ini. Berikut adalah keseluruhan MyClass.g.cpp file yang dihasilkan untuk contoh ini.

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

Seperti yang Anda lihat, fungsi winrt_make_MyProject_MyClass secara langsung membuat pabrik implementasi Anda. Semua ini berarti bahwa Anda dapat dengan leluasa mengubah implementasi apa pun, dan module.g.cpp sama sekali tidak perlu dikompilasi ulang. Hanya saat Anda menambahkan atau menghapus kelas Windows Runtime, module.g.cpp akan diperbarui dan perlu dikompilasi ulang.

Mengambil alih metode virtual kelas dasar

Kelas turunan Anda dapat mengalami masalah dengan metode virtual jika kelas dasar dan kelas turunannya sama-sama merupakan kelas yang didefinisikan oleh aplikasi, tetapi metode virtual tersebut didefinisikan dalam kelas leluhur Windows Runtime. Dalam praktiknya, ini terjadi jika Anda berasal dari kelas XAML. Sisa bagian ini berlanjut dari contoh di kelas Turunan.

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

Hierarkinya adalah Microsoft::UI::Xaml::Controls::Page<- BasePage<- DerivedPage. Metode BasePage::OnNavigatedFrom dengan benar meng-override Page::OnNavigatedFrom, tetapi DerivedPage::OnNavigatedFrom tidak meng-override BasePage::OnNavigatedFrom.

Di sini, DerivedPage menggunakan kembali IPageOverrides vtable dari BasePage, yang berarti bahwa ia gagal mengambil alih metode IPageOverrides::OnNavigatedFrom . Salah satu solusi potensial mengharuskan BasePage menjadi kelas templat, dan untuk memiliki implementasinya sepenuhnya dalam file header, tetapi itu membuat hal-hal yang tidak dapat diterima rumit.

Sebagai solusinya, nyatakan metode OnNavigatedFrom sebagai virtual secara eksplisit di kelas dasar. Dengan begitu, ketika entri vtable untuk DerivedPage::IPageOverrides::OnNavigatedFrom memanggil BasePage::IPageOverrides::OnNavigatedFrom, produsen memanggil BasePage::OnNavigatedFrom, yang (karena virtualnya), akhirnya memanggil DerivedPage::OnNavigatedFrom.

namespace winrt::MyNamespace::implementation
{
    struct BasePage : BasePageT<BasePage>
    {
        // Note the `virtual` keyword here.
        virtual void OnNavigatedFrom(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };

    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        void OnNavigatedFrom(Microsoft::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };
}

Ini mengharuskan semua anggota hierarki kelas menyetujui nilai pengembalian dan jenis parameter metode OnNavigatedFrom . Jika mereka berbeda pendapat, Anda harus menggunakan versi di atas sebagai metode virtual, dan membungkus versi alternatif.

Note

IDL Anda tidak perlu mendeklarasikan metode yang ditimpa. Untuk detail selengkapnya, lihat Menerapkan metode yang dapat diganti.

API penting