注释
本文中的代码示例使用 UWP Core App (IFrameworkView) 应用程序模型。 在 WinUI 3 桌面应用中,从 Microsoft::UI::Xaml::Application 和调用 Application::Start(...) 作为应用入口点派生类,并使用 DispatcherQueue 而不是 CoreDispatcher。 此处显示的 COM/Direct2D 概念和模式同样适用于 WinUI 3 应用。
可以使用 C++/WinRT 库的设施来使用 COM 组件,例如 DirectX API 的高性能二维图形和三维图形。 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 类上调用函数,请激活该类、获取接口,然后在该接口上调用函数。 若要访问对象的状态,则不会直接访问其数据成员;而是在接口上调用访问器和 mutator 函数。
为了更具体化,我们将讨论如何与接口 指针交互。 为此,我们得益于 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
Important
如果你有已固定的 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 接口指针的函数。
有关 投影类型的信息,请参阅 使用 C++/WinRT 的 API。
有关 get_unknown的代码示例,请参阅 本主题中 winrt::get_unknown 或 最小 Direct2D 应用程序的完整源代码列表 。
传递和返回 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 进行比较,以检查查询是否成功。
最小 Direct2D 应用程序的完整源代码列表
注释
有关为 C++/WinRT 开发设置Visual Studio(包括安装和使用 C++/WinRT Visual Studio 扩展(VSIX)和 NuGet 包(一起提供项目模板和生成支持)的信息,请参阅Visual Studio对 C++/WinRT 的支持。
如果要生成并运行此源代码示例,请先安装(或更新到)最新版本的 C++/WinRT Visual Studio 扩展(VSIX);请参阅上述说明。 然后在Visual Studio中创建新的核心应用(C++/WinRT)。
Direct2D 作为项目名称很合适,但你也可以按自己的喜好命名。 以最新版且正式发布的 Windows SDK(即非预览版)为目标。
步骤 1。 编辑 pch.h
打开pch.h,并在包括#include <unknwn.h>后立即添加windows.h。 这是因为我们使用的是 winrt::get_unknown。 每次使用 winrt::get_unknown 时,最好都显式地 #include <unknwn.h>,即使该头文件已被另一个头文件包含。
注释
如果省略此步骤,则会看到生成错误 “get_unknown”:找不到标识符。
步骤 2。 编辑 App.cpp
打开 App.cpp,删除其整个内容,并粘贴下面的列表。
以下代码尽可能使用 winrt::com_ptr::capture 函数 。
WINRT_ASSERT 是宏定义,它扩展到 _ASSERTE。
#include "pch.h"
#include <d2d1_1.h>
#include <d3d11.h>
#include <dxgi1_2.h>
#include <winrt/Windows.Graphics.Display.h>
using namespace winrt;
using namespace Windows;
using namespace Windows::ApplicationModel::Core;
using namespace Windows::UI;
using namespace Windows::UI::Core;
using namespace Windows::Graphics::Display;
namespace
{
winrt::com_ptr<ID2D1Factory1> CreateFactory()
{
D2D1_FACTORY_OPTIONS options{};
#ifdef _DEBUG
options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
winrt::com_ptr<ID2D1Factory1> factory;
winrt::check_hresult(D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
options,
factory.put()));
return factory;
}
HRESULT CreateDevice(D3D_DRIVER_TYPE const type, winrt::com_ptr<ID3D11Device>& device)
{
WINRT_ASSERT(!device);
return D3D11CreateDevice(
nullptr,
type,
nullptr,
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
nullptr, 0,
D3D11_SDK_VERSION,
device.put(),
nullptr,
nullptr);
}
winrt::com_ptr<ID3D11Device> CreateDevice()
{
winrt::com_ptr<ID3D11Device> device;
HRESULT hr{ CreateDevice(D3D_DRIVER_TYPE_HARDWARE, device) };
if (DXGI_ERROR_UNSUPPORTED == hr)
{
hr = CreateDevice(D3D_DRIVER_TYPE_WARP, device);
}
winrt::check_hresult(hr);
return device;
}
winrt::com_ptr<ID2D1DeviceContext> CreateRenderTarget(
winrt::com_ptr<ID2D1Factory1> const& factory,
winrt::com_ptr<ID3D11Device> const& device)
{
WINRT_ASSERT(factory);
WINRT_ASSERT(device);
winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };
winrt::com_ptr<ID2D1Device> d2device;
winrt::check_hresult(factory->CreateDevice(dxdevice.get(), d2device.put()));
winrt::com_ptr<ID2D1DeviceContext> target;
winrt::check_hresult(d2device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, target.put()));
return target;
}
winrt::com_ptr<IDXGIFactory2> GetDxgiFactory(winrt::com_ptr<ID3D11Device> const& device)
{
WINRT_ASSERT(device);
winrt::com_ptr<IDXGIDevice> const dxdevice{ device.as<IDXGIDevice>() };
winrt::com_ptr<IDXGIAdapter> adapter;
winrt::check_hresult(dxdevice->GetAdapter(adapter.put()));
winrt::com_ptr<IDXGIFactory2> factory;
factory.capture(adapter, &IDXGIAdapter::GetParent);
return factory;
}
void CreateDeviceSwapChainBitmap(
winrt::com_ptr<IDXGISwapChain1> const& swapchain,
winrt::com_ptr<ID2D1DeviceContext> const& target)
{
WINRT_ASSERT(swapchain);
WINRT_ASSERT(target);
winrt::com_ptr<IDXGISurface> surface;
surface.capture(swapchain, &IDXGISwapChain1::GetBuffer, 0);
D2D1_BITMAP_PROPERTIES1 const props{ D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)) };
winrt::com_ptr<ID2D1Bitmap1> bitmap;
winrt::check_hresult(target->CreateBitmapFromDxgiSurface(surface.get(),
props,
bitmap.put()));
target->SetTarget(bitmap.get());
}
winrt::com_ptr<IDXGISwapChain1> CreateSwapChainForCoreWindow(winrt::com_ptr<ID3D11Device> const& device)
{
WINRT_ASSERT(device);
winrt::com_ptr<IDXGIFactory2> const factory{ GetDxgiFactory(device) };
DXGI_SWAP_CHAIN_DESC1 props{};
props.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
props.SampleDesc.Count = 1;
props.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
props.BufferCount = 2;
props.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
winrt::com_ptr<IDXGISwapChain1> swapChain;
winrt::check_hresult(factory->CreateSwapChainForCoreWindow(
device.get(),
winrt::get_unknown(CoreWindow::GetForCurrentThread()),
&props,
nullptr, // all or nothing
swapChain.put()));
return swapChain;
}
constexpr D2D1_COLOR_F color_white{ 1.0f, 1.0f, 1.0f, 1.0f };
constexpr D2D1_COLOR_F color_orange{ 0.92f, 0.38f, 0.208f, 1.0f };
}
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
winrt::com_ptr<ID2D1Factory1> m_factory;
winrt::com_ptr<ID2D1DeviceContext> m_target;
winrt::com_ptr<IDXGISwapChain1> m_swapChain;
winrt::com_ptr<ID2D1SolidColorBrush> m_brush;
float m_dpi{};
IFrameworkView CreateView()
{
return *this;
}
void Initialize(CoreApplicationView const&)
{
}
void Load(hstring const&)
{
CoreWindow const window{ CoreWindow::GetForCurrentThread() };
window.SizeChanged([&](auto&&...)
{
if (m_target)
{
ResizeSwapChainBitmap();
Render();
}
});
DisplayInformation const display{ DisplayInformation::GetForCurrentView() };
m_dpi = display.LogicalDpi();
display.DpiChanged([&](DisplayInformation const& display, IInspectable const&)
{
if (m_target)
{
m_dpi = display.LogicalDpi();
m_target->SetDpi(m_dpi, m_dpi);
CreateDeviceSizeResources();
Render();
}
});
m_factory = CreateFactory();
CreateDeviceIndependentResources();
}
void Uninitialize()
{
}
void Run()
{
CoreWindow const window{ CoreWindow::GetForCurrentThread() };
window.Activate();
Render();
CoreDispatcher const dispatcher{ window.Dispatcher() };
dispatcher.ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
}
void SetWindow(CoreWindow const&) {}
void Draw()
{
m_target->Clear(color_white);
D2D1_SIZE_F const size{ m_target->GetSize() };
D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
m_target->DrawRectangle(rect, m_brush.get(), 100.0f);
char buffer[1024];
(void)snprintf(buffer, sizeof(buffer), "Draw %.2f x %.2f @ %.2f\n", size.width, size.height, m_dpi);
::OutputDebugStringA(buffer);
}
void Render()
{
if (!m_target)
{
winrt::com_ptr<ID3D11Device> const device{ CreateDevice() };
m_target = CreateRenderTarget(m_factory, device);
m_swapChain = CreateSwapChainForCoreWindow(device);
CreateDeviceSwapChainBitmap(m_swapChain, m_target);
m_target->SetDpi(m_dpi, m_dpi);
CreateDeviceResources();
CreateDeviceSizeResources();
}
m_target->BeginDraw();
Draw();
m_target->EndDraw();
HRESULT const hr{ m_swapChain->Present(1, 0) };
if (S_OK != hr && DXGI_STATUS_OCCLUDED != hr)
{
ReleaseDevice();
}
}
void ReleaseDevice()
{
m_target = nullptr;
m_swapChain = nullptr;
ReleaseDeviceResources();
}
void ResizeSwapChainBitmap()
{
WINRT_ASSERT(m_target);
WINRT_ASSERT(m_swapChain);
m_target->SetTarget(nullptr);
if (S_OK == m_swapChain->ResizeBuffers(0, // all buffers
0, 0, // client area
DXGI_FORMAT_UNKNOWN, // preserve format
0)) // flags
{
CreateDeviceSwapChainBitmap(m_swapChain, m_target);
CreateDeviceSizeResources();
}
else
{
ReleaseDevice();
}
}
void CreateDeviceIndependentResources()
{
}
void CreateDeviceResources()
{
winrt::check_hresult(m_target->CreateSolidColorBrush(
color_orange,
D2D1::BrushProperties(0.8f),
m_brush.put()));
}
void CreateDeviceSizeResources()
{
}
void ReleaseDeviceResources()
{
m_brush = nullptr;
}
};
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
CoreApplication::Run(winrt::make<App>());
}
处理 COM 类型,例如 BSTR 和 VARIANT
如你所看到的,C++/WinRT 为实现和调用 COM 接口提供支持。 对于使用 COM 类型(如 BSTR 和 VARIANT),建议使用由 Windows 实现库(WIL)提供的包装器,例如 wil::unique_bstr 和 wil::unique_variant(用于管理资源生存期)。
WIL 取代活动模板库(ATL)和 Visual C++ 编译器的 COM 支持等框架。 我们建议使用它,而不是自己编写包装器,或者直接以原始形式使用 BSTR 和 VARIANT 等 COM 类型(并配合相应的 API)。
避免命名空间冲突
在 C++/WinRT 中,广泛使用 using 指令是一种常见做法,正如此主题中的代码列表示范的那样。 但在某些情况下,这可能会导致将冲突名称导入到全局命名空间中的问题。 下面是一个示例。
C++/WinRT 包含名为 winrt::Windows::Foundation::IUnknown 的类型;而 COM 则定义名为 ::IUnknown 的类型。 因此,在使用 COM 标头的 C++/WinRT 项目中,请考虑以下代码。
using namespace winrt::Windows::Foundation;
...
void MyFunction(IUnknown*); // error C2872: 'IUnknown': ambiguous symbol
未限定的名称 IUnknown 在全局命名空间中发生冲突,因此会出现 符号不明确 编译器错误。 相反,可以将名称的 C++/WinRT 版本隔离到 winrt 命名空间中,如下所示。
namespace winrt
{
using namespace Windows::Foundation;
}
...
void MyFunctionA(IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.
或者,如果你想要 using namespace winrt 带来的便利,那也可以。 只需限定 IUnknown 的全局版本,如下所示。
using namespace winrt;
namespace winrt
{
using namespace Windows::Foundation;
}
...
void MyFunctionA(::IUnknown*); // Ok.
void MyFunctionB(winrt::IUnknown const&); // Ok.
当然,这适用于任何 C++/WinRT 命名空间。
namespace winrt
{
using namespace Windows::Storage;
using namespace Windows::System;
}
例如,你可以将 winrt::Windows::Storage::StorageFile 简称为 winrt::StorageFile。