Поделиться через


Создание com-компонентов с помощью C++/WinRT

C++/WinRT помогает создавать классические компоненты объектной модели компонентов (COM) (или соклассы), так же, как это помогает создавать классы среды выполнения Windows. В этом разделе показано, как это сделать.

Поведение C++/WinRT по умолчанию относительно com-интерфейсов

C++/WinRT::winrt::implements шаблон является основой, от которой непосредственно или опосредованно наследуются ваши классы времени выполнения и фабрики активации.

По умолчанию winrt::реализует молчаливо игнорирует классические COM-интерфейсы. Все вызовы QueryInterface (QI) для классических COM-интерфейсов завершатся ошибкой с E_NOINTERFACE. По умолчанию winrt::реализует поддерживает только интерфейсы C++/WinRT.

  • winrt::IUnknown является интерфейсом C++/WinRT, поэтому winrt::implements поддерживает winrt::IUnknown-основанные интерфейсы.
  • winrt::implements по умолчанию не поддерживает ::IUnknown.

Скоро вы увидите, как преодолеть случаи, которые не поддерживаются по умолчанию. Но сначала ниже приведен пример кода для иллюстрации того, что происходит по умолчанию.

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

И вот код клиента для использования класса Sample.

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

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

Включение классической поддержки COM

Хорошая новость в том, что для обеспечения поддержки классических COM-интерфейсов с помощью winrt::implements достаточно включить файл заголовков unknwn.h перед подключением заголовков C++/WinRT.

Это можно сделать явно или косвенно, включив другой файл заголовка, например ole2.h. Одним из рекомендуемых методов является включение файла заголовка wil\cppwinrt.h, который является частью библиотек реализации Windows (WIL) . Файл заголовка wil\cppwinrt.h не только гарантирует, что unknwn.h включен до winrt/base.h, но также настраивает взаимодействие, чтобы C++/WinRT и WIL разбирались в исключениях и кодах ошибок друг друга.

Затем вы можете использовать как<> для классических COM-интерфейсов, и код в приведенном выше примере скомпилируется.

Замечание

В приведенном выше примере даже после включения классической поддержки COM в клиенте (код, использующем класс), если вы также не включили классическую поддержку COM на сервере (код, реализующий класс), вызов как<> в клиенте завершится сбоем, так как QI для IInitializeWithWindow завершится ошибкой.

Локальный (непроектируемый) класс

Локальный класс — это класс, который реализуется и используется в одной единице компиляции (приложение или другой двоичный файл); и поэтому для него нет проекции.

Ниже приведен пример локального класса, реализующего только классические интерфейсы COM .

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

Если вы реализуете этот пример, но не включаете классическую поддержку COM, то следующий код завершается сбоем.

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

Опять же, IInitializeWithWindow не распознается как COM-интерфейс, поэтому C++/WinRT игнорирует его. В примере LocalObject результат игнорирования COM-интерфейсов состоит в том, что LocalObject вообще не имеет интерфейсов. Но каждый класс COM должен реализовать по крайней мере один интерфейс.

Простой пример com-компонента

Ниже приведен простой пример com-компонента, написанного с помощью C++/WinRT. Это полный список мини-приложения, поэтому вы можете попробовать код, если вы вставьте его в pch.h и main.cpp нового консольного приложения Windows (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());
}

См. также Потребление COM-компонентов с C++/WinRT.

Более реалистичный и интересный пример

Оставшаяся часть этого раздела описывает создание минимального проекта консольного приложения, использующего C++/WinRT для реализации базового coclass (COM-компонента или класса COM) и фабрики классов. В примере приложения показано, как доставить сообщение-тост с кнопкой обратного вызова, а сокласс (который реализует интерфейс INotificationActivationCallback COM) позволяет запускать приложение и вызывать его, когда пользователь нажимает на эту кнопку в сообщении-тосте.

Дополнительные сведения о области функции всплывающего уведомления можно найти на странице Отправить локальное всплывающее уведомление. Ни один из примеров кода в этом разделе документации не использует C++/WinRT, поэтому рекомендуется использовать код, показанный в этом разделе.

Создание проекта консольного приложения Windows (ToastAndCallback)

Начните с создания проекта в Microsoft Visual Studio. Создайте проект консольного приложения Windows (C++/WinRT) и назовите его ToastAndCallback.

Откройте pch.hи добавьте #include <unknwn.h> перед инструкциями для любых заголовков C++/WinRT. Вот результат; содержимое вашего pch.h вы можете заменить на этот список.

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

Откройте main.cppи удалите директивы using, создаваемые шаблоном проекта. На их место вставьте следующий код (который предоставляет нам нужные библиотеки, заголовочные файлы и имена типов). Вот результат; вы можете заменить содержимое main.cpp этим описанием (мы также удалили код из main в приведенном ниже списке, так как мы заменим эту функцию позже).

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

Проект еще не будет построен; После завершения добавления кода вам будет предложено создать и запустить.

Реализовать coclass и фабрику классов

В C++/WinRT вы реализуете кослассы и фабрики классов, производные от базовой структуры winrt::implements. Сразу после трех приведенных выше директив using (и до main) вставьте этот код, чтобы реализовать компонент-активатор COM для уведомления toast.

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

Реализация вышеуказанного кокласса следует тому же шаблону, который демонстрируется в API Author с помощью C++/WinRT. Таким образом, можно использовать тот же метод для реализации com-интерфейсов, а также интерфейсов среды выполнения Windows. Компоненты COM и классы среды выполнения Windows предоставляют свои функции через интерфейсы. Каждый интерфейс COM в конечном счете производен от интерфейса IUnknown . Среда выполнения Windows основана на COM — одно из различий заключается в том, что интерфейсы среды выполнения Windows в конечном счете происходят от интерфейса IInspectableIInspectable происходит от IUnknown).

В коклассе в приведенном выше коде мы реализуем метод INotificationActivationCallback::Activate, который вызывается при нажатии пользователем кнопки для активации на всплывающем уведомлении. Но перед вызовом этой функции необходимо создать экземпляр сокласса, и это задание функции IClassFactory::CreateInstance.

Coclass, нами реализованный, называется COM-активатором для уведомлений, с идентификатором класса (CLSID) в виде идентификатора callback_guid (типа GUID), который вы видите выше. Позже мы будем использовать этот идентификатор в виде контекстного меню "Пуск" и записи реестра Windows. Активатор COM CLSID и путь к связанному COM-серверу (который является путем к исполняемому файлу, который мы создаем здесь) — это механизм, который позволяет всплывающему уведомлению определить, какой класс необходимо создать при нажатии на кнопку обратного вызова, независимо от того, нажато ли уведомление в Центре уведомлений или нет.

Рекомендации по реализации методов COM

Методы обработки ошибок и управления ресурсами могут тесно взаимосвязываться. Это удобнее и удобно использовать исключения, чем коды ошибок. И если вы используете идиому ресурс-инициализация-приобретение (RAII), то можно избежать явной проверки кодов ошибок и явного освобождения ресурсов. Такие явные проверки делают код более свертанным, чем необходимо, и он дает ошибкам множество мест для скрытия. Вместо этого используйте RAII и исключения для перехвата. Таким образом, выделение ресурсов устойчиво к исключениям, и код прост.

Однако вы не должны позволять исключениям выходить за пределы реализаций ваших методов COM. Вы можете убедиться в этом, используя спецификатор noexcept в ваших методах COM. Допустимо, чтобы исключения возникали в любом месте графа вызовов вашего метода, если вы их обработаете до завершения метода. Если вы используете noexcept, но позволяете исключению покинуть ваш метод, то ваше приложение завершит работу.

Добавление вспомогательных типов и функций

На этом шаге мы добавим некоторые вспомогательные типы и функции, которые использует остальная часть кода. Таким образом, сразу перед mainдобавьте следующее.

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

Реализуйте оставшиеся функции и функцию точки входа wmain

Удалите свою функцию main и замените ее этим кодом, который включает код для регистрации вашего coclass, а также для доставки уведомления toast, способного вызывать ваше приложение.

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

Тестирование примера приложения

Создайте приложение, а затем запустите его по крайней мере один раз от имени администратора, чтобы вызвать регистрацию и другую настройку, выполнить код. Один из способов сделать это — запустить Visual Studio от имени администратора, а затем запустить приложение из Visual Studio. Щелкните Правой кнопкой мыши Visual Studio на панели задач, чтобы отобразить список переходов, щелкните правой кнопкой мыши Visual Studio в списке переходов и щелкните Запустить от имени администратора. Примите запрос, а затем откройте проект. При запуске приложения отображается сообщение, указывающее, работает ли приложение как администратор. Если это не так, регистрация и другая настройка не будут выполняться. Эта регистрация и другая настройка должна выполняться по крайней мере один раз, чтобы приложение работало правильно.

Независимо от того, запущено ли приложение в качестве администратора, нажмите клавишу T, чтобы отобразить тост-уведомление. Затем можно нажать кнопку Call back ToastAndCallback либо непосредственно из всплывающего уведомления, которое появится, либо из Центра уведомлений, и приложение будет запущено, coclass будет создан, и метод INotificationActivationCallback::Activate выполнен.

Внутрипроцессный сервер COM

В примере приложения ToastAndCallback выше приложение функционирует в качестве локального (или внепроцессного) COM-сервера. Это обозначено разделом реестра Windows LocalServer32, который используется для регистрации CLSID его coclass. Локальный COM-сервер размещает свои coclass в исполняемом двоичном файле (.exe).

Кроме того, (и, возможно, более вероятно), вы можете разместить свой coclass(es) в библиотеке динамических ссылок (.dll). COM-сервер в виде библиотеки DLL называется внутрипроцессным COM-сервером, и это обозначается CLSID, зарегистрированными с помощью ключа реестра Windows InprocServer32.

Вы можете начать задачу создания внутрипроцессного COM-сервера, создав проект в Microsoft Visual Studio. Создайте проект библиотекиDynamic-Link классической библиотеки Windows (DLL) Visual C++Visual C++.

Чтобы добавить поддержку C++/WinRT в новый проект, выполните действия, описанные в разделе Изменение проекта классического приложения Windows, чтобы добавить поддержку C++/WinRT.

Реализуйте экспорт coclass, фабрики классов и внутрипроцессного сервера

Откройте dllmain.cppи добавьте в него список кода, показанный ниже.

Если у вас уже есть библиотека DLL, реализующая классы среды выполнения Windows C++/WinRT, вы уже будете иметь функцию DllCanUnloadNow, показанную ниже. Если вы хотите добавить коклассы в эту библиотеку DLL, можно добавить функцию DllGetClassObject.

Если у вас нет библиотеки шаблонов среды выполнения Windows C++ (WRL) код, с которым вы хотите оставаться совместимым, можно удалить части WRL из показанного кода.

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

Поддержка слабых ссылок

См. также в разделе о слабых ссылках в C++/WinRT.

C++/WinRT (в частности, winrt::реализует шаблон базовой структуры) реализует IWeakReferenceSource, если тип реализует IInspectable (или любой интерфейс, производный от IInspectable).

Это связано с тем, что IWeakReferenceSource и IWeakReference предназначены для типов среды выполнения Windows. Таким образом, вы можете включить слабую поддержку ссылок для совместного класса, просто добавив winrt::Windows::Foundation::IInspectable (или интерфейс, производный от IInspectable) в реализацию.

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

Реализация COM-интерфейса, наследуемого от другого

Производность интерфейса — это функция классического COM (и она отсутствует намеренно в среде выполнения Windows). Ниже приведен пример того, как выглядит производность интерфейса.

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

Если вы пишете класс, которому нужно реализовать, например, как IFileSystemBindData, так и IFileSystemBindData2, то первый шаг в выражении этого — объявить, что реализуется только производный интерфейс .

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

Следующий шаг — убедиться, что QueryInterface успешно выполняется при вызове (прямо или косвенно) для IID_IFileSystemBindData (базового интерфейса) для экземпляра MyFileSystemBindData. Для этого необходимо предоставить специализацию для шаблона функции winrt::is_guid_of.

winrt::is_guid_of является вариативным, поэтому вы можете предоставить ему список интерфейсов. Вот как вы можете задать специализацию так, чтобы проверка для IFileSystemBindData2 также провела тест для 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.
}

Специализация winrt::is_guid_of должна быть идентична во всех файлах проекта и видима в момент, когда интерфейс используется winrt::реализует или winrt::delegate шаблоном. Как правило, вы помещаете его в общий файл заголовка.

Важные API