使用 C++/WinRT 创作 COM 组件

C++/WinRT 可以帮助你创作经典组件对象模型(或 coclass),就像帮助你创作Windows 运行时类一样。 本主题演示如何执行该操作。

默认情况下,C++/WinRT 的行为方式与 COM 接口有关

C++/WinRT 的 winrt::implements 模板是运行时类和激活工厂直接或间接派生的基础。

默认情况下, winrt::实现 以无提示方式忽略经典 COM 接口。 因此,对经典 COM 接口的任何 QueryInterface(QI)调用都将因返回 E_NOINTERFACE 而失败。 默认情况下, winrt::implements 仅支持 C++/WinRT 接口。

  • winrt::IUnknown 是一个 C++/WinRT 接口,因此 winrt::implements 支持 基于 winrt::IUnknown 的接口。
  • winrt::implements 默认情况下不支持 ::IUnknown 本身。

稍后你将看到如何解决默认不支持的情况。 但首先,下面是一个代码示例,说明默认情况下会发生什么情况。

// Sample.idl
namespace MyProject 
{
    runtimeclass Sample
    {
        Sample();
        void DoWork();
    }
}

// Sample.h
#include "pch.h"
#include <shobjidl.h> // Needed only for this file.

namespace winrt::MyProject::implementation
{
    struct Sample : implements<Sample, IInitializeWithWindow>
    {
        IFACEMETHOD(Initialize)(HWND hwnd);
        void DoWork();
    }
}

下面是使用 Sample 类的客户端代码。

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

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

启用经典 COM 支持

好消息是,要让 winrt::implements 支持经典 COM 接口,你只需在包含任何 C++/WinRT 头文件之前先包含 unknwn.h 头文件。

你可以显式地这样做,或者通过包含其他某个头文件(例如 ole2.h)来间接实现。 建议的一种方法是包含wil\cppwinrt.h头文件,该文件是Windows实现库(WIL)的一部分。 wil\cppwinrt.h 头文件不仅确保在包含 winrt/base.h 之前先包含 unknwn.h,还会进行相应设置,使 C++/WinRT 和 WIL 能够识别彼此的异常和错误代码。

然后,可以对经典 COM 接口调用 as<>,以上示例中的代码将进行编译

注释

在上面的示例中,即使已经在客户端(使用该类的代码)中启用了经典 COM 支持,如果尚未在服务器(实现该类的代码)中也启用经典 COM 支持,那么客户端中对 as<> 的调用将会崩溃,因为针对 IInitializeWithWindow 的 QI 会失败。

局部(非投影)类

本地类是在同一编译单元(应用或其他二进制)中实现和使用的类;因此没有投影。

下面是 仅实现经典 COM 接口的本地类的示例。

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

如果实现该示例,但未启用经典 COM 支持,则以下代码将失败。

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

同样, IInitializeWithWindow 无法识别为 COM 接口,因此 C++/WinRT 将忽略它。 对于 LocalObject 示例,忽略 COM 接口的结果意味着 LocalObject 根本不具有接口。 但每个 COM 类必须至少实现一个接口。

COM 组件的简单示例

下面是使用 C++/WinRT 编写的 COM 组件的简单示例。 这是一个小型应用程序的完整代码清单,因此,如果你将其粘贴到新建的Windows 控制台应用程序 (C++/WinRT)项目的pch.hmain.cpp中,就可以试运行这段代码。

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

另请参阅 通过 C++/WinRT 使用 COM 组件

更现实和有趣的示例

本主题的其余部分将逐步介绍如何创建一个最小控制台应用项目,该项目使用 C++/WinRT 实现一个基本协类(COM 组件或 COM 类)和类工厂。 示例应用程序演示了如何发送带有回调按钮的 Toast 通知,而 coclass(它实现了 INotificationActivationCallback COM 接口)则使应用程序能够在用户单击 Toast 上的该按钮时被启动并接收回调。

有关 Toast 通知功能区域的更多背景信息,请参阅 “发送本地 Toast 通知”。 不过,文档的该部分中没有代码示例使用 C++/WinRT,因此我们建议你更喜欢本主题中显示的代码。

创建Windows控制台应用程序项目(ToastAndCallback)

首先,在 Microsoft Visual Studio 中创建新项目。 创建Windows控制台应用程序(C++/WinRT)项目,并将其命名为 ToastAndCallback

打开 pch.h,在用于任何 C++/WinRT 标头的 include 语句之前添加 #include <unknwn.h>。 下面是结果。你可以将 pch.h 中的内容替换为下面这个列表。

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

打开 main.cpp并删除项目模板生成的 using 指令。 在它们的位置插入以下代码(它给我们提供所需的库、标头和类型名称)。 下面是结果;可以将你的 main.cpp 内容替换为此列表(我们还从 main 下面的列表中删除了代码,因为我们稍后将替换该函数)。

// main.cpp : Defines the entry point for the console application.

#include "pch.h"

#pragma comment(lib, "advapi32")
#pragma comment(lib, "ole32")
#pragma comment(lib, "shell32")

#include <iomanip>
#include <iostream>
#include <notificationactivationcallback.h>
#include <propkey.h>
#include <propvarutil.h>
#include <shlobj.h>
#include <winrt/Windows.UI.Notifications.h>
#include <winrt/Windows.Data.Xml.Dom.h>

using namespace winrt;
using namespace Windows::Data::Xml::Dom;
using namespace Windows::UI::Notifications;

int main() { }

该项目尚未生成;添加完代码后,系统会提示生成并运行。

实现 coclass 和类工厂

在 C++/WinRT 中,可以通过从 winrt::implements 基本结构派生来实现 coclasses 和类工厂。 紧接着上面所示的三个 using 指令之后(并且在 main 之前),粘贴此代码以实现 toast 通知 COM 激活器组件。

static constexpr GUID callback_guid // BAF2FA85-E121-4CC9-A942-CE335B6F917F
{
    0xBAF2FA85, 0xE121, 0x4CC9, {0xA9, 0x42, 0xCE, 0x33, 0x5B, 0x6F, 0x91, 0x7F}
};

std::wstring const this_app_name{ L"ToastAndCallback" };

struct callback : winrt::implements<callback, INotificationActivationCallback>
{
    HRESULT __stdcall Activate(
        LPCWSTR app,
        LPCWSTR args,
        [[maybe_unused]] NOTIFICATION_USER_INPUT_DATA const* data,
        [[maybe_unused]] ULONG count) noexcept final
    {
        try
        {
            std::wcout << this_app_name << L" has been called back from a notification." << std::endl;
            std::wcout << L"Value of the 'app' parameter is '" << app << L"'." << std::endl;
            std::wcout << L"Value of the 'args' parameter is '" << args << L"'." << std::endl;
            return S_OK;
        }
        catch (...)
        {
            return winrt::to_hresult();
        }
    }
};

struct callback_factory : implements<callback_factory, IClassFactory>
{
    HRESULT __stdcall CreateInstance(
        IUnknown* outer,
        GUID const& iid,
        void** result) noexcept final
    {
        *result = nullptr;

        if (outer)
        {
            return CLASS_E_NOAGGREGATION;
        }

        return make<callback>()->QueryInterface(iid, result);
    }

    HRESULT __stdcall LockServer(BOOL) noexcept final
    {
        return S_OK;
    }
};

上述 coclass 的实现遵循了 使用 C++/WinRT 编写 API 中演示的相同模式。 因此,可以使用相同的技术来实现 COM 接口和Windows 运行时接口。 COM 组件和Windows 运行时类通过接口公开其功能。 每个 COM 接口最终都派生自 IUnknown 接口 接口。 Windows 运行时基于 COM—一个区别是,Windows 运行时接口最终派生自 IInspectable 接口而 IInspectable 派生自 IUnknown)。

在上面的代码中的 coclass 中,我们实现 INotificationActivationCallback::Activate 方法,这是用户在 Toast 通知上单击回调按钮时调用的函数。 但在调用该函数之前,需要创建 coclass 的实例,这是 IClassFactory::CreateInstance 函数的作业。

我们刚刚实现的 coclass 称为用于通知的 COM 激活器,其类 ID(CLSID)是上面所示的 callback_guid 标识符(类型为 GUID)。 稍后我们将使用该标识符,格式为“开始”菜单快捷方式和Windows注册表项。 COM 激活器的 CLSID 以及其关联的 COM 服务器路径(也就是我们在此处构建的可执行文件路径),正是 Toast 通知在其回调按钮被点击时用来确定应创建哪个类的实例的机制(无论该通知是否是在操作中心中被点击)。

实现 COM 方法的最佳做法

错误处理与资源管理技术可以相辅相成。 使用异常比错误代码更方便实用。 如果采用资源获取即初始化 (RAII) 惯用做法,则可以避免显式检查错误代码,然后显式释放资源。 此类显式检查会让代码变得比实际需要的更复杂,也给 bug 提供了大量藏身之处。 相反,使用 RAII,并引发/捕获异常。 这样一来,你的资源分配就具备异常安全性,代码也更简洁。

但是,绝不能让异常从你的 COM 方法实现中逸出。 可以通过在 COM 方法上使用 noexcept 说明符来确保这一点。 可以在方法调用关系图中的任意位置引发异常,只要在方法退出之前处理它们。 如果你使用 noexcept,但随后又让异常从你的方法中逸出,那么你的应用程序将终止。

添加帮助程序类型和函数

在此步骤中,我们将添加代码其余部分使用的帮助程序类型和函数。 因此,紧接着 main添加以下内容。

struct prop_variant : PROPVARIANT
{
    prop_variant() noexcept : PROPVARIANT{}
    {
    }

    ~prop_variant() noexcept
    {
        clear();
    }

    void clear() noexcept
    {
        WINRT_VERIFY_(S_OK, ::PropVariantClear(this));
    }
};

struct registry_traits
{
    using type = HKEY;

    static void close(type value) noexcept
    {
        WINRT_VERIFY_(ERROR_SUCCESS, ::RegCloseKey(value));
    }

    static constexpr type invalid() noexcept
    {
        return nullptr;
    }
};

using registry_key = winrt::handle_type<registry_traits>;

std::wstring get_module_path()
{
    std::wstring path(100, L'?');
    uint32_t path_size{};
    DWORD actual_size{};

    do
    {
        path_size = static_cast<uint32_t>(path.size());
        actual_size = ::GetModuleFileName(nullptr, path.data(), path_size);

        if (actual_size + 1 > path_size)
        {
            path.resize(path_size * 2, L'?');
        }
    } while (actual_size + 1 > path_size);

    path.resize(actual_size);
    return path;
}

std::wstring get_shortcut_path()
{
    std::wstring format{ LR"(%ProgramData%\Microsoft\Windows\Start Menu\Programs\)" };
    format += (this_app_name + L".lnk");

    auto required{ ::ExpandEnvironmentStrings(format.c_str(), nullptr, 0) };
    std::wstring path(required - 1, L'?');
    ::ExpandEnvironmentStrings(format.c_str(), path.data(), required);
    return path;
}

实现其余函数,以及 wmain 入口函数

删除 main 函数,在其原位置粘贴此代码清单,其中包括用于注册组件类,然后提供能够回调应用程序的 toast 的代码。

void register_callback()
{
    DWORD registration{};

    winrt::check_hresult(::CoRegisterClassObject(
        callback_guid,
        make<callback_factory>().get(),
        CLSCTX_LOCAL_SERVER,
        REGCLS_SINGLEUSE,
        &registration));
}

void create_shortcut()
{
    auto link{ winrt::create_instance<IShellLink>(CLSID_ShellLink) };
    std::wstring module_path{ get_module_path() };
    winrt::check_hresult(link->SetPath(module_path.c_str()));

    auto store = link.as<IPropertyStore>();
    prop_variant value;
    winrt::check_hresult(::InitPropVariantFromString(this_app_name.c_str(), &value));
    winrt::check_hresult(store->SetValue(PKEY_AppUserModel_ID, value));
    value.clear();
    winrt::check_hresult(::InitPropVariantFromCLSID(callback_guid, &value));
    winrt::check_hresult(store->SetValue(PKEY_AppUserModel_ToastActivatorCLSID, value));

    auto file{ store.as<IPersistFile>() };
    std::wstring shortcut_path{ get_shortcut_path() };
    winrt::check_hresult(file->Save(shortcut_path.c_str(), TRUE));

    std::wcout << L"In " << shortcut_path << L", created a shortcut to " << module_path << std::endl;
}

void update_registry()
{
    std::wstring key_path{ LR"(SOFTWARE\Classes\CLSID\{????????-????-????-????-????????????})" };
    ::StringFromGUID2(callback_guid, key_path.data() + 23, 39);
    key_path += LR"(\LocalServer32)";
    registry_key key;

    winrt::check_win32(::RegCreateKeyEx(
        HKEY_CURRENT_USER,
        key_path.c_str(),
        0,
        nullptr,
        0,
        KEY_WRITE,
        nullptr,
        key.put(),
        nullptr));
    ::RegDeleteValue(key.get(), nullptr);

    std::wstring path{ get_module_path() };

    winrt::check_win32(::RegSetValueEx(
        key.get(),
        nullptr,
        0,
        REG_SZ,
        reinterpret_cast<BYTE const*>(path.c_str()),
        static_cast<uint32_t>((path.size() + 1) * sizeof(wchar_t))));

    std::wcout << L"In " << key_path << L", registered local server at " << path << std::endl;
}

void create_toast()
{
    XmlDocument xml;

    std::wstring toastPayload
    {
        LR"(
<toast>
  <visual>
    <binding template='ToastGeneric'>
      <text>)"
    };
    toastPayload += this_app_name;
    toastPayload += LR"(
      </text>
    </binding>
  </visual>
  <actions>
    <action content='Call back )";
    toastPayload += this_app_name;
    toastPayload += LR"(
' arguments='the_args' activationKind='Foreground' />
  </actions>
</toast>)";
    xml.LoadXml(toastPayload);

    ToastNotification toast{ xml };
    ToastNotifier notifier{ ToastNotificationManager::CreateToastNotifier(this_app_name) };
    notifier.Show(toast);
    ::Sleep(50); // Give the callback chance to display.
}

void LaunchedNormally(HANDLE, INPUT_RECORD &, DWORD &);
void LaunchedFromNotification(HANDLE, INPUT_RECORD &, DWORD &);

int wmain(int argc, wchar_t * argv[], wchar_t * /* envp */[])
{
    winrt::init_apartment();

    register_callback();

    HANDLE consoleHandle{ ::GetStdHandle(STD_INPUT_HANDLE) };
    INPUT_RECORD buffer{};
    DWORD events{};
    ::FlushConsoleInputBuffer(consoleHandle);

    if (argc == 1)
    {
        LaunchedNormally(consoleHandle, buffer, events);
    }
    else if (argc == 2 && wcscmp(argv[1], L"-Embedding") == 0)
    {
        LaunchedFromNotification(consoleHandle, buffer, events);
    }
}

void LaunchedNormally(HANDLE consoleHandle, INPUT_RECORD & buffer, DWORD & events)
{
    try
    {
        bool runningAsAdmin{ ::IsUserAnAdmin() == TRUE };
        std::wcout << this_app_name << L" is running" << (runningAsAdmin ? L" (administrator)." : L" (NOT as administrator).") << std::endl;

        if (runningAsAdmin)
        {
            create_shortcut();
            update_registry();
        }

        std::wcout << std::endl << L"Press 'T' to display a toast notification (press any other key to exit)." << std::endl;

        ::ReadConsoleInput(consoleHandle, &buffer, 1, &events);
        if (towupper(buffer.Event.KeyEvent.uChar.UnicodeChar) == L'T')
        {
            create_toast();
        }
    }
    catch (winrt::hresult_error const& e)
    {
        std::wcout << L"Error: " << e.message().c_str() << L" (" << std::hex << std::showbase << std::setw(8) << static_cast<uint32_t>(e.code()) << L")" << std::endl;
    }
}

void LaunchedFromNotification(HANDLE consoleHandle, INPUT_RECORD & buffer, DWORD & events)
{
    ::Sleep(50); // Give the callback chance to display its message.
    std::wcout << std::endl << L"Press any key to exit." << std::endl;
    ::ReadConsoleInput(consoleHandle, &buffer, 1, &events);
}

如何测试示例应用程序

生成应用程序,然后以管理员身份至少运行一次,以导致注册和其他安装程序代码运行。 执行此操作的一种方法是以管理员身份运行Visual Studio,然后从Visual Studio运行应用。 右键单击任务栏中的Visual Studio以显示跳转列表,右键单击跳转列表中的Visual Studio,然后单击“以管理员身份运行”。 同意提示,然后打开该项目。 运行应用程序时,会显示一条消息,指示应用程序是否以管理员身份运行。 如果不是,则注册和其他安装程序不会运行。 该注册和其他安装程序必须至少运行一次,以便应用程序正常工作。

无论是否在以管理员身份运行应用程序,按“T”使 toast 显示。 然后可以直接从弹出的 toast 通知或是从操作中心单击“回调 ToastAndCallback”按钮,这样会启动应用程序,实例化组件类并执行 INotificationActivationCallback::Activate 方法。

进程内 COM 服务器

上述 ToastAndCallback 示例应用作为本地(或进程外)COM 服务器运行。 这由 LocalServer32 Windows 注册表项进行指示,该项用于注册其组件类的 CLSID。 本地 COM 服务器将其一个或多个 coclass 托管在可执行二进制文件(一个 .exe)中。

或者(而且可以说这种情况更常见),你可以选择将一个或多个 coclass 放在动态链接库(.dll)中。 采用 DLL 形式的 COM 服务器称为进程内 COM 服务器,由使用 InprocServer32 Windows 注册表项注册 CLSID 进行指示。

可以通过在 Microsoft Visual Studio 中创建新项目来开始创建进程内 COM 服务器的任务。 创建 Visual C++>Windows 桌面>动态链接库 (DLL) 项目。

若要向新项目添加 C++/WinRT 支持,请按照修改 Windows 桌面应用程序项目中所述的步骤添加 C++/WinRT 支持

实现组件类、类工厂和进程内服务器导出

打开 dllmain.cpp并向其添加如下所示的代码列表。

如果已有实现 C++/WinRT Windows 运行时类的 DLL,则你已具有如下所示的 DllCanUnloadNow 函数。 如果要将 coclass 添加到该 DLL,则可以添加 DllGetClassObject 函数。

如果您没有现有的、需要与之保持兼容的 Windows 运行时 C++ 模板库 (WRL) 代码,则可以从所示代码中删除 WRL 相关部分。

// dllmain.cpp

struct MyCoclass : winrt::implements<MyCoclass, IPersist>
{
    HRESULT STDMETHODCALLTYPE GetClassID(CLSID* id) noexcept override
    {
        *id = IID_IPersist; // Doesn't matter what we return, for this example.
        return S_OK;
    }
};

struct __declspec(uuid("85d6672d-0606-4389-a50a-356ce7bded09"))
    MyCoclassFactory : winrt::implements<MyCoclassFactory, IClassFactory>
{
    HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject) noexcept override
    {
        try
        {
            return winrt::make<MyCoclass>()->QueryInterface(riid, ppvObject);
        }
        catch (...)
        {
            return winrt::to_hresult();
        }
    }

    HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock) noexcept override
    {
        // ...
        return S_OK;
    }

    // ...
};

HRESULT __stdcall DllCanUnloadNow()
{
#ifdef _WRL_MODULE_H_
    if (!::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().Terminate())
    {
        return S_FALSE;
    }
#endif

    if (winrt::get_module_lock())
    {
        return S_FALSE;
    }

    winrt::clear_factory_cache();
    return S_OK;
}

HRESULT __stdcall DllGetClassObject(GUID const& clsid, GUID const& iid, void** result)
{
    try
    {
        *result = nullptr;

        if (clsid == __uuidof(MyCoclassFactory))
        {
            return winrt::make<MyCoclassFactory>()->QueryInterface(iid, result);
        }

#ifdef _WRL_MODULE_H_
        return ::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().GetClassObject(clsid, iid, result);
#else
        return winrt::hresult_class_not_available().to_abi();
#endif
    }
    catch (...)
    {
        return winrt::to_hresult();
    }
}

对弱引用的支持

另请参阅 C++/WinRT 中的弱引用

C++/WinRT(具体而言,winrt::implements 基结构模板)会为你实现 IWeakReferenceSource,前提是你的类型实现了 IInspectable(或任何派生自 IInspectable 的接口)。

这是因为 IWeakReferenceSourceIWeakReference 专为Windows 运行时类型而设计。 因此,只需将 winrt::Windows::Foundation::IInspectable(或派生自 IInspectable 的接口)添加到实现,即可为 coclass 启用弱引用支持。

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

实现一个派生自另一个 COM 接口的 COM 接口

接口派生是经典 COM 的一项功能(而 Windows 运行时 刻意不提供这一特性)。 下面是一个接口派生的示例。

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

如果要编写需要实现的类,例如 IFileSystemBindDataIFileSystemBindData2,则表示第一步就是声明你仅实现 派生 接口,如下所示。

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

下一步是确保当针对 MyFileSystemBindData 的实例调用 Query­Interface(直接或间接)以查询 IID_IFileSystemBindData接口)时,该调用能够成功。 为此,请为 winrt::is_guid_of 函数模板提供特化。

winrt::is_guid_of 是可变的,因此可以提供接口列表。 以下说明如何提供特化,以便对 IFileSystemBindData2 的检查也包含对 IFileSystemBindData 的测试。

// pch.h
...
namespace winrt
{
    template<>
    inline bool is_guid_of<IFileSystemBindData2>(guid const& id) noexcept
    {
        return is_guid_of<IFileSystemBindData2, IFileSystemBindData>(id);
    }
}

// main.cpp
...
int main()
{
    ...
    auto mfsbd{ winrt::make<MyFileSystemBindData>() };
    auto a{ mfsbd.as<IFileSystemBindData2>() }; // Would succeed even without the **is_guid_of** specialization.
    auto b{ mfsbd.as<IFileSystemBindData>() }; // Needs the **is_guid_of** specialization in order to succeed.
}

winrt::is_guid_of 的专用化在项目的所有文件中必须相同,并且在 winrt::implementswinrt::delegate 模板使用接口时可见。 通常,你会将其放在通用头文件中。

重要 API