Komponen COM penulis dengan C ++/WinRT

C ++/WinRT dapat membantu Anda menulis komponen Component Object Model (COM) klasik (atau koclasses), sama seperti membantu Anda menulis Windows kelas Runtime. Topik ini menunjukkan caranya.

Bagaimana C ++/WinRT berperilaku, secara default, sehubungan dengan antarmuka COM

Template winrt::implements C ++/WinRT adalah dasar dari mana kelas runtime dan pabrik aktivasi Anda secara langsung atau tidak langsung berasal.

Secara default, winrt::implements diam-diam mengabaikan antarmuka COM klasik. Setiap panggilan QueryInterface (QI) untuk antarmuka COM klasik akibatnya akan gagal dengan E_NOINTERFACE. Secara default, winrt::implements hanya mendukung antarmuka C ++/WinRT.

  • winrt::IUnknown adalah antarmuka C ++/WinRT, jadi winrt::implements mendukung antarmuka berbasis winrt::IUnknown.
  • winrt::implements tidak mendukung ::IUnknown itself, secara default.

Sebentar lagi Anda akan melihat cara mengatasi kasus yang tidak didukung secara default. Tapi pertama-tama inilah contoh kode untuk menggambarkan apa yang terjadi secara default.

// Sample.idl
namespace MyProject 
{
    runtimeclass Sample
    {
        Sample();
        void DoWork();
    }
}

// Sample.h
#include "pch.h"
#include <shobjidl.h> // Needed only for this file.

namespace winrt::MyProject::implementation
{
    struct Sample : implements<Sample, IInitializeWithWindow>
    {
        IFACEMETHOD(Initialize)(HWND hwnd);
        void DoWork();
    }
}

Dan inilah kode klien untuk mengkonsumsi kelas Sampel .

// Client.cpp
Sample sample; // Construct a Sample object via its projection.

// This next line doesn't compile yet.
sample.as<IInitializeWithWindow>()->Initialize(hwnd); 

Mengaktifkan dukungan COM klasik

Kabar baiknya adalah bahwa semua yang diperlukan untuk menyebabkan winrt: : mengimplementasikan untuk mendukung antarmuka COM klasik adalah untuk menyertakan unknwn.h file header sebelum Anda menyertakan header C + + / WinRT.

Anda dapat melakukannya secara eksplisit, atau tidak langsung dengan memasukkan beberapa file header lainnya seperti ole2.h. Salah satu metode yang disarankan adalah dengan menyertakan wil\cppwinrt.h file header, yang merupakan bagian dari Windows Implementation Libraries (WIL). File wil\cppwinrt.h header tidak hanya memastikan bahwa unknwn.h disertakan sebelumnya winrt/base.h, tetapi juga mengatur semuanya sehingga C ++/ WinRT dan WIL memahami pengecualian dan kode kesalahan masing-masing.

Anda kemudian dapat adapun<> antarmuka COM klasik, dan kode dalam contoh di atas akan dikompilasi.

Catatan

Pada contoh di atas, bahkan setelah mengaktifkan dukungan COM klasik di klien (kode yang memakan kelas), jika Anda belum juga mengaktifkan dukungan COM klasik di server (kode yang menerapkan kelas), maka panggilan ke seperti<> di klien akan macet karena QI untuk IInitializeWithWindow akan gagal.

Kelas lokal (tidak diproyeksikan)

Kelas lokal adalah kelas yang diimplementasikan dan dikonsumsi dalam unit kompilasi yang sama (aplikasi, atau biner lainnya); jadi tidak ada proyeksi untuk itu.

Berikut adalah contoh kelas lokal yang hanya menerapkan antarmuka COM klasik.

struct LocalObject :
    winrt::implements<LocalObject, IInitializeWithWindow>
{
    ...
};

Jika Anda menerapkan contoh itu, tetapi Anda tidak mengaktifkan dukungan COM klasik, maka kode berikut gagal.

winrt::make<LocalObject>(); // error: ‘first_interface’: is not a member of ‘winrt::impl::interface_list<>’

Sekali lagi, IInitializeWithWindow tidak dikenali sebagai antarmuka COM, jadi C ++/WinRT mengabaikannya. Dalam kasus contoh LocalObject , hasil mengabaikan antarmuka COM berarti bahwa LocalObject tidak memiliki antarmuka sama sekali. Tetapi setiap kelas COM harus menerapkan setidaknya satu antarmuka.

Contoh sederhana dari komponen COM

Berikut adalah contoh sederhana dari komponen COM yang ditulis menggunakan C ++/ WinRT. Ini adalah daftar lengkap aplikasi mini, sehingga Anda dapat mencoba kode jika Anda menempelkannya ke dalam pch.h dan main.cpp proyek Aplikasi Konsol Windows baru (C ++/ WinRT).

// pch.h
#pragma once
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>

// main.cpp : Defines the entry point for the console application.
#include "pch.h"

struct __declspec(uuid("ddc36e02-18ac-47c4-ae17-d420eece2281")) IMyComInterface : ::IUnknown
{
    virtual HRESULT __stdcall Call() = 0;
};

using namespace winrt;
using namespace Windows::Foundation;

int main()
{
    winrt::init_apartment();

    struct MyCoclass : winrt::implements<MyCoclass, IPersist, IStringable, IMyComInterface>
    {
        HRESULT __stdcall Call() noexcept override
        {
            return S_OK;
        }

        HRESULT __stdcall GetClassID(CLSID* id) noexcept override
        {
            *id = IID_IPersist; // Doesn't matter what we return, for this example.
            return S_OK;
        }

        winrt::hstring ToString()
        {
            return L"MyCoclass as a string";
        }
    };

    auto mycoclass_instance{ winrt::make<MyCoclass>() };
    CLSID id{};
    winrt::check_hresult(mycoclass_instance->GetClassID(&id));
    winrt::check_hresult(mycoclass_instance.as<IMyComInterface>()->Call());
}

Lihat juga Konsumsi komponen COM dengan C++/WinRT.

Contoh yang lebih realistis dan menarik

Sisa dari topik ini berjalan melalui pembuatan proyek aplikasi konsol minimal yang menggunakan C ++/ WinRT untuk menerapkan coclass dasar (komponen COM, atau kelas COM) dan pabrik kelas. Contoh aplikasi menunjukkan cara memberikan pemberitahuan roti panggang dengan tombol callback di atasnya, dan coclass (yang mengimplementasikan antarmuka INotificationActivationCallback COM) memungkinkan aplikasi diluncurkan dan dipanggil kembali ketika pengguna mengklik tombol itu pada roti panggang.

Latar belakang lebih lanjut tentang area fitur notifikasi roti panggang dapat ditemukan di Kirim pemberitahuan roti panggang lokal. Tak satu pun dari contoh kode di bagian dokumentasi menggunakan C ++/ WinRT, jadi kami sarankan Anda lebih suka kode yang ditampilkan dalam topik ini.

Membuat proyek Aplikasi Konsol Windows (ToastAndCallback)

Mulailah dengan membuat proyek baru di Microsoft Visual Studio. Buat proyek Aplikasi Konsol Windows (C ++/WinRT), dan beri nama ToastAndCallback.

Buka pch.h, dan tambahkan #include <unknwn.h> sebelum menyertakan untuk header C ++/WinRT apa pun. Berikut hasilnya; Anda dapat mengganti konten Anda pch.h dengan daftar ini.

// pch.h
#pragma once
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>

Buka main.cpp, dan hapus arahan penggunaan yang dihasilkan templat proyek. Sebagai gantinya, masukkan kode berikut (yang memberi kita libs, header, dan nama tipe yang kita butuhkan). Berikut hasilnya; Anda dapat mengganti konten Anda main.cpp dengan daftar ini (kami juga telah menghapus kode dari main dalam daftar di bawah ini, karena kami akan mengganti fungsi itu nanti).

// main.cpp : Defines the entry point for the console application.

#include "pch.h"

#pragma comment(lib, "advapi32")
#pragma comment(lib, "ole32")
#pragma comment(lib, "shell32")

#include <iomanip>
#include <iostream>
#include <notificationactivationcallback.h>
#include <propkey.h>
#include <propvarutil.h>
#include <shlobj.h>
#include <winrt/Windows.UI.Notifications.h>
#include <winrt/Windows.Data.Xml.Dom.h>

using namespace winrt;
using namespace Windows::Data::Xml::Dom;
using namespace Windows::UI::Notifications;

int main() { }

Proyek ini belum akan dibangun; setelah kami selesai menambahkan kode, Anda akan diminta untuk membangun dan menjalankan.

Menerapkan coclass dan pabrik kelas

Di C ++/WinRT, Anda menerapkan coclasses, dan pabrik kelas, dengan berasal dari winrt::implements base struct. Segera setelah tiga menggunakan-arahan yang ditunjukkan di atas (dan sebelumnya main), tempel kode ini untuk mengimplementasikan komponen aktivator COM pemberitahuan roti panggang Anda.

static constexpr GUID callback_guid // BAF2FA85-E121-4CC9-A942-CE335B6F917F
{
    0xBAF2FA85, 0xE121, 0x4CC9, {0xA9, 0x42, 0xCE, 0x33, 0x5B, 0x6F, 0x91, 0x7F}
};

std::wstring const this_app_name{ L"ToastAndCallback" };

struct callback : winrt::implements<callback, INotificationActivationCallback>
{
    HRESULT __stdcall Activate(
        LPCWSTR app,
        LPCWSTR args,
        [[maybe_unused]] NOTIFICATION_USER_INPUT_DATA const* data,
        [[maybe_unused]] ULONG count) noexcept final
    {
        try
        {
            std::wcout << this_app_name << L" has been called back from a notification." << std::endl;
            std::wcout << L"Value of the 'app' parameter is '" << app << L"'." << std::endl;
            std::wcout << L"Value of the 'args' parameter is '" << args << L"'." << std::endl;
            return S_OK;
        }
        catch (...)
        {
            return winrt::to_hresult();
        }
    }
};

struct callback_factory : implements<callback_factory, IClassFactory>
{
    HRESULT __stdcall CreateInstance(
        IUnknown* outer,
        GUID const& iid,
        void** result) noexcept final
    {
        *result = nullptr;

        if (outer)
        {
            return CLASS_E_NOAGGREGATION;
        }

        return make<callback>()->QueryInterface(iid, result);
    }

    HRESULT __stdcall LockServer(BOOL) noexcept final
    {
        return S_OK;
    }
};

Implementasi coclass di atas mengikuti pola yang sama yang ditunjukkan dalam API Penulis dengan C ++/WinRT. Jadi, Anda dapat menggunakan teknik yang sama untuk mengimplementasikan antarmuka COM serta antarmuka runtime Windows. Komponen COM dan kelas runtime Windows mengekspos fitur mereka melalui antarmuka. Setiap antarmuka COM akhirnya berasal dari antarmuka antarmuka IUnknown . Runtime Windows didasarkan pada COM— satu perbedaan adalah bahwa antarmuka runtime Windows pada akhirnya berasal dari antarmuka IInspectable (dan IInspectable berasal dari IUnknown).

Dalam koclass pada kode di atas, kami menerapkan metode INotificationActivationCallback::Activate , yang merupakan fungsi yang dipanggil saat pengguna mengklik tombol callback pada notifikasi toast. Tetapi sebelum fungsi itu dapat dipanggil, contoh koclass perlu dibuat, dan itulah tugas fungsi IClassFactory::CreateInstance .

Coclass yang baru saja kami implementasikan dikenal sebagai aktivator COM untuk notifikasi, dan memiliki id kelas (CLSID) dalam bentuk callback_guid pengenal (tipe GUID) yang Anda lihat di atas. Kita akan menggunakan pengidentifikasi itu nanti, dalam bentuk pintasan menu Mulai dan entri Windows Registry. Aktivator COM CLSID, dan jalur ke server COM terkait (yang merupakan jalur menuju executable yang sedang kami bangun di sini) adalah mekanisme di mana pemberitahuan roti panggang tahu kelas apa yang harus dibuat contoh ketika tombol callback diklik (apakah pemberitahuan diklik di Action Center atau tidak).

Praktik terbaik untuk menerapkan metode COM

Teknik untuk penanganan kesalahan dan untuk manajemen sumber daya dapat berjalan seiring. Lebih nyaman dan praktis untuk menggunakan pengecualian daripada kode kesalahan. Dan jika Anda menggunakan idiom resource-acquisition-is-initialization (RAII), maka Anda dapat menghindari memeriksa kode kesalahan secara eksplisit dan kemudian secara eksplisit melepaskan sumber daya. Pemeriksaan eksplisit semacam itu membuat kode Anda lebih berbelit-belit dari yang diperlukan, dan memberi bug banyak tempat untuk bersembunyi. Sebagai gantinya, gunakan RAII, dan lempar / tangkap pengecualian. Dengan begitu, alokasi sumber daya Anda aman pengecualian, dan kode Anda sederhana.

Namun, Anda tidak boleh membiarkan pengecualian lolos dari implementasi metode COM Anda. Anda dapat memastikan bahwa dengan menggunakan penentu noexcept pada metode COM Anda. Tidak apa-apa untuk pengecualian yang akan dilemparkan di mana saja dalam grafik panggilan metode Anda, selama Anda menanganinya sebelum metode Anda keluar. Jika Anda menggunakan noexcept, tetapi Anda kemudian mengizinkan pengecualian untuk melarikan diri dari metode Anda, maka aplikasi Anda akan berakhir.

Menambahkan jenis dan fungsi pembantu

Pada langkah ini, kami akan menambahkan beberapa jenis dan fungsi pembantu yang digunakan oleh kode lainnya. Jadi, segera sebelum main, tambahkan yang berikut.

struct prop_variant : PROPVARIANT
{
    prop_variant() noexcept : PROPVARIANT{}
    {
    }

    ~prop_variant() noexcept
    {
        clear();
    }

    void clear() noexcept
    {
        WINRT_VERIFY_(S_OK, ::PropVariantClear(this));
    }
};

struct registry_traits
{
    using type = HKEY;

    static void close(type value) noexcept
    {
        WINRT_VERIFY_(ERROR_SUCCESS, ::RegCloseKey(value));
    }

    static constexpr type invalid() noexcept
    {
        return nullptr;
    }
};

using registry_key = winrt::handle_type<registry_traits>;

std::wstring get_module_path()
{
    std::wstring path(100, L'?');
    uint32_t path_size{};
    DWORD actual_size{};

    do
    {
        path_size = static_cast<uint32_t>(path.size());
        actual_size = ::GetModuleFileName(nullptr, path.data(), path_size);

        if (actual_size + 1 > path_size)
        {
            path.resize(path_size * 2, L'?');
        }
    } while (actual_size + 1 > path_size);

    path.resize(actual_size);
    return path;
}

std::wstring get_shortcut_path()
{
    std::wstring format{ LR"(%ProgramData%\Microsoft\Windows\Start Menu\Programs\)" };
    format += (this_app_name + L".lnk");

    auto required{ ::ExpandEnvironmentStrings(format.c_str(), nullptr, 0) };
    std::wstring path(required - 1, L'?');
    ::ExpandEnvironmentStrings(format.c_str(), path.data(), required);
    return path;
}

Menerapkan fungsi yang tersisa, dan fungsi titik masuk wmain

Hapus fungsi Anda main , dan sebagai gantinya tempel daftar kode ini, yang mencakup kode untuk mendaftarkan coclass Anda, dan kemudian untuk memberikan roti panggang yang mampu memanggil kembali aplikasi Anda.

void register_callback()
{
    DWORD registration{};

    winrt::check_hresult(::CoRegisterClassObject(
        callback_guid,
        make<callback_factory>().get(),
        CLSCTX_LOCAL_SERVER,
        REGCLS_SINGLEUSE,
        &registration));
}

void create_shortcut()
{
    auto link{ winrt::create_instance<IShellLink>(CLSID_ShellLink) };
    std::wstring module_path{ get_module_path() };
    winrt::check_hresult(link->SetPath(module_path.c_str()));

    auto store = link.as<IPropertyStore>();
    prop_variant value;
    winrt::check_hresult(::InitPropVariantFromString(this_app_name.c_str(), &value));
    winrt::check_hresult(store->SetValue(PKEY_AppUserModel_ID, value));
    value.clear();
    winrt::check_hresult(::InitPropVariantFromCLSID(callback_guid, &value));
    winrt::check_hresult(store->SetValue(PKEY_AppUserModel_ToastActivatorCLSID, value));

    auto file{ store.as<IPersistFile>() };
    std::wstring shortcut_path{ get_shortcut_path() };
    winrt::check_hresult(file->Save(shortcut_path.c_str(), TRUE));

    std::wcout << L"In " << shortcut_path << L", created a shortcut to " << module_path << std::endl;
}

void update_registry()
{
    std::wstring key_path{ LR"(SOFTWARE\Classes\CLSID\{????????-????-????-????-????????????})" };
    ::StringFromGUID2(callback_guid, key_path.data() + 23, 39);
    key_path += LR"(\LocalServer32)";
    registry_key key;

    winrt::check_win32(::RegCreateKeyEx(
        HKEY_CURRENT_USER,
        key_path.c_str(),
        0,
        nullptr,
        0,
        KEY_WRITE,
        nullptr,
        key.put(),
        nullptr));
    ::RegDeleteValue(key.get(), nullptr);

    std::wstring path{ get_module_path() };

    winrt::check_win32(::RegSetValueEx(
        key.get(),
        nullptr,
        0,
        REG_SZ,
        reinterpret_cast<BYTE const*>(path.c_str()),
        static_cast<uint32_t>((path.size() + 1) * sizeof(wchar_t))));

    std::wcout << L"In " << key_path << L", registered local server at " << path << std::endl;
}

void create_toast()
{
    XmlDocument xml;

    std::wstring toastPayload
    {
        LR"(
<toast>
  <visual>
    <binding template='ToastGeneric'>
      <text>)"
    };
    toastPayload += this_app_name;
    toastPayload += LR"(
      </text>
    </binding>
  </visual>
  <actions>
    <action content='Call back )";
    toastPayload += this_app_name;
    toastPayload += LR"(
' arguments='the_args' activationKind='Foreground' />
  </actions>
</toast>)";
    xml.LoadXml(toastPayload);

    ToastNotification toast{ xml };
    ToastNotifier notifier{ ToastNotificationManager::CreateToastNotifier(this_app_name) };
    notifier.Show(toast);
    ::Sleep(50); // Give the callback chance to display.
}

void LaunchedNormally(HANDLE, INPUT_RECORD &, DWORD &);
void LaunchedFromNotification(HANDLE, INPUT_RECORD &, DWORD &);

int wmain(int argc, wchar_t * argv[], wchar_t * /* envp */[])
{
    winrt::init_apartment();

    register_callback();

    HANDLE consoleHandle{ ::GetStdHandle(STD_INPUT_HANDLE) };
    INPUT_RECORD buffer{};
    DWORD events{};
    ::FlushConsoleInputBuffer(consoleHandle);

    if (argc == 1)
    {
        LaunchedNormally(consoleHandle, buffer, events);
    }
    else if (argc == 2 && wcscmp(argv[1], L"-Embedding") == 0)
    {
        LaunchedFromNotification(consoleHandle, buffer, events);
    }
}

void LaunchedNormally(HANDLE consoleHandle, INPUT_RECORD & buffer, DWORD & events)
{
    try
    {
        bool runningAsAdmin{ ::IsUserAnAdmin() == TRUE };
        std::wcout << this_app_name << L" is running" << (runningAsAdmin ? L" (administrator)." : L" (NOT as administrator).") << std::endl;

        if (runningAsAdmin)
        {
            create_shortcut();
            update_registry();
        }

        std::wcout << std::endl << L"Press 'T' to display a toast notification (press any other key to exit)." << std::endl;

        ::ReadConsoleInput(consoleHandle, &buffer, 1, &events);
        if (towupper(buffer.Event.KeyEvent.uChar.UnicodeChar) == L'T')
        {
            create_toast();
        }
    }
    catch (winrt::hresult_error const& e)
    {
        std::wcout << L"Error: " << e.message().c_str() << L" (" << std::hex << std::showbase << std::setw(8) << static_cast<uint32_t>(e.code()) << L")" << std::endl;
    }
}

void LaunchedFromNotification(HANDLE consoleHandle, INPUT_RECORD & buffer, DWORD & events)
{
    ::Sleep(50); // Give the callback chance to display its message.
    std::wcout << std::endl << L"Press any key to exit." << std::endl;
    ::ReadConsoleInput(consoleHandle, &buffer, 1, &events);
}

Cara menguji contoh aplikasi

Bangun aplikasi, lalu jalankan setidaknya sekali sebagai administrator untuk menyebabkan pendaftaran, dan pengaturan lainnya, kode berjalan. Salah satu cara untuk melakukannya adalah dengan menjalankan Visual Studio sebagai administrator, dan kemudian menjalankan aplikasi dari Visual Studio. Klik kanan Visual Studio di bilah tugas untuk menampilkan daftar lompat, klik kanan Visual Studio pada daftar lompat, lalu klik Jalankan sebagai administrator. Setujui prompt, lalu buka proyek. Saat Anda menjalankan aplikasi, pesan ditampilkan yang menunjukkan apakah aplikasi berjalan sebagai administrator atau tidak. Jika tidak, maka pendaftaran dan pengaturan lainnya tidak akan berjalan. Pendaftaran dan pengaturan lainnya harus berjalan setidaknya sekali agar aplikasi berfungsi dengan benar.

Apakah Anda menjalankan aplikasi sebagai administrator atau tidak, tekan 'T' untuk membuat roti panggang ditampilkan. Anda kemudian dapat mengklik tombol Panggil kembali ToastAndCallback baik langsung dari pemberitahuan roti panggang yang muncul, atau dari Pusat Aksi, dan aplikasi Anda akan diluncurkan, kokelas instantiated, dan metode INotificationActivationCallback::Activate dijalankan.

Server COM dalam proses

Aplikasi contoh ToastAndCallback di atas berfungsi sebagai server COM lokal (atau di luar proses). Ini ditunjukkan oleh localserver32 Windows Registry key yang Anda gunakan untuk mendaftarkan CLSID dari coclass-nya. Server COM lokal menghosting coclass-nya di dalam biner yang dapat dieksekusi (an .exe).

Atau (dan bisa dibilang lebih mungkin), Anda dapat memilih untuk meng-host coclass Anda di dalam pustaka tautan dinamis (a .dll). Server COM dalam bentuk DLL dikenal sebagai server COM dalam proses, dan itu ditunjukkan oleh CLSID yang terdaftar dengan menggunakan kunci InprocServer32 Windows Registry.

Anda dapat memulai tugas membuat server COM dalam proses dengan membuat proyek baru di Microsoft Visual Studio. Buat proyek Visual C++>Windows DesktopDynamic-Link>Library (DLL).

Untuk menambahkan dukungan C++/WinRT ke proyek baru, ikuti langkah-langkah yang dijelaskan dalam Memodifikasi proyek aplikasi Desktop Windows untuk menambahkan dukungan C++/WinRT.

Menerapkan ekspor coclass, pabrik kelas, dan server in-proc

Buka dllmain.cpp, dan tambahkan ke dalamnya daftar kode yang ditunjukkan di bawah ini.

Jika Anda sudah memiliki DLL yang mengimplementasikan kelas C ++/WinRT Windows Runtime, maka Anda sudah memiliki fungsi DllCanUnloadNow yang ditunjukkan di bawah ini. Jika Anda ingin menambahkan koclass ke DLL itu, maka Anda dapat menambahkan fungsi DllGetClassObject .

Jika tidak memiliki kode Windows Runtime C++ Template Library (WRL) yang ada yang ingin Anda tetap kompatibel, maka Anda dapat menghapus bagian WRL dari kode yang ditampilkan.

// dllmain.cpp

struct MyCoclass : winrt::implements<MyCoclass, IPersist>
{
    HRESULT STDMETHODCALLTYPE GetClassID(CLSID* id) noexcept override
    {
        *id = IID_IPersist; // Doesn't matter what we return, for this example.
        return S_OK;
    }
};

struct __declspec(uuid("85d6672d-0606-4389-a50a-356ce7bded09"))
    MyCoclassFactory : winrt::implements<MyCoclassFactory, IClassFactory>
{
    HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject) noexcept override
    {
        try
        {
            return winrt::make<MyCoclass>()->QueryInterface(riid, ppvObject);
        }
        catch (...)
        {
            return winrt::to_hresult();
        }
    }

    HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock) noexcept override
    {
        // ...
        return S_OK;
    }

    // ...
};

HRESULT __stdcall DllCanUnloadNow()
{
#ifdef _WRL_MODULE_H_
    if (!::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().Terminate())
    {
        return S_FALSE;
    }
#endif

    if (winrt::get_module_lock())
    {
        return S_FALSE;
    }

    winrt::clear_factory_cache();
    return S_OK;
}

HRESULT __stdcall DllGetClassObject(GUID const& clsid, GUID const& iid, void** result)
{
    try
    {
        *result = nullptr;

        if (clsid == __uuidof(MyCoclassFactory))
        {
            return winrt::make<MyCoclassFactory>()->QueryInterface(iid, result);
        }

#ifdef _WRL_MODULE_H_
        return ::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().GetClassObject(clsid, iid, result);
#else
        return winrt::hresult_class_not_available().to_abi();
#endif
    }
    catch (...)
    {
        return winrt::to_hresult();
    }
}

Dukungan untuk referensi yang lemah

Lihat juga Referensi lemah di C++/WinRT.

C ++/WinRT (khususnya, winrt::implements base struct template) mengimplementasikan IWeakReferenceSource untuk Anda jika jenis Anda mengimplementasikan IInspectable (atau antarmuka apa pun yang berasal dari IInspectable).

Ini karena IWeakReferenceSource dan IWeakReference dirancang untuk jenis Runtime Windows. Jadi, Anda dapat mengaktifkan dukungan referensi yang lemah untuk coclass Anda hanya dengan menambahkan winrt::Windows::Foundation::IInspectable (atau antarmuka yang berasal dari IInspectable) ke implementasi Anda.

struct MyCoclass : winrt::implements<MyCoclass, IMyComInterface, winrt::Windows::Foundation::IInspectable>
{
    //  ...
};

Menerapkan antarmuka COM yang berasal dari yang lain

Derivasi antarmuka adalah fitur COM klasik (dan kebetulan tidak ada, sengaja, dari Windows Runtime). Berikut adalah contoh seperti apa derivasi antarmuka.

IFileSystemBindData2 : public IFileSystemBindData { /* ... */  };

Jika Anda menulis kelas yang perlu diimplementasikan, misalnya, IFileSystemBindData dan IFileSystemBindData2, maka langkah pertama dalam mengekspresikannya adalah menyatakan bahwa Anda hanya menerapkan antarmuka turunan , seperti ini.

// pch.h
#pragma once
#include <Shobjidl.h>
...

// main.cpp
...
struct MyFileSystemBindData :
    implements<MyFileSystemBindData,
    IFileSystemBindData2>
{
    // IFileSystemBindData
    IFACEMETHOD(SetFindData)(const WIN32_FIND_DATAW* pfd) override { /* ... */ return S_OK; };
    IFACEMETHOD(GetFindData)(WIN32_FIND_DATAW* pfd) override { /* ... */ return S_OK; };

    // IFileSystemBindData2
    IFACEMETHOD(SetFileID)(LARGE_INTEGER liFileID) override { /* ... */ return S_OK; };
    IFACEMETHOD(GetFileID)(LARGE_INTEGER* pliFileID) override { /* ... */ return S_OK; };
    IFACEMETHOD(SetJunctionCLSID)(REFCLSID clsid) override { /* ... */ return S_OK; };
    IFACEMETHOD(GetJunctionCLSID)(CLSID* pclsid) override { /* ... */ return S_OK; };
};
...
int main()
...

Langkah selanjutnya adalah memastikan bahwa QueryInterface berhasil ketika dipanggil (langsung atau tidak langsung) untuk IID_IFileSystemBindData (antarmuka dasar ) terhadap instance MyFileSystemBindData. Anda melakukannya dengan memberikan spesialisasi untuk template fungsi winrt: is_guid_of .

winrt::is_guid_of variadic, sehingga Anda dapat memberikannya daftar antarmuka. Berikut adalah bagaimana Anda akan memberikan spesialisasi sehingga pemeriksaan untuk IFileSystemBindData2 juga termasuk tes untuk IFileSystemBindData.

// pch.h
...
namespace winrt
{
    template<>
    inline bool is_guid_of<IFileSystemBindData2>(guid const& id) noexcept
    {
        return is_guid_of<IFileSystemBindData2, IFileSystemBindData>(id);
    }
}

// main.cpp
...
int main()
{
    ...
    auto mfsbd{ winrt::make<MyFileSystemBindData>() };
    auto a{ mfsbd.as<IFileSystemBindData2>() }; // Would succeed even without the **is_guid_of** specialization.
    auto b{ mfsbd.as<IFileSystemBindData>() }; // Needs the **is_guid_of** specialization in order to succeed.
}

Spesialisasi winrt::is_guid_of harus identik di semua file dalam proyek, dan terlihat pada titik antarmuka digunakan oleh winrt: : mengimplementasikan atau winrt: :d templateelegate . Biasanya Anda akan memasukkannya ke dalam file header umum.

API Penting