I have MFC application that use Parallel Patterns Library for some asynchronous tasks. Some of them use COM objects, so I need to initialize COM library in such tasks. In all such cases I use COM STA model initialization, because main thread is MFC app (MFC App thread can be STA ONLY) and I don't know in which tread context my tasks will be called.
Some example:
BOOL CMyApp::InitInstance() {
// base initialization
CWinAppEx::InitInstance();
AfxOleInit();
// ... some code ...
// PPL usage
{
Concurrency::task_group aTasks;
// Task1
aTasks.run([&](){
HRESULT hRes = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (SUCCEEDED(hRes)) {
Sleep(100);
::CoUninitialize();
}
});
// Task2
aTasks.run([&](){
HRESULT hRes = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (SUCCEEDED(hRes)) {
Sleep(100);
::CoUninitialize();
}
});
// Task3
aTasks.run([&](){
HRESULT hRes = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (SUCCEEDED(hRes)) {
Sleep(100);
::CoUninitialize();
}
});
aTasks.wait();
}
}
This code works OK on Windows 7/XP. But on Windows 8.1 with C++ 2012 Platform Toolset tasks 1 and 2 not working because CoInitializeEx() returns RPC_E_CHANGED_MODE error! Task 3 usually called by PPL core in main MFC thread context which is OLE and his COM already initialized as COINIT_APARTMENTTHREADED, so CoInitializeEx() returns success S_FALSE code (double initialization).
For tasks 2 and 3 PPL core creates separate threads which is not PRE-initialized as COM on Windows 7/XP, so tasks first line initialize COM with success. BUT on Windows 8.1 all look as threads are PRE-INITIALIZED as COM with COINIT_MULTITHREADED flag and subsequent CoInitializeEx(..., COINIT_APARTMENTTHREADED) call returns ERROR!
What the hell! How I can define correct COM initialization rule on Window 8.1 ? Where is my mistake ? PPL is not guaranteed to my thread context for tasks and it can be main thread which in MFC MUST be STA. And I can't define when I should use MTA or STA COM initialization.
Please help me. May be this is error in PPL core code from 2012 C++ platform toolset or error in PPL usage with Windows 8.1 ?
UPDATED: (new code are offered)
Hans Passant is right on 100%. VC++ CRT initializes WinRT in PPL lib! And to do it on Windows 8 and later. And now all PPL tasks are pre-initialized for COM in multithreading mode (MTA/COINIT_MULTITHREADED mode). So, if you want to initialize COM in your PPL tasks you should be very carefully. I wrote special class for COM initialization that allow to you simplify this task.
namespace Concurrency {
/**
* COM MultiThreading initialization for ConcRT
*/
class com_init
{
protected:
const HRESULT m_hRes;
public:
com_init(bool bInit = true)
: m_hRes(bInit ? (isWinRT() ? ERROR_ALREADY_INITIALIZED : CoInitializeEx(NULL, COINIT_MULTITHREADED)) : ERROR_CANCELLED)
{}
~com_init()
{
if (SUCCEEDED(m_hRes)) {
CoUninitialize();
}
}
inline static bool isWinRT() {
const bool bRes = (::Concurrency::GetOSVersion() == ::Concurrency::IResourceManager::Win8OrLater) && (::Concurrency::CurrentScheduler::GetPolicy().GetPolicyValue(WinRTInitialization) == ::Concurrency::InitializeWinRTAsMTA);
return bRes;
}
};
}
So previos code should be that
BOOL CMyApp::InitInstance() {
// base initialization
CWinAppEx::InitInstance();
AfxOleInit();
// ... some code ...
// PPL usage
{
Concurrency::task_group aTasks;
// Task1
aTasks.run([&](){
const Concurrency::com_init objInitCOM;
// ... to do COM work.
});
// Task2
aTasks.run([&](){
const Concurrency::com_init objInitCOM;
// ... to do COM work.
});
// Task3
aTasks.run([&](){
const Concurrency::com_init objInitCOM;
// ... to do COM work.
});
aTasks.wait();
}}