这部分将演练如何创建使用 C++/WinRT 实现基本组件类(COM 组件或 COM 类)和类工厂的最小控制台应用程序项目。 下面的示例应用程序演示如何提供具有一个回调按钮的 toast 通知,组件类(实现 INotificationActivationCallback COM 接口)使应用程序可以启动并在用户单击 toast 上的该按钮时进行回调。
创建 Windows 控制台应用程序项目
首先在 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() { }
项目尚未生成;完成添加代码之后,系统会提示生成并运行。
实现组件类和类工厂
在 C++/WinRT 中,通过从 winrt::implements 基结构派生来实现组件类和类工厂。 紧接着上面所示的三个 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;
}
};
上面的组件类实现遵循在使用 C++/WinRT 创作 API 中演示的相同模式。 因此,可以使用相同技术实现 COM 接口以及 Windows 运行时接口。 COM 组件和 Windows 运行时类会通过接口公开其功能。 每个 COM 接口最终派生自 IUnknown 接口接口。 Windows 运行时基于 COM,一个区别是 Windows 运行时接口最终派生自 IInspectable 接口(并且 IInspectable 派生自 IUnknown)。
在上面代码中的组件类中,我们实现 INotificationActivationCallback::Activate 方法,这是在用户单击 toast 通知上的回调按钮时调用的函数。 但是在可以调用该函数之前,需要创建组件类的实例,这是 IClassFactory::CreateInstance 函数的工作。
我们刚刚实现的组件类称为通知的 COM 激活器,其类 id (CLSID) 的形式为如上所示的 callback_guid 标识符(类型为 GUID)。 我们会在以后采用“开始”菜单快捷方式和 Windows 注册表项的形式来使用该标识符。 COM 激活器 CLSID 以及指向其关联 COM 服务器的路径(这是在此处生成的可执行文件的路径)是一种机制,toast 通知该机制可知道在单击其回调按钮时要创建哪个类的实例(无论是否在操作中心内单击通知)。
实现 COM 方法的最佳做法
用于错误处理和资源管理的方法可以一起进行。 使用异常比错误代码更加方便且实用。 如果采用资源获取即初始化 (RAII) 惯用做法,则可以避免显式检查错误代码,然后显式释放资源。 这类显式检查使代码比实际所需更复杂,并使 bug 有大量位置可以隐藏。 相反,使用 RAII,并引发/捕获异常。 这样,资源分配是异常安全的,并且代码十分简单。
但是,不得允许异常转义 COM 方法实现。 可以通过对 COM 方法使用 noexcept 说明符来确保这一点。 可以在方法调用关系图中的任意位置引发异常,只要在方法退出之前处理它们。 如果使用 noexcept,但随后允许异常转义方法,则应用程序会终止。