Модель COM STA в библиотеке параллельных шаблонов (PPL)?

У меня есть приложение MFC, которое использует библиотеку параллельных шаблонов для некоторых асинхронных задач. Некоторые из них используют COM-объекты, поэтому мне нужно инициализировать COM-библиотеку в таких задачах. Во всех таких случаях я использую инициализацию модели COM STA, потому что основным потоком является приложение MFC (поток приложения MFC может быть ТОЛЬКО STA), и я не знаю, в каком контексте протектора будут вызываться мои задачи.

Пример:

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

Этот код работает нормально в Windows 7 / XP. Но в Windows 8.1 с C ++ 2012 Platform Toolset задачи 1 и 2 не работают, потому что CoInitializeEx () возвращает ошибку RPC_E_CHANGED_MODE! Задача 3 обычно вызывается ядром PPL в контексте основного потока MFC, которым является OLE, и его COM уже инициализирован как COINIT_APARTMENTTHREADED, поэтому CoInitializeEx () возвращает успешный код S_FALSE (двойная инициализация).

Для задач 2 и 3 ядро ​​PPL создает отдельные потоки, которые предварительно не инициализируются как COM в Windows 7 / XP, поэтому первая строка задач успешно инициализирует COM. НО в Windows 8.1 все выглядит так, как будто потоки ПРЕДНАЗНАЧЕНЫ как COM с флагом COINIT_MULTITHREADED, а последующий вызов CoInitializeEx (..., COINIT_APARTMENTTHREADED) возвращает ОШИБКУ!

Какого черта! Как я могу определить правильное правило инициализации COM в Window 8.1? В чем моя ошибка? PPL не гарантируется для моего контекста потока для задач, и это может быть основной поток, который в MFC ДОЛЖЕН быть STA. И я не могу определить, когда мне следует использовать инициализацию MTA или STA COM.

Пожалуйста помогите. Может быть, это ошибка в основном коде PPL из набора инструментов платформы C ++ 2012 или ошибка в использовании PPL с Windows 8.1?


person 23W    schedule 13.03.2015    source источник
comment
Хм, крутое печенье. Вы можете увидеть это в функции VC \ crt \ src \ InternalContextBase.cpp, InternalContextBase :: Dispatch (). Обратите внимание на вызов WinRT :: RoInitialize (RO_INIT_MULTITHREADED); Не случайно. Жесткое требование к потоку STA состоит в том, что он должен прокачивать цикл сообщений. Задачи этого не делают.   -  person Hans Passant    schedule 13.03.2015
comment
Какой смысл инициализировать WinRT в настольных приложениях?   -  person 23W    schedule 15.03.2015
comment
Вы должны инициализировать среду выполнения Windows в настольном приложении по той же причине, по которой вы инициализируете среду выполнения Windows в приложении UWP: чтобы иметь возможность использовать API-интерфейсы, представленные как типы среды выполнения Windows.   -  person IInspectable    schedule 14.01.2020
comment
@Inspectable, да, теперь я это вижу. Спасибо.   -  person 23W    schedule 16.01.2020


Ответы (1)


ОБНОВЛЕНО: (предлагается новый код)

Ханс Пассант прав на 100%. VC ++ CRT инициализирует WinRT в PPL lib! Причем делать это в Windows 8 и новее. И теперь все задачи PPL предварительно инициализируются для COM в многопоточном режиме (режим MTA / COINIT_MULTITHREADED). Итак, если вы хотите инициализировать COM в своих задачах PPL, вам следует быть очень осторожными. Я написал специальный класс для инициализации COM, который позволяет вам упростить эту задачу.

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

Итак, предыдущий код должен быть таким

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();
}}
person 23W    schedule 18.03.2015