Api Penulis 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.

Catatan

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 menulis kelas Windows Runtime (kelas runtime); Anda hanya ingin menerapkan satu atau beberapa antarmuka Windows Runtime untuk konsumsi lokal dalam aplikasi Anda. Anda memperoleh langsung dari winrt::implements dalam hal ini, dan menerapkan fungsi.
  • Anda menulis kelas runtime. Anda mungkin menulis komponen yang akan digunakan dari 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.

Penting

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

Jika Anda tidak menulis kelas runtime

Skenario paling sederhana adalah di mana jenis Anda mengimplementasikan antarmuka Windows Runtime, dan Anda akan mengonsumsi jenis tersebut dalam aplikasi yang sama. Dalam hal ini, jenis Anda tidak perlu menjadi kelas runtime; hanya kelas C++ biasa. Misalnya, Anda mungkin menulis aplikasi berdasarkan CoreApplication.

Jika jenis Anda dirujuk oleh XAML UI, maka itu perlu menjadi kelas runtime, meskipun berada dalam proyek yang sama dengan XAML. Untuk kasus ini, lihat bagian Jika Anda menulis kelas runtime untuk direferensikan di antarmuka pengguna XAML Anda.

Catatan

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.

Di Visual Studio, templat proyek Visual C++>Windows Universal>Core App (C++/WinRT) mengilustrasikan pola CoreApplication. Pola dimulai dengan meneruskan implementasi Windows::ApplicationModel::Core::IFrameworkViewSource ke CoreApplication::Run.

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

CoreApplication menggunakan antarmuka untuk membuat tampilan pertama aplikasi. Secara konseptual, IFrameworkViewSource terlihat seperti ini.

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

Sekali lagi secara konseptual, implementasi CoreApplication::Run melakukan ini.

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

Jadi Anda, sebagai pengembang, menerapkan antarmuka IFrameworkViewSource . C++/WinRT memiliki templat struct dasar winrt::implements untuk memudahkan penerapan antarmuka (atau beberapa) tanpa menggunakan pemrograman gaya COM. Anda baru saja mendapatkan jenis Anda dari implementasi, lalu mengimplementasikan fungsi antarmuka. Berikut caranya.

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

Itu diurus IFrameworkViewSource. Langkah selanjutnya adalah mengembalikan objek yang mengimplementasikan antarmuka IFrameworkView . Anda juga dapat memilih untuk mengimplementasikan antarmuka tersebut di Aplikasi. Contoh kode berikutnya ini mewakili aplikasi minimal yang setidaknya akan mendapatkan jendela aktif dan berjalan di desktop.

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

Karena Jenis aplikasi Anda adalahIFrameworkViewSource, Anda hanya dapat meneruskan satu untuk Menjalankan.

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

Jika Anda menulis kelas runtime dalam komponen Windows Runtime

Jika jenis Anda dimas dalam komponen Windows Runtime untuk dikonsumsi dari biner lain (biner lainnya biasanya merupakan aplikasi), maka jenis Anda harus menjadi kelas runtime. Anda mendeklarasikan kelas runtime dalam file Microsoft Interface Definition Language (IDL) (.idl) (lihat Memperhitungkan kelas runtime ke dalam file Midl (.idl)).

Setiap file IDL menghasilkan .winmd file, dan Visual Studio menggabungkan semua file tersebut ke dalam satu file dengan nama yang sama dengan namespace layanan 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 kelas Windows Runtime (runtime). Kelas runtime adalah jenis yang dapat diaktifkan dan dikonsumsi melalui antarmuka COM modern, biasanya di seluruh batas yang dapat dieksekusi. 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, pada akar hierarki warisan adalah templat struct dasar winrt::implements sekali lagi.

Untuk detail selengkapnya, kode, dan panduan penulisan API dalam komponen Windows Runtime, lihat komponen Windows Runtime dengan peristiwa C++/WinRT dan Penulis di C++/WinRT.

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

Jika jenis Anda dirujuk oleh UI XAML Anda, maka itu harus menjadi kelas runtime, meskipun berada dalam proyek yang sama dengan XAML. 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 di 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 dihargai bahwa hanya mengatakan "MyRuntimeClass" dalam skenario ini mungkin ambigu; ada beberapa entitas dengan nama itu, dari berbagai jenis.

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

Memperhitungkan 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 merasa lebih mudah untuk menulis dan meninjau kelas runtime Anda jika mereka bersama-sama.

Konstruktor kelas runtime

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

  • Setiap konstruktor yang Anda deklarasikan di IDL Anda menyebabkan konstruktor dihasilkan pada jenis implementasi Anda dan pada jenis yang diproyeksikan. Konstruktor yang dideklarasikan IDL digunakan untuk mengonsumsi kelas runtime dari unit kompilasi yang berbeda .
  • Apakah Anda memiliki konstruktor yang dideklarasikan IDL atau tidak, kelebihan beban konstruktor yang mengambil std::nullptr_t dihasilkan pada jenis yang diproyeksikan. 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).

Catatan

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 pabrik, dan tidak akan dihasilkan. 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

Kami telah melihat bahwa alur kerja adalah menggunakan IDL untuk mendeklarasikan kelas runtime Anda dan anggotanya, dan kemudian alat menghasilkan prototipe dan implementasi stub untuk Anda. Adapun prototipe yang dibuat secara otomatis untuk anggota kelas runtime Anda, Anda dapat mengeditnya sehingga mereka melewati berbagai jenis dari jenis yang Anda deklarasikan di 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 ketika Anda perlu menghindari menangkap referensi ke dalam coroutine (lihat Parameter-passing).
  • Anda dapat melonggarkan nilai pengembalian. Misalnya, Anda dapat mengubah kekosongan menjadi winrt::fire_and_forget.

Dua terakhir sangat berguna ketika Anda menulis penanganan aktivitas asinkron.

Membuat instans dan mengembalikan jenis dan antarmuka implementasi

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

Catatan

Namun, jika Anda merujuk jenis Anda dari UI XAML Anda, maka akan ada jenis implementasi dan jenis yang diproyeksikan dalam proyek yang sama. Dalam hal ini, buat mengembalikan instans dari jenis yang diproyeksikan. 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 sebagai atau try_as adalah derivasi kelas runtime ("kelas yang dapat disusupi"). Saat jenis implementasi menyusun kelas lain, jangan panggil sebagai atau try_as untuk melakukan QueryInterface kelas yang tidak dicentang atau dicentang yang sedang dibuat. Sebagai gantinya, akses anggota data (this->) m_inner , dan panggil sebagai atau try_as pada itu. 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 apa adanya, 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. Tetapi dengan cara ini Anda dapat memanggil metode implementasinya secara langsung, tanpa overhead panggilan 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 menyimpan 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 adalah teknik yang menghindari panggilan fungsi virtual, dan memungkinkan Anda mendapatkan langsung pada implementasi.

Catatan

Jika Anda belum menginstal Windows SDK 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 adalah contoh. 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 menahannya, maka 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.

Jenis implementasi itu sendiri tidak berasal dari winrt::Windows::Foundation::IUnknown, sehingga tidak memiliki fungsi. 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 sudah ditampilkan, dan kembalikan antarmuka yang diproyeksikan, 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 jenis implementasi, Anda dapat meneruskan *this ke metode yang mengharapkan nilai jenis 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 disusupi". 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 : Windows.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 memenuhi syarat dengan base_type alias jenis:

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 jenis implementasi menyusun kelas lain, jangan panggil sebagai atau try_as untuk melakukan QueryInterface kelas yang tidak dicentang atau dicentang yang sedang dibuat. Sebagai gantinya, akses anggota data (this->) m_inner , dan panggil sebagai atau try_as pada itu.

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 meneruskan pemilik. Akibatnya, jika Anda berasal dari ToggleButtonAutomationPeer, maka Anda perlu menyediakan konstruktor yang mengambil pemilik dan meneruskannya ke basis. Mari kita lihat seperti apa yang terlihat dalam praktik.

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

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 kelas bass dari jenis implementasi Anda.

Namespace: jenis proyeksi, jenis implementasi, dan pabrik

Seperti yang telah Anda lihat sebelumnya dalam topik ini, kelas runtime C++/WinRT ada dalam bentuk lebih dari satu kelas C++ di lebih dari satu namespace layanan. Jadi, nama MyRuntimeClass memiliki satu arti dalam namespace winrt::MyProject , dan arti yang berbeda dalam namespace winrt::MyProject::implementation . Ketahui namespace layanan mana yang saat ini Anda miliki 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. Kami telah menunjukkan contoh winrt::make in action sebelumnya dalam topik ini, dan ada contoh lain dalam kontrol XAML; ikat 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

Penting

Ketika Anda ingin mengembalikan jenis yang diproyeksikan dari implementasi Anda, berhati-hatilah untuk tidak membuat instans jenis 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 merusak, sesuai aturan C++ normal, ketika cakupan keluar. Jadi, jika Anda mengembalikan jenis yang diproyeksikan (penunjuk pintar) ke objek tersebut, maka pointer itu sekarang menjuntai.

Jenis bug kerusakan memori ini sulit didiagnosis. Jadi, untuk build debug, pernyataan C++/WinRT membantu Anda menangkap kesalahan ini, menggunakan detektor tumpukan. Tetapi coroutine dialokasikan di timbunan, sehingga Anda tidak akan mendapatkan bantuan dengan kesalahan ini jika Anda membuatnya 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 tempat di mana fitur C++/WinRT mengharapkan jenis, dan jenis apa yang diharapkannya (jenis yang diproyeksikan, jenis implementasi, atau keduanya).

Fitur Menerima Catatan
T (mewakili penunjuk cerdas) Diproyeksikan Lihat perhatian di Namespace: jenis yang diproyeksikan , jenis implementasi, dan pabrik tentang menggunakan jenis implementasi secara tidak sengaja.
agile_ref<T> Keduanya Jika Anda menggunakan jenis implementasi, maka argumen konstruktor harus com_ptr<T>.
com_ptr<T> implementasi Menggunakan jenis yang diproyeksikan menghasilkan kesalahan: 'Release' is not a member of 'T'.
default_interface<T> Keduanya Jika Anda menggunakan jenis implementasi, maka antarmuka pertama yang diimplementasikan dikembalikan.
get_self<T> implementasi Menggunakan jenis yang diproyeksikan menghasilkan kesalahan: '_abi_TrustLevel': is not a member of 'T'.
guid_of<T>() Keduanya Mengembalikan GUID antarmuka default.
IWinRTTemplateInterface<T>
Diproyeksikan Menggunakan jenis implementasi mengkompilasi, tetapi ini adalah kesalahan—lihat perhatian di Namespace: jenis yang diproyeksikan , jenis implementasi, dan pabrik.
make<T> implementasi 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;) Keduanya Jika Anda menggunakan jenis implementasi, maka argumen harus com_ptr<T>.
make_self<T> implementasi 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 jenis implementasi, maka Anda mendapatkan GUID string dari antarmuka default.
weak_ref<T> Keduanya Jika Anda menggunakan jenis implementasi, maka argumen konstruktor harus com_ptr<T>.

Ikut serta dalam konstruksi seragam, dan akses implementasi langsung

Bagian ini menjelaskan fitur C++/WinRT 2.0 yang ikut serta, meskipun diaktifkan secara default untuk proyek baru. Untuk proyek yang ada, Anda harus ikut serta dengan mengonfigurasi cppwinrt.exe alat. Di Visual Studio, atur properti proyek Properti>Umum C++/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. Untuk mengilustrasikan, 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 pabrik untuk membuat apa yang tampaknya merupakan panggilan 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 konsekuensinya, pengembang C++/WinRT yang dibumbui terbiasa menggunakan fungsi winrt::make atau winrt::make_self helper 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 untuk membuat kelas berarti pabrik aktivasinya akan di-cache. Biasanya, itu yang Anda inginkan, tetapi jika pabrik berada di modul yang sama (DLL) yang melakukan panggilan, maka Anda telah secara efektif menyematkan DLL dan mencegahnya dari pernah membongkar. 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 -opt[imize] sakelar, panggilan melalui proyeksi bahasa mengkompilasi ke panggilan efisien yang sama ke fungsi winrt::make yang secara langsung membuat jenis implementasi. Itu membuat sintaks Anda sederhana dan dapat diprediksi, itu menghindari hit performa panggilan melalui pabrik, dan menghindari penyematan komponen dalam proses. Selain proyek komponen, ini juga berguna untuk aplikasi XAML. Melewati RoGetActivationFactory untuk kelas yang diterapkan dalam aplikasi yang sama memungkinkan Anda membangunnya (tanpa perlu didaftarkan) dengan semua cara yang sama seperti yang Anda bisa jika 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 terhadap implementasi, dan bahkan memiliki potensi untuk di-inlin. Yang berbicara dengan istilah lain sering digunakan ketika berbicara tentang -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 khusus header, Anda dapat melihat ke dalam dan melihat apa yang 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; tujuannya 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 telah tiba di perubahan yang melanggar. Definisi tersebut dihasilkan untuk Anda saat Anda menggunakan dan -component-opt[imize], dan muncul dalam file bernama Type.g.cpp, di mana Jenis adalah nama kelas runtime yang diimplementasikan. Itulah sebabnya Anda dapat mengalami berbagai kesalahan tautan saat pertama kali mengaktifkan -opt[imize] dalam proyek yang ada. Anda perlu menyertakan file yang dihasilkan ke dalam implementasi Anda untuk menjahit semuanya.

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

Anda MyClass.cpp adalah di mana semuanya datang bersama-sama.

#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 seragam dalam proyek yang ada, Anda perlu mengedit file setiap implementasi .cpp sehingga Anda #include <Sub/Namespace/Type.g.cpp> setelah penyertaan (dan definisi) dari kelas implementasi. File tersebut menyediakan definisi fungsi-fungsi yang dibiarkan proyeksi tidak terdefinisi. Berikut adalah tampilan definisi tersebut di MyClass.g.cpp dalam file.

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

Dan itu dengan baik menyelesaikan proyeksi dengan panggilan yang efisien langsung ke dalam implementasi, menghindari panggilan ke cache pabrik, dan memenuhi linker.

Hal terakhir yang -opt[imize] dilakukan untuk Anda adalah mengubah implementasi proyek module.g.cpp Anda (file yang membantu Anda mengimplementasikan ekspor DllGetActivationFactory dan DllCanUnloadNow DLL) dengan cara yang sedih sehingga build bertambah bertahap akan cenderung jauh lebih cepat dengan menghilangkan konektor jenis 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 memiliki efek dramatis pada perulangan dalam Anda, karena setiap perubahan pada satu implementasi akan menyebabkan module.g.cpp kompilasi 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, ini tidak akan menyertakannya MyClass.h . 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 MyClass.g.cpp file yang dihasilkan untuk Anda (dan yang sebelumnya Anda sertakan untuk 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. Ini semua berarti bahwa Anda dapat dengan senang hati mengubah implementasi tertentu, dan module.g.cpp kebutuhan tidak dikompilasi ulang sama sekali. Ini hanya ketika Anda menambahkan atau menghapus kelas Windows Runtime yang module.g.cpp akan diperbarui, dan perlu dikompresi ulang.

Mengambil alih metode virtual kelas dasar

Kelas turunan Anda dapat mengalami masalah dengan metode virtual jika basis dan kelas turunan adalah kelas yang ditentukan aplikasi, tetapi metode virtual ditentukan dalam kelas Windows Runtime kakek-nenek. 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(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };

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

Hierarkinya adalah Windows::UI::Xaml::Controls::P age<- BasePage<- DerivedPage. Metode BasePage::OnNavigatedFrom dengan benar menimpa Halaman::OnNavigatedFrom, tetapi DerivedPage::OnNavigatedFrom tidak menimpa 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(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };

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

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

Catatan

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

API penting