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

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

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

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

По умолчанию winrt::implements молча игнорирует классические COM-интерфейсы. Все вызовы QueryInterface (QI) для классических COM-интерфейсов поэтому завершаются ошибкой E_NOINTERFACE. По умолчанию winrt::implements поддерживает только интерфейсы 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

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

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

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

Note

В приведённом выше примере, даже после включения классической поддержки COM в клиенте (коде, использующем класс), если вы также не включили классическую поддержку COM в сервере (коде, реализующем класс), то вызов as<> в клиенте завершится сбоем, поскольку запрос 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) и фабрики классов. В примере приложения показано, как отправить toast-уведомление с кнопкой, нажатие на которую вызывает обратный вызов, а кокласс (который реализует COM-интерфейс INotificationActivationCallback) позволяет запускать приложение и вызывать его обратно, когда пользователь нажимает эту кнопку в toast-уведомлении.

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

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

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

Откройте pch.h и добавьте #include <unknwn.h> перед директивами include для всех заголовков 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() { }

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

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

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

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

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

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

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

Рекомендации по реализации методов 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, а также для отправки всплывающего уведомления, способного выполнить обратный вызов в ваше приложение.

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 либо непосредственно во всплывающем toast-уведомлении, либо в Центре уведомлений, и приложение будет запущено, будет создан экземпляр coclass и выполнен метод INotificationActivationCallback::Activate.

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

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

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

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

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

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

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

Если у вас уже есть библиотека DLL, реализующая классы C++/WinRT среда выполнения Windows, вы уже будете иметь функцию 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::implements) реализует 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::implements или winrt::delegate. Как правило, вы помещаете его в общий файл заголовка.

Важные API