为原生 C++ WRL 移植的并行任务库的创建和使用






4.91/5 (11投票s)
如何创建和使用为原生 C++ WRL 移植的并行任务库
引言
Microsoft 在 ppltasks.h 中包含了并行任务库 (ppl) 的全部源代码,但他们仅在条件编译中提供 WRL 包装器来支持 C++/CX。如果您想使用任务来包装现有的 IAsync*
并创建 IAsync*
操作,那么您需要自己创建任务库。这可以通过删除所有 C++/CX 代码并将其转换为等效的 C++ 来实现。
背景
为了能够管理原生 C++ 的整个项目,应该熟悉 C++、IDL、基础 WRL、Windows Runtime 和 C++/CX,并意识到所需的显著开销,主要的节省在于大小,因为无需链接 C++/CX 平台包装器库。
Using the Code
有几种原始的 ppltasks.h 类型和结构可能会被重用,尽管很少,但将 Concurrency
命名空间任务库与 Concurrency_winrt
命名空间库完全分开处理要清晰得多,也更不容易混淆。该库包含在 ppltasks_winrt.h 文件中。ppltasks.h 的最后一部分没有重复,因为代码不是 C++/CX 或 WRL 特有的。
最大的挑战是必须修改库,使任务 lambda 函数返回 HRESULT
值而不是结果。相反,结果必须是一个作为参数传递给 task
对象创建的变量,然后包含在 .then
lambda 函数的捕获中,而不是简单地成为 lambda 函数的唯一参数。HRESULT
现在是所有 lambda 函数的唯一允许且必需的返回值,并且必须在访问结果值或处理之前进行检查,这允许错误情况被处理,而不是必须将错误捕获为 Platform::Exception
,而它在原生 C++ 中将作为支持 IErrorInfo
或 IRestrictedErrorInfo
接口的 _com_error
引发。
以下是使用 WinRT 任务库为 XAML ICaptureElement
启动 MediaCapture
预览捕获的示例
class CaptureManager
{
public:
static HRESULT InitCapture(HSTRING videoId, HSTRING audioId,
ABI::Windows::Media::Capture::IMediaCapture** pMedCap, ABI::Windows::Foundation::IAsyncAction** pAction)
{
Microsoft::WRL::ComPtr<IActivationFactory> objFactory;
HRESULT hr = Windows::Foundation::GetActivationFactory(
Microsoft::WRL::Wrappers::HStringReference(
RuntimeClass_Windows_Media_Capture_MediaCapture).Get(),
objFactory.ReleaseAndGetAddressOf());
Microsoft::WRL::ComPtr<ABI::Windows::Media::Capture::IMediaCapture> pIMedCap;
if (SUCCEEDED(hr)) {
Microsoft::WRL::ComPtr<IInspectable> pInsp;
hr = objFactory->ActivateInstance(pInsp.ReleaseAndGetAddressOf());
if (SUCCEEDED(hr)) {
hr = pInsp.As(&pIMedCap);
}
}
if (SUCCEEDED(hr)) {
hr = Windows::Foundation::GetActivationFactory(
Microsoft::WRL::Wrappers::HStringReference(
RuntimeClass_Windows_Media_Capture_MediaCaptureInitializationSettings).Get(),
objFactory.ReleaseAndGetAddressOf());
Microsoft::WRL::ComPtr<
ABI::Windows::Media::Capture::IMediaCaptureInitializationSettings> pCapInitSet;
if (SUCCEEDED(hr)) {
Microsoft::WRL::ComPtr<IInspectable> pCapInitSettings;
hr = objFactory->ActivateInstance(
pCapInitSettings.ReleaseAndGetAddressOf());
if (SUCCEEDED(hr)) {
hr = pCapInitSettings.As(&pCapInitSet);
}
}
if (SUCCEEDED(hr))
hr = pCapInitSet->put_PhotoCaptureSource(
ABI::Windows::Media::Capture::PhotoCaptureSource::PhotoCaptureSource_VideoPreview);
if (SUCCEEDED(hr))
hr = pCapInitSet->put_VideoDeviceId(videoId);
if (SUCCEEDED(hr))
hr = pCapInitSet->put_AudioDeviceId(audioId);
if (SUCCEEDED(hr))
hr = pCapInitSet->put_StreamingCaptureMode(
ABI::Windows::Media::Capture::StreamingCaptureMode::StreamingCaptureMode_Video);
if (SUCCEEDED(hr)) hr = pIMedCap->InitializeWithSettingsAsync(
pCapInitSet.Get(), pAction);
if (SUCCEEDED(hr)) *pMedCap = pIMedCap.Detach();
}
return hr;
}
HRESULT StartPreview(ABI::Windows::UI::Xaml::Controls::ICaptureElement* pCaptureElement)
{
HRESULT hr = DeviceManager::InitCapture(NULL, NULL, _mediaCapture.GetAddressOf(),
_action.GetAddressOf());
if (SUCCEEDED(hr)) {
Concurrency_winrt::create_task<void>(_action.Get()).then([pCaptureElement, this]
() -> HRESULT {
HRESULT hr = pCaptureElement->put_Source(_mediaCapture.Get());
Microsoft::WRL::ComPtr<
ABI::Windows::Media::Capture::IMediaCaptureVideoPreview> pMedPrevCap;
if (SUCCEEDED(hr)) hr = _mediaCapture.As(&pMedPrevCap);
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> pAction;
if (SUCCEEDED(hr)) hr = pMedPrevCap->StartPreviewAsync(
pAction.GetAddressOf());
return hr;
});
}
return hr;
}
protected:
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> _action;
Microsoft::WRL::ComPtr<ABI::Windows::Media::Capture::IMediaCapture> _mediaCapture;
};
该示例将从 InitializeWithSettingsAsync
返回的 IAsyncAction
接口通过 create_task
包装到一个任务中。then
函数允许以 HRESULT
作为参数进行继续。在继续进行实际开始预览到 ICaptureElement
的预览后初始化之前,会对此进行检查。
现在考虑 create_async
函数或任何必须保留单线程单元 (STA) 上下文的代码。通过宏可以更清晰地产生这些情况,该宏接收 lambda 捕获参数,允许为 WRL 原生 C++ 和 C++/CX 编写一套代码
#ifdef __cplusplus_winrt
#define _ContextCallback Concurrency::details::_ContextCallback
#define BEGIN_CALL_IN_CONTEXT(hr, var, ...) hr = S_OK;\
var._CallInContext([__VA_ARGS__]() {
#define END_CALL_IN_CONTEXT(hr) if (FAILED(hr)) throw Platform::Exception::CreateException(hr);\
});
#define BEGIN_CREATE_ASYNC(type, ...) (Concurrency::create_async([__VA_ARGS__]() {
#define END_CREATE_ASYNC(hr) if (FAILED(hr)) throw Platform::Exception::CreateException(hr);\
}));
#else
#define _ContextCallback Concurrency_winrt::details::_ContextCallback
#define BEGIN_CALL_IN_CONTEXT(hr, var, ...) hr = var._CallInContext([__VA_ARGS__]() -> HRESULT {
#define END_CALL_IN_CONTEXT(hr) return hr;\
});
#define BEGIN_CREATE_ASYNC(type, ...) Concurrency_winrt::create_async<type>([__VA_ARGS__]() -> HRESULT {
#define END_CREATE_ASYNC(hr) return hr;\
});
#endif
#define GET_CURRENT_CONTEXT _ContextCallback::_CaptureCurrent()
#define SAVE_CURRENT_CONTEXT(var) _ContextCallback var = GET_CURRENT_CONTEXT
与 C++/CX 相比的差异在示例宏中很明显,主要是 HRESULT
和 Platform::Exception
的抛出。对于某些对象(如 MediaCapture
)而言,上下文是必需的,因为它们必须在 XAML 或 WinJS 应用程序启动的 STA 线程上调用。某些对象需要这样做的主要原因是,这些函数可能需要 UI 来向用户显示一条消息,请求权限。在这种特定情况下,MediaCapture
可能会提示用户访问麦克风和/或网络摄像头。
宏的另一个可能用途是包装 create_task
和 then 调用,以便可以链接或启动操作,尽管此示例忽略了一个任务传递给下一个任务的返回值,但可以通过使用在启动情况中默认的参数来自定义。这是一个 C++/CX 的示例宏
#define CREATE_OR_CONTINUE_TASK(_task, rettype, func) _task = (_task == Concurrency::task<rettype>()) ?
Concurrency::create_task(func) : _task.then([func](rettype) -> rettype { return func(); });
create_task
和 create_async
通常是库暴露的全部功能,而 _ContextCallback
仅在回调中执行耗时操作或使用非标准异步 WRL 回调函数(如下列所示)的特殊情况下才真正需要。
关注点
使用几个条件编译宏来创建任务和 IAsync
任务包装器,以及每个接口的宏,可以生成可以在原生 C++ 或 C++/CX 中编译的源代码。
不能混合使用 Concurrency
和 Concurrency_winrt
任务对象。将 Concurrency_winrt
用于非 WinRT 异步任务没有坏处,因为它克隆了整个库,而没有减去 WRL 功能,只是添加了未提供的 WRL IAsync*
实现。
某些接口不遵循一般的架构模式,例如 IActivateAudioInterfaceAsyncOperation
,它通过 IActivateAudioInterfaceCompletionHandler
完成,必须手动实现,并且未集成到并行任务库中。通过相对较少的更改,也可以以与 IAsyncOperation<TResult>
几乎相同的方式在任务库中实现它们。
这些文件旨在实现跨版本兼容,支持 Visual Studio 2012/2013/2015/2017 和 Windows 8/8.1/10 的所有各种组合或形式。
历史
- 初始版本
- 已更新以支持 Visual Studio 2012 Update 4、Windows 8.1 和 Visual Studio 2013 Update 2,以及大量错误修复,以支持 lambda 值返回使用
HRESULT
的正确架构,并且不对 WRL 异步接口进行不必要但危险的专门化。注意:现在已实现大量的更新来支持一个彻底的测试库。强烈建议重新下载并使用当前库,因为不再有已知的错误或问题。 - 已更新以支持 Visual Studio 2012 Update 5、Visual Studio 2013 Update 5、Visual Studio 2015 Update 3、Windows 10 通用 Windows 应用。还包括了评论者推荐的范围修复。
- 已更新以支持 Visual Studio 2017(第 1次至第 5次主要版本)的支持。