C++/WinRT 可以幫助你撰寫經典的元件物件模型(COM)元件(或共類別),就像它幫助你撰寫 Windows 執行階段 類別一樣。 本主題會說明作法。
C++/WinRT 預設對 COM 介面的行為
C++/WinRT 的 winrt::implements 範本是你的執行時類別和啟動工廠直接或間接衍生的基礎。
預設情況下, winrt::impments 會默默忽略經典 COM 介面。 因此,任何經典 COM 介面的 QueryInterface (QI)呼叫都會因 E_NOINTERFACE 而失敗。 預設情況下, winrt::implements 僅支援 C++/WinRT 介面。
- winrt::IUnknown 是 C++/WinRT 介面,因此 winrt::impemments 支援基於 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 標頭檔不僅可確保 unknwn.h 會先於 winrt/base.h 被包含,還會完成相關設定,讓 C++/WinRT 和 WIL 能夠理解彼此的例外狀況與錯誤碼。
你接著可以對經典 COM 介面使用 as<>,而且上述範例中的程式碼即可通過編譯。
Note
在上述範例中,即使客戶端啟用經典 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 元件簡單範例。 這是一個迷你應用程式的完整清單,如果你把程式碼貼到pch.hmain.cpp新的 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());
}
另請參閱 使用 C++/WinRT 消費 COM 元件。
一個更真實且有趣的例子
本主題的其餘部分將逐步說明如何建立一個最小的主控台應用程式專案,該專案使用 C++/WinRT 實作基本的 coclass(COM 元件或 COM 類別)及類別工廠。 範例應用程式展示了如何傳送帶有回撥按鈕的吐司通知,而 coclass(實作 INotificationActivationCallback COM 介面)允許使用者點擊吐司上的該按鈕時啟動並回撥應用程式。
關於吐司通知功能區的更多背景,請參閱 「發送本地吐司通知」。 不過該部分文件中的程式碼範例都沒有使用 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,並移除專案範本所產生的使用指令。 改為插入以下程式碼(它會提供我們所需的程式庫、標頭檔和型別名稱)。 結果如下:你可以用這個列表替換 Your 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 基礎結構體推導來實作共類別和類別工廠。 在上方顯示的三個 using 指示詞之後(並在 main 之前),貼上以下程式碼,以實作您的快顯通知 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 的實作遵循 Author API 中 C++/WinRT 示範的模式。 因此,你可以用同樣的技術實作 COM 介面以及 Windows 執行階段 介面。 COM 元件與 Windows 執行階段 類別透過介面暴露其特性。 每個 COM 介面最終都源自 IUnknown 介面 介面。 Windows 執行階段 基於 COM——其中一個不同之處是 Windows 執行階段 介面最終源自 IInspectable 介面(而 IInspectable 則源自 IUnknown)。
在上述程式碼的 coclass 中,我們實作了 INotificationActivationCallback::Activate 方法,這是使用者點擊吐司通知回調按鈕時會被呼叫的函式。 但在呼叫該函式之前,必須先建立 coclass 的實例,而這是 IClassFactory::CreateInstance 函式的工作。
我們剛剛實作的共類別稱為用於通知的 COM 啟動器,其類別識別碼(CLSID)採用你在上方看到的 callback_guid 識別碼形式(型別為 GUID)。 我們稍後會用這個識別碼,作為開始選單捷徑和 Windows 登錄檔項目。 COM 啟用器 CLSID 以及其關聯 COM 伺服器的路徑(也就是我們在此建置的可執行檔路徑),就是當快顯通知的回呼按鈕被點擊時,讓它知道要建立哪個類別執行個體的機制(無論該通知是否是在行動中心中被點擊)。
實施 COM 方法的最佳實務
錯誤處理與資源管理的技術可以相輔相成。 使用例外比錯誤碼更方便且實用。 如果你採用資源取得即初始化(RAII)這種慣用法,就可以避免顯式檢查錯誤碼,以及顯式釋放資源。 這種明確的檢查會讓你的程式碼比必要的更複雜,也讓錯誤有許多藏身之處。 相反地,使用RAII,並設置投擲/接球例外。 這樣一來,你的資源配置作業就具備例外安全性,而你的程式碼也很簡單。
然而,你不得讓例外從你的 COM 方法實作中逸出。 你可以透過使用 noexcept COM方法中的指定符來確保這一點。 只要在您的方法結束執行之前處理這些例外,那麼在您的方法呼叫圖中的任何位置拋出例外都是沒問題的。 如果你使用 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 的程式碼,以及傳送可回呼您應用程式的快顯通知。
void register_callback()
{
DWORD registration{};
winrt::check_hresult(::CoRegisterClassObject(
callback_guid,
make<callback_factory>().get(),
CLSCTX_LOCAL_SERVER,
REGCLS_SINGLEUSE,
®istration));
}
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」鍵即可顯示吐司。 接著,你可以直接在跳出的快顯通知中,或在行動中心裡,按一下「回呼 ToastAndCallback」按鈕;接著系統便會啟動你的應用程式、將 coclass 具現化,並執行 INotificationActivationCallback::Activate 方法。
進行中 COM 伺服器
上述 ToastAndCallback 範例應用程式作為本地(或非程序)的 COM 伺服器運作。 這可由您用來註冊其 coclass 之 CLSID 的 Windows 登錄機碼 LocalServer32 看出。 本地 COM 伺服器將其 coclass(es)存放在可執行的二進位檔(一個 .exe)。
另外(也可以說更常見)你也可以選擇將 coclasss 托管在動態連結函式庫(a .dll)。 以 DLL 形式存在的 COM 伺服器稱為處理序內 COM 伺服器,其特徵是 CLSID 會使用 InprocServer32 Windows 登錄機碼進行註冊。
建立 Dynamic-Link 圖書館(DLL)專案
你可以先在 Microsoft Visual Studio 建立一個新專案,開始建立正在進行中的 COM 伺服器。 建立一個Visual C++>Windows 桌面>動態連結程式庫 (DLL)專案。
若要為新專案新增 C++/WinRT 支援,請依照修改 Windows 桌面應用程式專案中的步驟,加入 C++/WinRT 支援。
實作 coclass、class factory 以及 in-proc 伺服器的匯出功能
打開 dllmain.cpp,並加入下方列出的程式碼清單。
如果你已經有一個實作 C++/WinRT Windows 執行階段 類別的 DLL,那麼你應該已經有下面顯示的 DllCanUnloadNow 函式。 如果你想在那個 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 衍生的介面)。
這是因為 IWeakReferenceSource 和 IWeakReference 是為 Windows 執行階段 類型設計的。 所以,只要在實作中加入 winrt::Windows::Foundation::IInspectable(或衍生自 IInspectable 的介面),就可以為你的 coclass 啟用弱式參考支援。
struct MyCoclass : winrt::implements<MyCoclass, IMyComInterface, winrt::Windows::Foundation::IInspectable>
{
// ...
};
實作一個衍生自另一個介面的 COM 介面
介面推導是經典 COM 的一項功能(而且在 Windows 執行階段 中是刻意省略的)。 這裡有一個介面推導的範例。
IFileSystemBindData2 : public IFileSystemBindData { /* ... */ };
如果你寫的類別需要同時實作 IFileSystemBindData 和 IFileSystemBindData2,那麼表達這點的第一步是宣告你只實作 導出 的介面,就像這樣。
// 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()
...
下一步是確保當 QueryInterface 被(直接或間接)呼叫給 IID_IFileSystemBindData ( 基礎 介面)針對 MyFileSystemBindData 實例時,能成功執行。 你可以透過為 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::implements 或 winrt::delegate 樣板使用該介面的地方必須可見。 通常你會把它放在一個通用的標頭檔裡。