65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (11投票s)

2014年1月6日

CPOL

5分钟阅读

viewsIcon

39105

downloadIcon

471

如何创建和使用为原生 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++ 中将作为支持 IErrorInfoIRestrictedErrorInfo 接口的 _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 相比的差异在示例宏中很明显,主要是 HRESULTPlatform::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_taskcreate_async 通常是库暴露的全部功能,而 _ContextCallback 仅在回调中执行耗时操作或使用非标准异步 WRL 回调函数(如下列所示)的特殊情况下才真正需要。

关注点

使用几个条件编译宏来创建任务和 IAsync 任务包装器,以及每个接口的宏,可以生成可以在原生 C++ 或 C++/CX 中编译的源代码。

不能混合使用 ConcurrencyConcurrency_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主要版本)的支持。
© . All rights reserved.