使用 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 函数,并在其位置粘贴下面这段代码,其中包括用于注册 coclass 的代码,以及用于显示一个能够回调你的应用的 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 按钮,此时将启动你的应用程序、实例化该 coclass,并执行 INotificationActivationCallback::Activate 方法。

进程内 COM 服务器

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

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

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

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

实现 coclass、类工厂和进程内服务器的导出

打开 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