Criar componentes COM com C++/WinRT

O C++/WinRT pode ajudá-lo a criar componentes clássicos de COM (ou coclasses), assim como ajuda você a criar classes Windows Runtime. Este tópico mostra como fazer isso.

Como o C++/WinRT se comporta, por padrão, em relação às interfaces COM

O modelo winrt::implements do C++/WinRT é a base da qual suas classes de runtime e fábricas de ativação derivam direta ou indiretamente.

Por padrão, winrt::implements ignora silenciosamente interfaces COM clássicas. Quaisquer chamadas de QueryInterface (QI) para interfaces COM clássicas, portanto, falharão retornando E_NOINTERFACE. Por padrão, winrt::implements dá suporte apenas a interfaces C++/WinRT.

  • winrt::IUnknown é uma interface C++/WinRT, portanto , winrt::implements dá suporte a interfaces baseadas em winrt::IUnknown.
  • Winrt::implements não dá suporte a ::IUnknown em si, por padrão.

Em um momento, você verá como superar os casos que não têm suporte por padrão. Mas primeiro aqui está um exemplo de código para ilustrar o que acontece por padrão.

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

E aqui está o código do cliente para consumir a classe Sample .

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

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

Habilitando o suporte ao COM clássico

A boa notícia é que tudo o que é necessário para causar winrt::implements para dar suporte a interfaces COM clássicas é incluir o unknwn.h arquivo de cabeçalho antes de incluir quaisquer cabeçalhos C++/WinRT.

Você pode fazer isso explicitamente ou indiretamente incluindo algum outro arquivo de cabeçalho, como ole2.h. Um método recomendado é incluir o wil\cppwinrt.h arquivo de cabeçalho, que faz parte das bibliotecas de implementação de Windows (WIL). O arquivo de cabeçalho wil\cppwinrt.h não apenas garante que unknwn.h seja incluído antes de winrt/base.h, como também faz os ajustes necessários para que C++/WinRT e WIL entendam as exceções e os códigos de erro um do outro.

Você pode então usar as<> para interfaces COM clássicas, e o código no exemplo acima será compilado.

Note

No exemplo acima, mesmo depois de habilitar o suporte a COM clássico no cliente (o código que consome a classe), se você também não tiver habilitado o suporte a COM clássico no servidor (o código que implementa a classe), a chamada a as<> no cliente irá travar porque o QI para IInitializeWithWindow falhará.

Uma classe local (não projetada)

Uma classe local é aquela que é implementada e consumida na mesma unidade de compilação (aplicativo ou outro binário); e, portanto, não há projeção para isso.

Aqui está um exemplo de uma classe local que implementa apenas interfaces COM clássicas.

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

Se você implementar esse exemplo, mas não habilitar o suporte clássico a COM, o código a seguir falha.

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

Novamente, IInitializeWithWindow não é reconhecido como uma interface COM, portanto, C++/WinRT a ignora. No caso do exemplo LocalObject , o resultado de ignorar interfaces COM significa que LocalObject não tem nenhuma interface. Mas cada classe COM deve implementar pelo menos uma interface.

Um exemplo simples de um componente COM

Aqui está um exemplo simples de um componente COM escrito usando C++/WinRT. Esta é uma listagem completa de um miniaplicativo, para que você possa testar o código se o colar em pch.h e main.cpp de um novo projeto Aplicativo de Console do 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());
}

Consulte também Usar componentes COM com C++/WinRT.

Um exemplo mais realista e interessante

O restante deste tópico explica como criar um projeto de aplicativo de console mínimo que usa C++/WinRT para implementar uma coclasse básica (componente COM ou classe COM) e uma fábrica de classes. O aplicativo de exemplo mostra como exibir uma notificação toast com um botão de callback, e a coclasse (que implementa a interface COM INotificationActivationCallback) permite que o aplicativo seja iniciado e receba o callback quando o usuário clicar nesse botão da notificação.

Mais informações sobre o recurso de notificação toast podem ser encontradas em Enviar uma notificação toast local. Nenhum dos exemplos de código nessa seção da documentação usa C++/WinRT, portanto, recomendamos que você prefira o código mostrado neste tópico.

Criar um projeto de aplicativo de console Windows (ToastAndCallback)

Comece criando um novo projeto no Microsoft Visual Studio. Crie um projeto Windows Aplicativo de Console (C++/WinRT) e nomeie-o ToastAndCallback.

Abra pch.h e adicione #include <unknwn.h> antes das diretivas de inclusão de quaisquer cabeçalhos C++/WinRT. Este é o resultado; você pode substituir o conteúdo do seu pch.h por esta listagem.

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

Abra main.cppe remova as diretivas de uso geradas pelo modelo de projeto. No lugar deles, insira o código a seguir (que nos fornece as bibliotecas, os cabeçalhos e os nomes de tipo de que precisamos). Este é o resultado; você pode substituir o conteúdo do seu main.cpp por esta listagem (também removemos o código main na listagem abaixo, pois substituiremos essa função mais tarde).

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

O projeto ainda não será compilado; depois de concluirmos a adição de código, você será solicitado a compilar e executar.

Implementar a coclasse e a fábrica de classes

No C++/WinRT, você implementa coclasses e fábricas de classes derivando do struct base winrt::implements . Imediatamente após as três diretivas de uso mostradas acima (e antes main), cole esse código para implementar o componente do ativador COM de notificação do sistema.

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

A implementação da coclass acima segue o mesmo padrão demonstrado nas APIs de Autor com C++/WinRT. Portanto, você pode usar a mesma técnica para implementar interfaces COM, bem como interfaces Windows Runtime. Componentes COM e classes Windows Runtime expõem seus recursos por meio de interfaces. Cada interface COM, em última análise, deriva da interface IUnknown . O Windows Runtime se baseia em COM—uma diferença é que as interfaces do Windows Runtime, em última análise, derivam da interface IInspectable (e IInspectable deriva da interface IUnknown).

No coclass no código acima, implementamos o método INotificationActivationCallback::Activate, que é chamado quando o usuário clica no botão de callback em uma notificação toast. Mas antes que essa função possa ser chamada, uma instância da coclasse precisa ser criada e esse é o trabalho da função IClassFactory::CreateInstance .

A coclass que acabamos de implementar é conhecida como ativadora COM para notificações e tem sua ID de classe (CLSID) na forma do callback_guid identificador (do tipo GUID) que você vê acima. Usaremos esse identificador posteriormente, na forma de um atalho de menu Iniciar e uma entrada do Registro Windows. O CLSID do ativador COM e o caminho para o servidor COM associado a ele (que é o caminho para o executável que estamos criando aqui) são o mecanismo pelo qual uma notificação toast sabe de qual classe deve criar uma instância quando seu botão de callback é clicado (quer a notificação seja clicada na Central de Ações ou não).

Práticas recomendadas para implementar métodos COM

As técnicas para tratamento de erros e para o gerenciamento de recursos podem andar lado a lado. É mais conveniente e prático usar exceções do que códigos de erro. E se você empregar o idioma RAII (resource-acquisition-is-initialization), poderá evitar a verificação explícita de códigos de erro e, em seguida, liberar explicitamente os recursos. Essas verificações explícitas tornam seu código mais complicado do que o necessário e oferece a bugs muitos lugares para ocultar. Em vez disso, use RAII e exceções de lançamento/captura. Dessa forma, suas alocações de recursos são seguras para exceções e seu código é simples.

No entanto, você não deve permitir que exceções escapem das implementações do método COM. Você pode garantir isso usando o noexcept especificador em seus métodos COM. Não há problema em que exceções sejam lançadas em qualquer ponto do grafo de chamadas do seu método, desde que você as trate antes que seu método termine. Se você usar noexcept, mas permitir que uma exceção escape do método, o aplicativo será encerrado.

Adicionar tipos e funções auxiliares

Nesta etapa, adicionaremos alguns tipos auxiliares e funções que o restante do código usa. Portanto, imediatamente antes main, adicione o seguinte.

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

Implementar as funções restantes e a função de ponto de entrada wmain

Exclua sua função main e, no lugar dela, cole esta listagem de código, que inclui o código para registrar sua coclass e, em seguida, exibir uma notificação toast capaz de acionar seu aplicativo novamente.

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

Como testar o aplicativo de exemplo

Compile o aplicativo e, em seguida, execute-o pelo menos uma vez como administrador para que o código de registro e de outras configurações seja executado. Uma maneira de fazer isso é executar Visual Studio como administrador e, em seguida, executar o aplicativo de Visual Studio. Clique com o botão direito do mouse Visual Studio na barra de tarefas para exibir a lista de saltos, clique com o botão direito do mouse Visual Studio na lista de saltos e clique em Executar como administrador. Concorde com o prompt e abra o projeto. Quando você executa o aplicativo, uma mensagem é exibida indicando se o aplicativo está ou não em execução como administrador. Se não estiver, o registro e outra configuração não serão executados. Esse registro e outra configuração precisam ser executados pelo menos uma vez para que o aplicativo funcione corretamente.

Esteja ou não executando o aplicativo como administrador, pressione 'T' para exibir uma notificação toast. Em seguida, você pode clicar no botão Call back ToastAndCallback, seja diretamente na notificação toast exibida ou pela Central de Ações, e seu aplicativo será lançado, a coclasse será instanciada e o método INotificationActivationCallback::Activate será executado.

Servidor COM em processo

O aplicativo de exemplo ToastAndCallback acima funciona como um servidor COM local (ou fora de processo). Isso é indicado pela chave localServer32 Windows Registro que você usa para registrar o CLSID de sua coclasse. Um servidor COM local hospeda sua coclass(es) dentro de um binário executável (um .exe).

Como alternativa (e indiscutivelmente mais provável), você pode optar por hospedar sua coclass(es) dentro de uma biblioteca de vínculo dinâmico (a .dll). Um servidor COM na forma de uma DLL é conhecido como um servidor COM no processo, e isso é indicado pelo registro de CLSIDs usando a chave InprocServer32 do Registro do Windows.

Você pode começar a tarefa de criar um servidor COM em processo criando um novo projeto em Microsoft Visual Studio. Crie um projeto Visual C++>da Área de Trabalho do Windows>de biblioteca de vínculo dinâmico (DLL).

Para adicionar suporte do C++/WinRT ao novo projeto, siga as etapas descritas em Modificar um projeto de aplicativo da área de trabalho Windows para adicionar suporte ao C++/WinRT.

Implementar as exportações de servidores coclass, class factory e in-proc

Abra dllmain.cppe adicione a ele a listagem de código mostrada abaixo.

Se você já tiver uma DLL que implemente classes de Windows Runtime C++/WinRT, você já terá a função DllCanUnloadNow mostrada abaixo. Se você quiser adicionar coclasses a essa DLL, poderá adicionar a função DllGetClassObject .

Se não tiver o código WRL (Biblioteca de Modelos) do Windows Runtime C++ com o qual você deseja manter-se compatível, você poderá remover as partes WRL do código mostrado.

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

Suporte para referências fracas

Veja também referências fracas em C++/WinRT.

C++/WinRT (especificamente, o modelo de estrutura base winrt::implements) implementa IWeakReferenceSource para você caso seu tipo implemente IInspectable (ou qualquer interface que derive de IInspectable).

Isso ocorre porque IWeakReferenceSource e IWeakReference foram projetados para tipos de Windows Runtime. Portanto, você pode habilitar o suporte a referência fraca para sua coclass simplesmente adicionando winrt::Windows::Foundation::IInspectable (ou uma interface derivada de IInspectable) à sua implementação.

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

Implementar uma interface COM que deriva de outra

A derivação de interface é um recurso do COM clássico (e acontece que ele está ausente, intencionalmente, do Windows Runtime). Aqui está um exemplo de como fica a derivação de interface.

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

Se você estiver escrevendo uma classe que precise implementar, por exemplo, IFileSystemBindData e IFileSystemBindData2, a primeira etapa para expressar isso é declarar que você implementa apenas a interface derivada , assim.

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

A próxima etapa é garantir que QueryInterface tenha êxito quando for chamado (direta ou indiretamente) para IID_IFileSystemBindData (a interface base ) em uma instância do MyFileSystemBindData. Você faz isso ao fornecer uma especialização para o modelo de função winrt::is_guid_of.

winrt::is_guid_of é variádica e, portanto, você pode passar a ela uma lista de interfaces. Veja como você forneceria uma especialização para que uma verificação de IFileSystemBindData2 também inclua um teste para 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.
}

A especialização de winrt::is_guid_of deve ser idêntica em todos os arquivos do projeto e estar visível no ponto em que a interface é usada pela template winrt::implements ou pela template winrt::delegate. Normalmente, você o colocaria em um arquivo de cabeçalho comum.

APIs importantes