【问题标题】:COM STA model in Parallel Patterns Library (PPL)?并行模式库 (PPL) 中的 COM STA 模型?
【发布时间】:2021-05-20 06:29:34
【问题描述】:

我有 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 上运行良好。但是在带有 C++ 2012 Platform Toolset 任务 1 和 2 的 Windows 8.1 上无法正常工作,因为 CoInitializeEx() 返回 RPC_E_CHANGED_MODE 错误!任务 3 通常由 PPL 核心在主 MFC 线程上下文中调用,即 OLE,并且他的 COM 已经初始化为 COINIT_APARTMENTTHREADED,因此 CoInitializeEx() 返回成功 S_FALSE 代码(双重初始化)。

对于任务 2 和 3,PPL 核心创建单独的线程,这些线程在 Windows 7/XP 上未预初始化为 COM,因此任务第一行成功初始化 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 使用错误?

【问题讨论】:

  • 嗯,硬饼干。您可以在 VC\crt\src\InternalContextBase.cpp, InternalContextBase::Dispatch() 函数中看到它。注意对 WinRT::RoInitialize(RO_INIT_MULTITHREADED); 的调用不是意外。 STA 线程的一项严格要求是它必须泵送消息循环。任务不会那样做。
  • 在桌面应用程序中初始化 WinRT 有什么意义?
  • 您将在桌面应用程序中初始化 Windows 运行时,原因与在 UWP 应用程序中初始化 Windows 运行时相同:为了能够使用公开为 Windows 运行时类型的 API。
  • @IInspectable,是的,我现在看到了。谢谢。

标签: visual-c++ visual-studio-2012 mfc windows-8.1 ppl


【解决方案1】:

更新:(提供新代码)

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

所以previos代码应该是这样的

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多