Search code examples
visual-c++visual-studio-2012mfcwindows-8.1ppl

COM STA model in Parallel Patterns Library (PPL)?


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 ?


Solution

  • 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();
    }}