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
:
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:
#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:
#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.