Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Creating and Using the Parallel Task Library Ported for Native C++ WRL

4.91/5 (14 votes)
5 Sep 2016CPOL5 min read 38.7K   444  
How to create and use the parallel task library ported for native C++ WRL

Introduction

Microsoft includes the entire source for the parallel task library (ppl) in ppltasks.h, yet they provide the WRL wrappers in the conditional compilation only to support C++/CX. If you want to use tasks to wrap existing IAsync* and create IAsync* operations, then you need to roll your own task library. This can be done by removing all the C++/CX code and translating it to the equivalent C++.

Background

One should be familiar with C++, IDL, basic WRL, the Windows Runtime and C++/CX to be able to manage an entire project in native C++ and be aware of the significant overhead required with the main savings being in size from not having to link with the C++/CX platform wrapper library.

Using the Code

There are several original ppltasks.h types and structures which could potentially be reused though so few that it is far more clear and less confusing to treat the Concurrency namespace task library completely separately from the Concurrency_winrt namespace library. The library is included in the ppltasks_winrt.h file. The final part of ppltasks.h is not duplicated as the code is not specific to C++/CX or WRL.

The biggest challenge was having to modify the library to make tasks lambda functions return HRESULT values instead of the result. The result instead must be a variable passed as an argument to the task object creation and then included in the capture of the .then lambda function as opposed to simply being the only argument to the lambda function. The HRESULT is now the only allowed and required return value from all lambda functions and must be checked before accessing the result value or processing and allows error cases to be handled as opposed to having to catch the errors as Platform::Exception which would be thrown as a _com_error supporting the IErrorInfo or IRestrictedErrorInfo interfaces in native C++.

The following is an example of using the tasks library for WinRT for starting a MediaCapture preview capture to an XAML ICaptureElement:

C++
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;
};   

The example takes the IAsyncAction interface returned from InitializeWithSettingsAsync and wraps a task around it with create_task. The then function allows continuation with the HRESULT as the argument. This is checked before proceeding with the post preview initialization which actually starts the previewing to the ICaptureElement.

Now consider the create_async function or any code where the single threaded apartment (STA) context must be preserved. These situations can be produced more read-ably with a macro which takes the lambda capture arguments to allow one set of code to be written for WRL native C++ and C++/CX:

C++
#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 

The differences versus C++/CX come obvious in the sample macros primarily with HRESULT and throwing of Platform::Exception. The context is necessary for certain objects like MediaCapture which must be called on the STA thread which the XAML or WinJS application is started on. The primary reason some objects require this is that the functions may require UI generally to display a message to the user asking for permission. In this specific case, the MediaCapture can potentially prompt the user for access to the microphone and/or web camera.

Another possible use for macros is to wrap the create_task and then calls so that one can chain or initiate operations though this example ignores the return value of one task passed to the next, it can be customized by using an argument that is defaulted in the initiation case. Here is a sample macro for C++/CX:

C++
 #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 and create_async are generally the entire exposed functionality of the library with _ContextCallback only really needed for special situations where lengthy operations take place in a callback or non-standard asynchronous WRL callback functions are used like the one listed below.

Points of Interest

Using a handful of conditional compilation macros for task and IAsync task wrapper creation, along with macros for each interface used, one can produce the source code which will compile in both native C++ or C++/CX.

There cannot be any mixing of Concurrency and Concurrency_winrt task objects. There is no harm in using Concurrency_winrt for non-WinRT asynchronous tasks as it clones the entire library without subtracting the WRL functionality only adding the WRL IAsync* implementations that were not provided.

Certain interfaces do not follow the general architectural pattern such as IActivateAudioInterfaceAsyncOperation which is completed through IActivateAudioInterfaceCompletionHandler must be implemented manually and are not integrated into the parallel task library. With relatively few changes, they could also be implemented in the task library in a manner nearly identical to IAsyncOperation<TResult>.

The files are designed to be cross version compatible, supporting Visual Studio 2012/2013/2015/2017 and Windows 8/8.1/10 in all various combinations or forms.

History

  • Initial version
  • Updated for Visual Studio 2012 Update 4, Windows 8.1 and Visual Studio 2013 Update 2 support as well as a great deal of bug fixes to support a proper architecture where lambda value returns use HRESULT and no unnecessary yet dangerous specialization is done on WRL async interfaces. Note: A very large amount of updates to support a thorough test library is now implemented. It is highly recommended to re-download and use the current library as there are no longer any known bugs or issues.
  • Updated for Visual Studio 2012 Update 5, Visual Studio 2013 Update 5, Visual Studio 2015 Update 3, Windows 10 Universal Windows apps. Scope fix recommended by commenter also included.
  • Updated for Visual Studio 2017 (1st through 5th major release) support.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)