Creare componenti COM con C++/WinRT

C++/WinRT consente di creare componenti COM (Component Object Model) classici (o coclassi), proprio come consente di creare Windows Runtime classi. Questo argomento spiega come fare.

Come si comporta C++/WinRT, per impostazione predefinita, rispetto alle interfacce COM

Il modello winrt::implements di C++/WinRT è la base da cui derivano direttamente o indirettamente le classi di runtime e le factory di attivazione.

Per impostazione predefinita, winrt::implements ignora automaticamente le interfacce COM classiche. Qualsiasi chiamata di QueryInterface (QI) per le interfacce COM classiche avrà di conseguenza esito negativo con E_NOINTERFACE. Per impostazione predefinita, winrt::implements supporta solo le interfacce C++/WinRT.

  • winrt::IUnknown è un'interfaccia C++/WinRT, quindi winrt::implements supporta le interfacce basate su winrt::IUnknown.
  • winrt::implements non supporta ::IUnknown stesso, per impostazione predefinita.

In un momento si vedrà come superare i casi che non sono supportati per impostazione predefinita. Ma prima di tutto ecco un esempio di codice per illustrare cosa accade per impostazione predefinita.

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

Ed ecco il codice client per usare la 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); 

Abilitazione del supporto COM classico

La buona notizia è che tutto ciò che serve per fare in modo che winrt::implements supporti le interfacce COM classiche consiste nell'includere il unknwn.h file di intestazione prima di includere eventuali intestazioni C++/WinRT.

È possibile eseguire questa operazione in modo esplicito o indiretto includendo altri file di intestazione, ole2.had esempio . Un metodo consigliato consiste nell'includere il wil\cppwinrt.h file di intestazione, che fa parte delle librerie di implementazione di Windows (WIL). Il file di intestazione wil\cppwinrt.h non solo assicura che unknwn.h sia incluso prima di winrt/base.h, ma predispone anche il necessario affinché C++/WinRT e WIL possano comprendere reciprocamente le eccezioni e i codici di errore.

È quindi possibile as<> per le interfacce COM classiche e il codice nell'esempio precedente verrà compilato.

Note

Nell'esempio sopra, anche dopo aver abilitato il supporto COM classico nel client (il codice che utilizza la classe), se non hai abilitato anche il supporto COM classico nel server (il codice che implementa la classe), la chiamata a as<> nel client causerà un arresto anomalo perché la QI per IInitializeWithWindow non riuscirà.

Classe locale (non proiettata)

Una classe locale è una classe implementata e utilizzata nella stessa unità di compilazione (app o altro file binario); e quindi non c'è alcuna proiezione per esso.

Di seguito è riportato un esempio di classe locale che implementa solo interfacce COM classiche.

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

Se si implementa questo esempio, ma non si abilita il supporto COM classico, il codice seguente ha esito negativo.

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

Anche in questo caso , IInitializeWithWindow non viene riconosciuto come interfaccia COM, quindi C++/WinRT lo ignora. Nel caso dell'esempio LocalObject , il risultato dell'ignorare le interfacce COM significa che LocalObject non ha alcuna interfaccia. Tuttavia, ogni classe COM deve implementare almeno un'interfaccia.

Esempio semplice di un componente COM

Ecco un semplice esempio di un componente COM scritto con C++/WinRT. Si tratta di un elenco completo di una mini-applicazione, quindi è possibile provare il codice se lo incollare in pch.h e main.cpp di un nuovo progetto di applicazione console di 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());
}

Vedi anche Utilizzare componenti COM con C++/WinRT.

Un esempio più realistico e interessante

Nella parte restante di questo argomento viene illustrata la creazione di un progetto di applicazione console minimo che usa C++/WinRT per implementare una coclasse di base (componente COM o classe COM) e una class factory. L'applicazione di esempio mostra come inviare una notifica toast con un pulsante di richiamo e la coclasse (che implementa l'interfaccia COM INotificationActivationCallback) consente di avviare e richiamare l'applicazione quando l'utente fa clic su quel pulsante nella notifica toast.

Ulteriori informazioni sulla funzionalità delle notifiche popup sono disponibili in Inviare una notifica popup locale. Nessuno degli esempi di codice in tale sezione della documentazione usa tuttavia C++/WinRT, quindi ti consigliamo di preferire il codice illustrato in questo argomento.

Creare un progetto applicazione console di Windows (ToastAndCallback)

Per iniziare, crea un nuovo progetto in Microsoft Visual Studio. Creare un progetto applicazione console di Windows (C++/WinRT) e denominarlo ToastAndCallback.

Apri pch.h e aggiungi #include <unknwn.h> prima delle direttive di inclusione per eventuali header C++/WinRT. Ecco il risultato; puoi sostituire il contenuto del tuo pch.h con questo elenco.

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

Apri main.cpp, e rimuovi le direttive using generate dal modello di progetto. Al loro posto, inserire il codice seguente (che fornisce le librerie, le intestazioni e i nomi dei tipi necessari). Ecco il risultato; puoi sostituire il contenuto di main.cpp con questo listato (abbiamo anche rimosso il codice da main nel listato seguente, perché sostituiremo quella funzione più avanti).

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

Il progetto non verrà ancora compilato; Al termine dell'aggiunta del codice, verrà richiesto di compilare ed eseguire.

Implementare la coclasse e la class factory

In C++/WinRT implementi coclassi e class factory, derivando dallo struct di base winrt::implements . Subito dopo le tre direttive using mostrate sopra (e prima di main), incolli questo codice per implementare il componente COM di attivazione delle notifiche 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;
    }
};

L'implementazione della coclasse precedente segue lo stesso modello illustrato in Creare API con C++/WinRT. È quindi possibile usare la stessa tecnica per implementare interfacce COM e interfacce Windows Runtime. I componenti COM e le classi Windows Runtime espongono le relative funzionalità tramite interfacce. Ogni interfaccia COM deriva in definitiva dall'interfaccia IUnknown . Windows Runtime si basa su COM: una differenza è che le interfacce di Windows Runtime derivano in ultima analisi da l'interfaccia IInspectable (e IInspectable deriva da IUnknown).

Nella coclasse nel codice sopra viene implementato il metodo INotificationActivationCallback::Activate, ovvero la funzione che viene chiamata quando l'utente fa clic sul pulsante di callback in una notifica popup. Ma prima di poter chiamare tale funzione, è necessario creare un'istanza della coclasse ed è il processo della funzione IClassFactory::CreateInstance .

La coclasse appena implementata è nota come attivatore COM per le notifiche e ha il relativo ID classe (CLSID) sotto forma di callback_guid identificatore (di tipo GUID) visualizzato in precedenza. Questo identificatore verrà usato in un secondo momento sotto forma di scelta rapida del menu Start e di una voce del Registro di sistema Windows. Il CLSID dell'attivatore COM e il percorso del server COM associato (ovvero il percorso del file eseguibile che stiamo generando qui) costituiscono il meccanismo tramite cui una notifica popup sa di quale classe creare un'istanza quando si fa clic sul relativo pulsante di callback (indipendentemente dal fatto che si faccia clic sulla notifica nel Centro notifiche oppure no).

Procedure consigliate per l'implementazione di metodi COM

Le tecniche per la gestione degli errori e per la gestione delle risorse possono passare a portata di mano. È più pratico e pratico usare eccezioni rispetto ai codici di errore. E se si usa l'idioma resource acquisition is initialization (RAII), allora è possibile evitare sia di controllare esplicitamente i codici di errore sia di rilasciare esplicitamente le risorse. Tali controlli espliciti rendono il codice più contorto del necessario e fornisce bug in abbondanza di luoghi da nascondere. Usare invece RAII e generare/intercettare le eccezioni. In questo modo, le allocazioni delle risorse sono sicure per le eccezioni e il codice è semplice.

Tuttavia, non devi permettere che le eccezioni si propaghino all'esterno delle implementazioni dei metodi COM. È possibile assicurarsi che usando l'identificatore noexcept nei metodi COM. Va bene che vengano generate eccezioni in qualsiasi punto del grafo delle chiamate del tuo metodo, purché vengano gestite prima che il tuo metodo termini. Se si usa noexcept, ma si consente quindi a un'eccezione di eseguire l'escape del metodo, l'applicazione terminerà.

Aggiungere tipi e funzioni di supporto

In questo passaggio verranno aggiunti alcuni tipi di helper e funzioni usati dal resto del codice. Quindi, immediatamente prima di main, aggiungere quanto segue.

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

Implementare le funzioni rimanenti e la funzione del punto di ingresso wmain

Elimina la funzione main e, al suo posto, incolla questo listato di codice, che include il codice per registrare la classe COM e quindi visualizzare una notifica toast in grado di eseguire il callback dell'applicazione.

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

Come testare l'applicazione di esempio

Compila l'applicazione, quindi eseguila almeno una volta come amministratore, in modo da eseguire il codice di registrazione e altro codice di configurazione. Un modo per eseguire questa operazione consiste nell'eseguire Visual Studio come amministratore e quindi eseguire l'app da Visual Studio. Fare clic con il pulsante destro del mouse Visual Studio nella barra delle applicazioni per visualizzare la jump list, fare clic con il pulsante destro del mouse Visual Studio nella jump list e quindi scegliere Esegui come amministratore. Accettare la richiesta e quindi aprire il progetto. Quando si esegue l'applicazione, viene visualizzato un messaggio che indica se l'applicazione è in esecuzione come amministratore. In caso contrario, la registrazione e altre impostazioni non verranno eseguite. Per il corretto funzionamento dell'applicazione, la registrazione e altre configurazioni deve essere eseguita almeno una volta.

Che l'applicazione sia in esecuzione con privilegi di amministratore o meno, premere 'T' per visualizzare una notifica toast. È quindi possibile fare clic sul pulsante Richiama ToastAndCallback sia direttamente dalla notifica popup visualizzata, sia dal Centro notifiche; verranno avviati l'applicazione, creata la coclasse ed eseguito il metodo INotificationActivationCallback::Activate.

Server COM in-process

L'app di esempio ToastAndCallback sopra funziona come server COM locale (o out-of-process). Ciò è indicato dalla chiave del Registro di sistema LocalServer32 Windows usata per registrare il CLSID della relativa coclasse. Un server COM locale ospita una o più coclassi all'interno di un binario eseguibile (un .exe).

In alternativa (e probabilmente più probabile), è possibile scegliere di ospitare la coclasse (es) all'interno di una libreria di collegamento dinamico (a .dll). Un server COM sotto forma di DLL è noto come server COM in-process ed è indicato dai CLSID registrati tramite la chiave del Registro di sistema InprocServer32 Windows.

È possibile iniziare l'attività di creazione di un server COM in-process creando un nuovo progetto in Microsoft Visual Studio. Crea un progetto Visual C++>Windows Desktop>Dynamic-Link Library (DLL).

Per aggiungere il supporto C++/WinRT al nuovo progetto, seguire i passaggi descritti in Modificare un progetto di applicazione desktop Windows per aggiungere il supporto C++/WinRT.

Implementa le esportazioni di coclass, class factory e server in-processo

Aprire dllmain.cppe aggiungerlo al listato di codice illustrato di seguito.

Se hai già una DLL che implementa le classi di Windows Runtime C++/WinRT, avrai già la funzione DllCanUnloadNow illustrata di seguito. Se si desidera aggiungere coclassi a tale DLL, è possibile aggiungere la funzione DllGetClassObject .

Se non si dispone di codice Windows Runtime C++ Template Library (WRL) esistente con cui si desidera mantenere la compatibilità, è possibile rimuovere dal codice mostrato le parti di 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();
    }
}

Supporto per riferimenti deboli

Vedi anche Riferimenti deboli in C++/WinRT.

C++/WinRT (in particolare, il modello di struct di base winrt::implements ) implementa IWeakReferenceSource se il tipo implementa IInspectable (o qualsiasi interfaccia che deriva da IInspectable).

Ciò è dovuto al fatto che IWeakReferenceSource e IWeakReference sono progettati per i tipi Windows Runtime. È quindi possibile attivare il supporto dei riferimenti deboli per la coclasse semplicemente aggiungendo winrt::Windows::Foundation::IInspectable (o un'interfaccia che deriva da IInspectable) all'implementazione.

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

Implementare un'interfaccia COM che deriva da un altro

La derivazione dell'interfaccia è una funzionalità di COM classico (e sembra essere assente, intenzionalmente, dalla Windows Runtime). Ecco un esempio dell'aspetto della derivazione dell'interfaccia.

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

Se si scrive una classe che deve implementare, ad esempio, sia IFileSystemBindData che IFileSystemBindData2, il primo passaggio per esprimere che è quello di dichiarare di implementare solo l'interfaccia derivata , come illustrato di seguito.

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

Il passaggio successivo consiste nel garantire che QueryInterface abbia esito positivo quando viene chiamato (direttamente o indirettamente) per IID_IFileSystemBindData (interfaccia di base ) rispetto a un'istanza di MyFileSystemBindData. Per farlo, è necessario fornire una specializzazione per il template di funzione winrt::is_guid_of.

winrt::is_guid_of è variadica, quindi puoi passarle un elenco di interfacce. Ecco come fornire una specializzazione in modo che un controllo per IFileSystemBindData2 includa anche un test per 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.
}

La specializzazione di winrt::is_guid_of deve essere identica in tutti i file del progetto e visibile nel punto in cui l'interfaccia viene usata dal modello winrt::implements o winrt::d elegate . Di solito lo si inserisce in un file header comune.

API importanti