首页 文章

安全地同步COM线程

提问于
浏览
3

我过去在多线程方面做了很多,但我对COM很新 . 无论如何,这是我的问题:

我创建了一个工作线程,它注册为STA,并创建一个COM对象 . 然后工作线程和主线程尝试相互通信 . 使用 CoMarshalInterThreadInterfaceInStreamCoGetInterfaceAndReleaseStream ,我可以获取线程来调用另一个线程中COM对象的方法 .

这是工作线程的样子:

void workerThread()
{
  CoInitialize(NULL);
  MyLib::IFooPtr foo = ...; // create my COM object

  // Marshall it so the main thread can talk to it
  HRESULT hr = CoMarshalInterThreadInterfaceInStream(foo.GetIID(),
                                                     foo.GetInterfacePtr(),
                                                     &m_stream);
  if (FAILED(hr)) {
    // handle failure
  }

  // begin message loop, to keep this STA alive
  MSG msg;
  BOOL bRet;
  while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
  { 
    if (bRet == -1)  break;

    DispatchMessage(&msg); 
  }
}

在主线程中:

// launch the thread
m_worker = boost::thread (&workerThread);

// get the interface proxy
MyLib::IFooPtr foo;
LPVOID vp (NULL);
HRESULT hr = CoGetInterfaceAndReleaseStream(m_stream, foo.GetIID(), &vp);
if (SUCCEEDED(hr)) foo.Attach(static_cast<MyLib::IFoo*>(vp));

这会创建对象(需要一段时间来初始化),并允许主线程与它通信,并且所有内容都与COM Apartment的内容正确同步 . 据我所知,从阅读msdn,这似乎是正确的做事方式 . 现在,主线程可以使用其代理来调用COM对象上的方法,而工作线程将通过消息队列接收这些调用,并正确地调度它们 .

但是, what about synchronizing these threads?

显然,在这种情况下,我希望主线程等待调用 CoGetInterfaceAndReleaseStream ,直到工作线程通过 CoMarshalInterThreadInterfaceInStream 创建了该流 . 但我怎么能安全地做到这一点?

MSDN开始,我应该使用 MsgWaitForMultipleObjects 这样的东西,所以我可以等待my_condition或new_message_arrived,然后我可以做类似的事情:

// verbatim from msdn
while (TRUE)
{
   // wait for the event and for messages
   DWORD dwReturn = ::MsgWaitForMultipleObjects(1,
                     &m_hDoneLoading, FALSE, INFINITE, QS_ALLINPUT);

   // this thread has been reawakened. Determine why
   // and handle appropriately.
   if (dwReturn == WAIT_OBJECT_0)
     // our event happened.
     break ;
   else if (dwReturn == WAIT_OBJECT_0 + 1)
   {
     // handle windows messages to maintain
     // client liveness
     MSG msg ;
     while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
       ::DispatchMessage(&msg) ;
   }
}

但是如何将 boost::thread.join()boost::condition.wait()MsgWaitForMultipleObjects 混合?这是可能的,还是我必须做一些其他事情以避免竞争条件?

1 回答

  • 3

    你的主线程有一个消息队列(必须是,因为是一个STA主机),为什么不简单地向它发一条消息,PostThreadMessage?发布用户消息(WM_USER X),您的普通主线程消息泵可以处理此用户消息,作为COM对象已将接口封送到流中的通知,并且主线程可以安全地调用 CoGetInterfaceAndReleaseStream .

    我必须打电话说,你的当前设计,你的工作线程基本上只是运行一个额外的消息泵 . 从主线程对接口上的任何方法的任何调用都将阻塞,等待工作线程从其消息队列中获取消息,处理调用,响应,然后主线程将恢复 . 所有操作至少与在主线程中托管COM对象一样慢,加上COM在两个STA之间来回编组的开销 . 基本上,由于COM STA的工作原理,两个线程之间没有任何并发性 . 你确定这是你想要的吗?

    编辑

    (省略一些细节,如线程数,超时处理,为每个工作者分配流/ IID / CLSID等)

    在.h:

    HANDLE m_startupDone;
    volatile int m_threadStartCount;
    

    工作线程:

    void workerThread()
    {
      CoInitialize(NULL);
      MyLib::IFooPtr foo = ...; // create my COM object
    
      // Marshall it so the main thread can talk to it
      HRESULT hr = CoMarshalInterThreadInterfaceInStream(foo.GetIID(),
                                                         foo.GetInterfacePtr(),
                                                         &m_stream);
      if (FAILED(hr)) {
        // handle failure
        // remember to decrement and signal *even on failure*
      }
    
      if (0 == InterlockedDecrement(&m_threadStartCount))
      {
         SetEvent (m_startupDone);
      } 
    
      // begin message loop, to keep this STA alive
      MSG msg;
      BOOL bRet;
      while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
      { 
        if (bRet == -1)  break;
    
        DispatchMessage(&msg); 
      }
    }
    

    在主线程中:

    m_startupDone = CreateEvent (NULL, FALSE, FALSE, NULL);
    m_threadStartCount = <number of workerthreads>
    
    // launch the thread(s)
    m_worker = boost::thread (&workerThread);
    m_worker2 = boost::thread (&workerThread);
    ...
    
    // now wait for tall the threads to create the COM object(s)
    if (WAIT_OBJECT0 != WaitForSingleObject(m_startupDone, ...))
    {
       // handle failure like timeout
    }
    // By now all COM objects are guaranteed created and marshaled, unmarshall them all in main
    // here must check if all threads actually succeeded (could be as simple as m_stream is not NULL)
    
    // get the interface proxy
    MyLib::IFooPtr foo;
    LPVOID vp (NULL);
    HRESULT hr = CoGetInterfaceAndReleaseStream(m_stream, foo.GetIID(), &vp);
    if (SUCCEEDED(hr)) foo.Attach(static_cast<MyLib::IFoo*>(vp));
    

相关问题