Skapa COM-komponenter med C++/WinRT

C++/WinRT kan hjälpa dig att skapa com-komponenter (eller samklasser) för klassisk komponentobjektmodell på samma sätt som det hjälper dig att skapa Windows Runtime klasser. Det här avsnittet visar hur.

Hur C++/WinRT fungerar som standard med avseende på COM-gränssnitt

C++/WinRT:s winrt::implements-mall är den bas som dina runtimeklasser och aktiveringsfabriker direkt eller indirekt härleds från.

Som standard ignorerar winrt::implementer tyst klassiska COM-gränssnitt. Alla QueryInterface-anrop (QI) för klassiska COM-gränssnitt misslyckas därför med E_NOINTERFACE. Som standard stöder winrt::implements endast C++/WinRT-gränssnitt.

  • winrt::IUnknown är ett C++/WinRT-gränssnitt, så winrt::implements stöder winrt::IUnknown-baserade gränssnitt.
  • winrt::implements stöder inte ::IUnknown själv som standard.

Om en stund får du se hur du kan lösa de fall som inte stöds som standard. Men först här är ett kodexempel för att illustrera vad som händer som standard.

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

Och här är klientkoden för att använda exempelklassen .

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

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

Aktivera klassiskt COM-stöd

Den goda nyheten är att allt som krävs för att få winrt::implements att stödja klassiska COM-gränssnitt är att inkludera huvudfilen unknwn.h innan du inkluderar några C++/WinRT-rubrikfiler.

Du kan göra det explicit eller indirekt genom att inkludera någon annan rubrikfil, till exempel ole2.h. En rekommenderad metod är att inkludera wil\cppwinrt.h huvudfilen, som är en del av Windows Implementeringsbibliotek (WIL). Huvudfilen wil\cppwinrt.h ser inte bara till att unknwn.h den ingår före winrt/base.h, den konfigurerar även saker så att C++/WinRT och WIL förstår varandras undantag och felkoder.

Du kan sedan as<> för klassiska COM-gränssnitt, och koden i exemplet ovan kommer att kompilera.

Note

I exemplet ovan, även efter aktivering av klassiskt COM-stöd i klienten (koden som använder klassen), om du inte också har aktiverat klassiskt COM-stöd på servern (koden som implementerar klassen), kraschar anropet till som<> i klienten eftersom QI för IInitializeWithWindow misslyckas.

En lokal klass (ej projicerad)

En lokal klass är en klass som både implementeras och används i samma kompileringsenhet (app eller annan binär fil); så det finns ingen prognos för det.

Här är ett exempel på en lokal klass som endast implementerar klassiska COM-gränssnitt.

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

Om du implementerar det exemplet, men inte aktiverar klassiskt COM-stöd, misslyckas följande kod.

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

Återigen identifieras inte IInitializeWithWindow som ett COM-gränssnitt, så C++/WinRT ignorerar det. När det gäller Exemplet LocalObject innebär resultatet av att ignorera COM-gränssnitt att LocalObject inte har några gränssnitt alls. Men varje COM-klass måste implementera minst ett gränssnitt.

Ett enkelt exempel på en COM-komponent

Här är ett enkelt exempel på en COM-komponent som skrivits med C++/WinRT. Det här är en fullständig lista över ett miniprogram, så du kan prova koden om du klistrar in den i pch.h och main.cpp för ett nytt Windows Konsolprogram (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());
}

Mer information finns i Använda COM-komponenter med C++/WinRT.

Ett mer realistiskt och intressant exempel

Resten av det här avsnittet går igenom hur du skapar ett minimalt konsolprogramprojekt som använder C++/WinRT för att implementera en grundläggande coclass (COM-komponent eller COM-klass) och klassfabrik. Exempelprogrammet visar hur du skickar ett toast-meddelande med en callback-knapp, och coclassen (som implementerar INotificationActivationCallback COM-gränssnittet) gör att programmet kan startas och få ett återanrop när användaren klickar på den knappen i toast-meddelandet.

Mer bakgrund om funktionsområdet för popup-meddelanden finns i Skicka ett lokalt popup-meddelande. Inget av kodexemplen i det avsnittet i dokumentationen använder dock C++/WinRT, så vi rekommenderar att du föredrar den kod som visas i det här avsnittet.

Skapa ett Windows Console Application-projekt (ToastAndCallback)

Börja med att skapa ett nytt projekt i Microsoft Visual Studio. Skapa ett Windows konsolprogramprojekt (C++/WinRT) och ge det namnet ToastAndCallback.

Öppna pch.hoch lägg till #include <unknwn.h> före inkluderingen för C++/WinRT-huvuden. Här är resultatet; du kan ersätta innehållet i din pch.h med den här listan.

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

Öppna main.cppoch ta bort de användningsdirektiv som projektmallen genererar. I deras ställe infogar du följande kod (som ger oss de libs, rubriker och typnamn som vi behöver). Här är resultatet; du kan ersätta innehållet i din main.cpp med den här listan (vi har också tagit bort koden från main listan nedan, eftersom vi kommer att ersätta den funktionen senare).

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

Projektet kommer inte att byggas än. När vi har lagt till kod uppmanas du att skapa och köra.

Implementera coclass- och klassfabriken

I C++/WinRT implementerar du samklasser och klassfabriker genom att härleda från winrt::implements base struct. Omedelbart efter de tre using-direktiven som visas ovan (och innan main) klistrar du in följande kod för att implementera din COM-aktiveringskomponent för toast-meddelanden.

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

Implementeringen av coclass ovan följer samma mönster som visas i Api:er för författare med C++/WinRT. Du kan därför använda samma teknik för att implementera COM-gränssnitt och Windows Runtime gränssnitt. COM-komponenter och Windows Runtime klasser exponerar sina funktioner via gränssnitt. Varje COM-gränssnitt härleds slutligen från gränssnittet IUnknown . Windows Runtime baseras på COM – en skillnad är att Windows Runtime-gränssnitt ytterst härstammar från IInspectable-gränssnittet (och IInspectable härstammar från IUnknown).

I coclassen i koden ovan implementerar vi metoden INotificationActivationCallback::Activate, som är funktionen som anropas när användaren klickar på callback-knappen i ett toast-meddelande. Men innan den funktionen kan anropas måste en instans av coclass skapas, och det är jobbet för funktionen IClassFactory::CreateInstance .

Den coclass som vi precis implementerade kallas COM-aktivatorn för meddelanden och har dess klass-ID (CLSID) i form av identifieraren callback_guid (av typen GUID) som du ser ovan. Vi kommer att använda identifieraren senare, i form av en genväg till Start-menyn och en Windows registerpost. COM-aktiverarens CLSID och sökvägen till den associerade COM-servern (det vill säga sökvägen till den körbara fil som vi bygger här) är den mekanism som gör att en toastavisering vet vilken klass den ska skapa en instans av när dess callback-knapp klickas, oavsett om aviseringen klickas i Åtgärdscenter eller inte.

Metodtips för att implementera COM-metoder

Tekniker för felhantering och resurshantering kan gå hand i hand. Det är enklare och mer praktiskt att använda undantag än felkoder. Och om du använder RAII-idiomet (resource-acquisition-is-initialization), kan du undvika att uttryckligen kontrollera felkoder och sedan uttryckligen frigöra resurser. Sådana explicita kontroller gör din kod mer invecklad än nödvändigt, och den ger buggar många platser att dölja. Använd i stället RAII och kasta/fånga undantag. På så sätt är dina resursallokeringar undantagssäkra och koden är enkel.

Du får dock inte tillåta undantag för att komma undan dina COM-metodimplementeringar. Du kan säkerställa det genom att använda specificeraren noexcept på dina COM-metoder. Det är ok att undantag utlöses var som helst i anropsdiagrammet för din metod, så länge du hanterar dem innan metoden avslutas. Om du använder noexcept, men sedan tillåter ett undantag från din metod, avslutas programmet.

Lägga till hjälptyper och funktioner

I det här steget lägger vi till några hjälptyper och funktioner som resten av koden använder. Lägg därför till följande direkt före 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;
}

Implementera de återstående funktionerna, och startpunktsfunktionen wmain

Ta bort funktionen main, och klistra i stället in den här kodlistan, som innehåller kod för att registrera din coclass och sedan skicka ett toast-meddelande som kan anropa ditt program.

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

Testa exempelprogrammet

Skapa programmet och kör det sedan minst en gång som administratör för att få registreringen och annan konfigurationskod att köras. Ett sätt att göra det är att köra Visual Studio som administratör och sedan köra appen från Visual Studio. Högerklicka på Visual Studio i aktivitetsfältet för att visa snabblistan, högerklicka på Visual Studio i hopplistan och klicka sedan på Kör som administratör. Godkänn uppmaningen och öppna sedan projektet. När du kör programmet visas ett meddelande som anger om programmet körs som administratör eller inte. Om så inte är fallet genomförs varken registreringen eller övrig konfiguration. Registreringen och den andra konfigurationen måste köras minst en gång för att programmet ska fungera korrekt.

Oavsett om du kör programmet som administratör eller inte, trycker du på ”T” för att visa ett toast-meddelande. Du kan sedan klicka på knappen Call back ToastAndCallback, antingen direkt från toastnotisen som visas eller från Åtgärdscenter, och programmet kommer att startas, coclassen instansieras och metoden INotificationActivationCallback::Activate köras.

COM-server i processen

ToastAndCallback-exempelappen ovan fungerar som en lokal (eller out-of-process) COM-server. Detta indikeras av registernyckeln LocalServer32 i Windows-registret som du använder för att registrera CLSID:t för dess coclass. En lokal COM-server är värd för sin coclass(es) i en körbar binärfil (en .exe).

Alternativt (och förmodligen mer sannolikt) kan du välja att vara värd för dina coclass(es) i ett dynamiskt länkbibliotek (a .dll). En COM-server i form av en DLL kallas för en processbaserad COM-server, och det indikeras av att CLSID:er registreras med hjälp av InprocServer32-Windows registernyckel.

Du kan börja skapa en com-server i processen genom att skapa ett nytt projekt i Microsoft Visual Studio. Skapa ett projekt för Visual C++>Windows Desktop>Dynamic-Link Library (DLL).

Om du vill lägga till stöd för C++/WinRT i det nya projektet följer du stegen som beskrivs i Ändra ett Windows Desktop-programprojekt för att lägga till C++/WinRT-stöd.

Implementera coclass-, klassfabriks- och in-proc-serverexporter

Öppna dllmain.cppoch lägg till kodlistan som visas nedan.

Om du redan har en DLL som implementerar C++/WinRT-Windows Runtime klasser har du redan funktionen DllCanUnloadNow som visas nedan. Om du vill lägga till samklasser i DLL:en kan du lägga till funktionen DllGetClassObject .

Om du inte har någon befintlig kod för Windows Runtime C++-mallbibliotek (WRL) som du vill vara kompatibel med kan du ta bort WRL-delarna från koden som visas.

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

Stöd för svaga referenser

Se även Svaga referenser i C++/WinRT.

C++/WinRT (specifikt winrt::implements base struct template) implementerar IWeakReferenceSource åt dig om din typ implementerar IInspectable (eller något gränssnitt som härleds från IInspectable).

Det beror på att IWeakReferenceSource och IWeakReference är utformade för Windows Runtime typer. Därför kan du aktivera svagt referensstöd för din coclass genom att lägga till winrt::Windows::Foundation::IInspectable (eller ett gränssnitt som härleds från IInspectable) till implementeringen.

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

Implementera ett COM-gränssnitt som härleds från ett annat

Gränssnittsavledning är en funktion i klassisk COM (och det råkar vara frånvarande, avsiktligt, från Windows Runtime). Här är ett exempel på hur gränssnittsinledning ser ut.

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

Om du skriver en klass som till exempel behöver implementera både IFileSystemBindData och IFileSystemBindData2, är det första steget för att uttrycka detta att deklarera att du bara implementerar det härledda gränssnittet, så här.

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

Nästa steg är att se till att QueryInterface lyckas när det anropas (direkt eller indirekt) för IID_IFileSystemBindData ( basgränssnittet ) mot en instans av MyFileSystemBindData. Det gör du genom att tillhandahålla en specialisering för funktionsmallen winrt::is_guid_of .

winrt::is_guid_of är variadic, så du kan ge den en lista över gränssnitt. Så här tillhandahåller du en specialisering så att en kontroll av IFileSystemBindData2 även innehåller ett test för 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.
}

Specialiseringen av winrt::is_guid_of måste vara identisk i alla filer i projektet och synlig vid den punkt där gränssnittet används tillsammans med mallen winrt::implements eller winrt::delegate. Vanligtvis lägger du den i en gemensam rubrikfil.

Viktiga API:er