可以通过 C++/WinRT 库的工具来使用 COM 组件,例如 DirectX API 的高性能 2D 和 3D 图形。 C++/ WinRT 是在不影响性能的情况下使用 DirectX 的最简单方法。 本文借助一个 Direct2D 代码示例演示如何通过 C++/WinRT 使用 COM 类和接口。 当然,你也可以在同一个 C++/WinRT 项目中混合使用 COM 和 Windows 运行时编程方法。
本文的末尾提供了一个精简 Direct2D 应用程序的完整源代码列表。 我们将提取该代码的摘录内容,演示如何使用 C++/WinRT 库的各种工具通过 C++/WinRT 来使用 COM 组件。
COM 智能指针 (winrt::com_ptr)
使用 COM 编程时,你会直接使用接口而不是对象(Windows 运行时 API 在幕后也是如此,这是 COM 的一种演进)。 若要针对 COM 类调用函数(例如,激活该类),需先获取一个接口,然后针对该接口调用该函数。 若要访问对象的状态,请不要直接访问其数据成员,而应该针对某个接口调用取值函数和赋值函数。
更具体而言,我们将讨论如何与接口指针交互。 对于这种操作,我们可以受益于 C++/WinRT 中的 COM 智能指针类型 winrt::com_ptr 类型。
#include <d2d1_1.h>
...
winrt::com_ptr<ID2D1Factory1> factory;
以上代码演示如何声明指向 ID2D1Factory1 COM 接口的未初始化智能指针。 该智能指针未初始化,因此暂时不会指向属于任何实际对象的 ID2D1Factory1 接口根本不指向任何接口。 但它可以做到这一点;作为一个智能指针,它能够通过 COM 引用计数来管理它所指向的接口的拥有对象的生存期,并充当中介来让你针对该接口调用函数。
返回 void 形式的接口指针的 COM 函数
可以调用 com_ptr::put_void 函数写入到未初始化智能指针的基础原始指针。
D2D1_FACTORY_OPTIONS options{ D2D1_DEBUG_LEVEL_NONE };
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(factory),
&options,
factory.put_void()
);
以上代码调用 D2D1CreateFactory 函数,该函数通过其最后一个参数返回 void** 类型的 ID2D1Factory1 接口指针。 许多 COM 函数返回 void**。 对于此类函数,请按如下所示使用 com_ptr::put_void。
返回特定接口指针的 COM 函数
D3D11CreateDevice 函数通过其倒数第三个参数返回 ID3D11Device** 类型的 ID3D11Device 接口指针。 对于返回此类特定接口指针的函数,请使用 com_ptr::put。
winrt::com_ptr<ID3D11Device> device;
D3D11CreateDevice(
...
device.put(),
...);
此节前面一节中的代码示例演示如何调用原始 D2D1CreateFactory 函数。 但实际上,当本主题中的代码示例调用 D2D1CreateFactory 时,它会使用一个帮助器函数模板来包装原始 API,因此,代码示例实际使用的是 com_ptr::put。
winrt::com_ptr<ID2D1Factory1> factory;
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
options,
factory.put());
返回 IUnknown 形式的接口指针的 COM 函数
DWriteCreateFactory 函数通过其最后一个参数返回 IUnknown 类型的 DirectWrite 工厂接口指针。 对于此类函数,请使用 com_ptr::put,但需要将其重新解释并转换为 IUnknown。
DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(dwriteFactory2),
reinterpret_cast<IUnknown**>(dwriteFactory2.put()));
重新定位 winrt::com_ptr
如果某个 winrt::com_ptr 已定位(其内部原始指针已有目标),而你想要将其重新定位为指向不同的对象,则首先需要向其分配 nullptr,如以下代码示例所示。 否则,已定位的 com_ptr 会断言其内部指针不为 null,从而产生需要关注的问题(调用 com_ptr::put 或 com_ptr::put_void 时)。
winrt::com_ptr<ID2D1SolidColorBrush> brush;
...
brush.put()
...
brush = nullptr; // Important because we're about to re-seat
target->CreateSolidColorBrush(
color_orange,
D2D1::BrushProperties(0.8f),
brush.put()));
处理 HRESULT 错误代码
若要检查 COM 函数返回的 HRESULT 值,并在它显示错误代码时引发异常,请调用 winrt::check_hresult。
winrt::check_hresult(D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(factory),
options,
factory.put_void()));
采用特定接口指针的 COM 函数
可以调用 com_ptr::get 函数,将 com_ptr 传递给采用相同类型的特定接口指针的函数。
... ExampleFunction(
winrt::com_ptr<ID2D1Factory1> const& factory,
winrt::com_ptr<IDXGIDevice> const& dxdevice)
{
...
winrt::check_hresult(factory->CreateDevice(dxdevice.get(), ...));
...
}
采用 IUnknown 接口指针的 COM 函数
可以使用 com_ptr::get,将 com_ptr 传递给采用 IUnknown 接口指针的函数。
可以使用 winrt::get_unknown 自由函数返回投影类型的对象的基础原始 IUnknown 接口的地址,也就是说,指向该接口的指针。 然后,可以将该地址传递给采用 IUnknown 接口指针的函数。
传递和返回 COM 智能指针
采用 winrt::com_ptr 形式的 COM 智能指针的函数应该按常量引用或按引用来采用该指针。
... GetDxgiFactory(winrt::com_ptr<ID3D11Device> const& device) ...
... CreateDevice(..., winrt::com_ptr<ID3D11Device>& device) ...
// 返回 winrt::com_ptr 的函数应按值返回该指针。
winrt::com_ptr<ID2D1Factory1> CreateFactory() ...
查询不同接口的 COM 智能指针
可以使用 com_ptr::as 函数查询不同接口的 COM 智能指针。 如果查询未成功,该函数将引发异常。
void ExampleFunction(winrt::com_ptr<ID3D11Device> const& device)
{
...
winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };
...
}
或者可以使用 com_ptr::try_as,该函数会返回一个值,将该值与 nullptr 进行比较可以确定查询是否成功。