Creare componenti COM con C++/WinRT

C++/WinRT non solo ti consente di creare classi di Windows Runtime, ma ti aiuta anche a creare componenti COM (Component Object Model) classici (o coclassi). Questo argomento spiega come fare.

Comportamento predefinito di C++/WinRT rispetto alle interfacce COM

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

Per impostazione predefinita, winrt::implements ignora senza avvisi le interfacce COM classiche. Le chiamate a QueryInterface (QI) per le interfacce COM classiche hanno quindi esito negativo con errore 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 per impostazione predefinita.

Tra poco sarà possibile vedere come superare i casi non supportati per impostazione predefinita. Ma prima di tutto, ecco un esempio di codice per illustrare ciò che 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();
    }
}

Di seguito è riportato il codice client per l'utilizzo della 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 per far sì che winrt::implements supporti le classiche interfacce COM è sufficiente includere il file di intestazione unknwn.h prima di includere qualsiasi intestazione C++/WinRT.

Questa operazione può essere eseguita in modo esplicito oppure indirettamente, includendo un altro file di intestazione come ole2.h. Un metodo consigliato è quello di includere il file di intestazione wil\cppwinrt.h, che fa parte delle librerie di implementazione di Windows (WIL). Il file di intestazione wil\cppwinrt.h non consente solo di assicurarsi che unknwn.h sia incluso prima winrt/base.h, ma configura il tutto in modo che C++/WinRT e le librerie WIL siano in grado di comprendere i rispettivi codici di errore ed eccezioni.

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

Nota

Nell'esempio precedente, anche dopo aver abilitato il supporto COM classico nel client (il codice che usa la classe), se non è anche stato abilitato il supporto COM classico nel server (il codice che implementa la classe), la chiamata a come<> nel client si arresterà in modo anomalo perché la QI per IInitializeWithWindow avrà esito negativo.

Classe locale (non proiettata)

La classe locale può essere sia implementata che usata nella stessa unità di compilazione (app o altro file binario); pertanto non c'è proiezione.

Ecco un esempio di classe locale che implementa solo le 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 è riconosciuta come interfaccia COM, quindi C++/WinRT la ignora. Nel caso dell'esempio di LocalObject, il risultato di ignorare le interfacce COM significa che LocalObject non ha alcuna interfaccia. Tuttavia, ogni classe COM deve implementare almeno un'interfaccia.

Un esempio semplice di un componente COM

Ecco un esempio semplice di un componente COM, scritto usando C++/WinRT. Questo è un listato completo di una piccola applicazione di cui puoi provare il codice incollandolo nei file pch.h e main.cpp di un nuovo progetto 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ù interessante e realistico

La parte restante di questo argomento descrive come creare 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 illustra come fornire una notifica di tipo avviso popup che include un pulsante di callback e la coclasse (che implementa l'interfaccia COM INotificationActivationCallback) consente di avviare ed eseguire il callback dell'applicazione quando l'utente fa clic su questo pulsante sull'avviso popup.

Altre informazioni sull'area della funzionalità di notifica di tipo avviso popup sono disponibili in Inviare una notifica di tipo avviso popup locale. Tuttavia, nessun esempio di codice in quella sezione della documentazione usa C++/WinRT e quindi ti consigliamo di scegliere il codice riportato in questo argomento.

Creare un progetto Applicazione console di Windows (ToastAndCallback)

Per iniziare, crea un nuovo progetto in Microsoft Visual Studio. Crea un progetto Applicazione console di Windows (C++/WinRT) e denominalo ToastAndCallback.

Apri pch.h e aggiungi #include <unknwn.h> prima degli include per qualsiasi intestazione C++/WinRT. Ecco il risultato; puoi sostituire il contenuto di pch.h con questo listato.

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

Apri main.cpp e rimuovi le direttive using generate dal modello del progetto. In sostituzione inserisci 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 riportato di seguito perché sostituiremo quella funzione in un secondo momento).

// 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 sarà ancora compilato; dopo che abbiamo finito di aggiungere il codice ti verrà richiesto di eseguire la compilazione e l'esecuzione.

Implementare la coclasse e la class factory

In C++/WinRT le coclassi e le class factory vengono implementate mediante derivazione dallo struct di base winrt::implements. Immediatamente dopo le tre direttive using illustrate in precedenza (e prima di main), incolla questo codice per implementare il componente attivatore COM della notifica di tipo avviso popup.

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. Puoi quindi usare la stessa tecnica per implementare le interfacce COM e le interfacce di Windows Runtime. I componenti COM e le classi di Windows Runtime espongono le proprie funzionalità tramite interfacce. Ogni interfaccia COM deriva in ultima analisi dall'interfaccia IUnknown. Windows Runtime è basato su COM, con la differenza che le interfacce di Windows Runtime derivano in ultima analisi dall'interfaccia IInspectable (e IInspectable deriva da IUnknown).

Nella coclasse nel codice precedente implementiamo il metodo INotificationActivationCallback::Activate, ovvero la funzione chiamata quando l'utente fa clic sul pulsante di callback su una notifica di tipo avviso popup. Prima che questa funzione possa essere chiamata, devi creare un'istanza della coclasse; questa azione viene eseguita dalla funzione IClassFactory::CreateInstance.

La coclasse che abbiamo appena implementato è nota come attivatore COM per le notifiche e ha il suo ID di classe (CLSID) sotto forma di identificatore callback_guid (di tipo GUID) che puoi vedere sopra. Useremo quell'identificatore in seguito, sotto forma di collegamento al menu Start e voce del Registro di sistema di Windows. Il CLSID dell'attivatore COM e il percorso del server COM associato, ovvero il percorso del file eseguibile che stiamo creando qui, sono il meccanismo mediante il quale una notifica di avviso popup sa di quale classe creare un'istanza quando viene selezionato il relativo pulsante di callback (sia che la notifica venga selezionata in Centro notifiche o meno).

Procedure consigliate per implementare i metodi COM

Le tecniche per la gestione degli errori e per la gestione delle risorse possono procedere in stretta associazione. È più conveniente e pratico utilizzare le eccezioni rispetto ai codici di errore. Se utilizzi resource-acquisition-is-initialization (RAII), puoi evitare di controllare esplicitamente i codici di errore e quindi rilasciare esplicitamente le risorse. Questi controlli espliciti rendono il codice più complesso del necessario e offrono ai bug più posizioni in cui nascondersi. In alternativa, utilizza RAII e le eccezioni throw/catch. In questo modo le allocazioni delle risorse sono indipendenti dalle eccezioni e il codice è semplice.

Non devi, tuttavia, consentire alle eccezioni di usare la sequenza di escape per le implementazioni del metodo COM. A questo scopo usa l'identificatore noexcept per i metodi COM. È accettabile che le eccezioni vengano generate in un punto qualsiasi del grafico chiamate del metodo, a condizione che le gestisci prima dell'uscita dal metodo. Se usi noexcept, ma consenti a un'eccezione di eseguire la sequenza di escape per il metodo, l'applicazione verrà terminata.

Aggiungere le funzioni e i tipi di helper

In questo passaggio aggiungeremo alcune funzioni e alcuni tipi di helper di cui il resto del codice fa uso. Immediatamente prima di main aggiungi 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 coclasse e quindi per fornire un avviso popup 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 ed eseguila almeno una volta come amministratore per far si che vengano eseguite la registrazione e le altre impostazioni. Un modo per farlo è eseguire Visual Studio come amministratore ed eseguire l'app da Visual Studio. Fai clic con il pulsante destro del mouse su Visual Studio nella barra delle applicazioni per visualizzare la jump list, fai clic con il pulsante destro del mouse su Visual Basic sulla jump list, quindi fai clic su Esegui come amministratore. Conferma il prompt e quindi apri il progetto. Quando esegui l'applicazione, viene visualizzato un messaggio che indica se l'applicazione è in esecuzione come amministratore. In caso contrario, la registrazione e le altre impostazioni non verranno eseguite. La registrazione e le altre impostazioni devono essere eseguite almeno una volta affinché l'applicazione funzioni correttamente.

Indipendentemente dal fatto che tu stia eseguendo l'applicazione come amministratore, premi "T" per visualizzare un avviso popup. Puoi quindi fare clic sul pulsante Richiama ToastAndCallback direttamente dalla notifica del tipo di avviso popup o dal Centro notifiche; verrà avviata l'applicazione, verrà istanziata la coclasse e verrà eseguito il metodo INotificationActivationCallback::Activate.

Server COM in-process

L'app di esempio precedente ToastAndCallback funziona come server COM locale (o out-of-process). Questa condizione è indicata dalla chiave del Registro di sistema di Windows LocalServer32 usata per registrare il CLSID della relativa coclasse. Un server COM locale ospita la coclasse o le coclassi all'interno di un file eseguibile binario (.exe).

In alternativa e verosimilmente con maggiore probabilità, puoi scegliere di ospitare le coclassi all'interno di una libreria di collegamento dinamico (.dll). Un server COM sotto forma di DLL è noto come server COM in-process ed è indicato dai CLSID in fase di registrazione utilizzando la chiave del registro di sistema di Windows InprocServer32.

Puoi 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>Libreria di collegamento dinamico (DLL).

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

Implementare la coclasse, la class factory e le esportazioni del server in-process

Apri dllmain.cpp e aggiungi il listato di codice illustrato di seguito.

Se disponi già di una DLL che implementa le classi di Windows Runtime C++/WinRT, disponi già della funzione DllCanUnloadNow illustrata di seguito. Se vuoi aggiungere coclassi a quella DLL, puoi aggiungere la funzione DllGetClassObject.

Se non disponi di un codice Windows Runtime C++ Template Library (WRL) con cui desideri mantenere la compatibilità, puoi rimuovere le parti WRL dal codice illustrato.

// 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 dello struct di base winrt::implements) implementa IWeakReferenceSource automaticamente se il tuo tipo implementa IInspectable (o qualsiasi interfaccia che deriva da IInspectable).

Ciò è dovuto al fatto che IWeakReferenceSource e IWeakReference sono progettati per i tipi di Windows Runtime. Puoi quindi 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'altra

La derivazione dell'interfaccia è una funzionalità COM classica (e sembra assente, intenzionalmente, da Windows Runtime). Ecco un esempio di come si presenta la derivazione dell’interfaccia.

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

Se si sta scrivendo una classe che deve implementare, ad esempio, sia IFileSystemBindData che IFileSystemBindData2, il primo passaggio da fare è dichiarare di implementare solo l'interfaccia derivata, in questo modo.

// 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 nell’assicurarsi che Query­Interface abbia esito positivo quando viene chiamata (direttamente o indirettamente) per IID_IFileSystemBindData (l'interfaccia di base) su un'istanza di MyFileSystemBindData. Lo si fa fornendo una specializzazione per il modello di funzione winrt::is_guid_of.

winrt::is_guid_of è variabile, quindi è possibile specificare un elenco di interfacce. Ecco come fornire una specializzazione in modo che un controllo 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::delegate. In genere lo si inserisce in un comune file di intestazione.

API importanti