1

いくつかの非同期タスクに並列パターン ライブラリを使用する MFC アプリケーションがあります。それらのいくつかは COM オブジェクトを使用するため、そのようなタスクでは COM ライブラリを初期化する必要があります。メイン スレッドは MFC アプリ (MFC アプリ スレッドは STA のみ) であり、タスクがどのトレッド コンテキストで呼び出されるかわからないため、このような場合はすべて COM 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 で正常に動作します。しかし、C++ 2012 Platform Toolset を使用する Windows 8.1 では、CoInitializeEx() が RPC_E_CHANGED_MODE エラーを返すため、タスク 1 と 2 が機能しません! タスク 3 は通常、OLE であるメイン MFC スレッド コンテキストで PPL コアによって呼び出され、その COM は既に COINIT_APARTMENTTHREADED として初期化されているため、CoInitializeEx() は成功の S_FALSE コード (二重初期化) を返します。

タスク 2 および 3 の場合、PPL コアは、Windows 7/XP で COM として PRE 初期化されていない個別のスレッドを作成するため、タスクの最初の行で COM を正常に初期化します。 しかし、Windows 8.1 では、すべてのスレッドが COINIT_MULTITHREADED フラグを持つ COM として事前に初期化されているように見え、その後の CoInitializeEx(..., COINIT_APARTMENTTHREADED) 呼び出しはエラーを返します!

なんてこったい!Window 8.1 で正しい COM 初期化ルールを定義するにはどうすればよいですか? 私の間違いはどこですか?PPL は、タスクのスレッド コンテキストに対して保証されておらず、MFC では STA でなければならないメイン スレッドである可能性があります。また、いつ MTA または STA COM 初期化を使用する必要があるかを定義できません。

私を助けてください。これは、2012 C++ プラットフォーム ツールセットの PPL コア コードのエラーか、Windows 8.1 での PPL 使用のエラーでしょうか?

4

1 に答える 1

0

更新: (新しいコードが提供されます)

Hans Passantは 100% 正しいです。VC++ CRT は PPL ライブラリで WinRT を初期化します! そして、Windows 8以降でそれを行う。また、すべての PPL タスクは、マルチスレッド モード (MTA/COINIT_MULTITHREADED モード) で COM 用に事前に初期化されます。そのため、PPL タスクで COM を初期化する場合は、十分に注意する必要があります。このタスクを簡素化できる 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();
}}
于 2015-03-18T07:45:07.257 に答える