COM线程模型 - Part VI - COM服务端(STA组件)创建线程

来源:互联网 发布:照片审核工具 mac 编辑:程序博客网 时间:2024/06/06 07:22

From: http://blog.csdn.net/zj510/article/details/39290061

 

当我们在COM组件内部创建线程的时候,又是怎么样的一种情况呢?

这个地方跟组件的具体类型有关了,先来看看STA组件的情况。

STA组件内部创建线程

先看一段代码,CMyCarEx是一个STA组件,我们在Run()函数里面起了1000个线程。每个线程就会把m_nMiles加1,1000个线程运行完毕后通过连接点将英里数返回给调用者。

[cpp] view plaincopyprint?
  1. // MyCarEx.cpp : Implementation of CMyCarEx 
  2.  
  3. #include "stdafx.h" 
  4. #include "MyCarEx.h" 
  5.  
  6. #include <thread> 
  7. #include <vector> 
  8. // CMyCarEx 
  9.  
  10. void CMyCarEx::WorkThread() 
  11.     printf("CMyCarEx::WorkThread, begin, tid: %d\n", ::GetCurrentThreadId()); 
  12.  
  13.     std::this_thread::sleep_for(std::chrono::milliseconds(10)); 
  14.     ++m_nMiles; 
  15.      
  16.     printf("CMyCarEx::WorkThread, end, tid: %d, miles: %d\n", ::GetCurrentThreadId(), m_nMiles); 
  17.  
  18. STDMETHODIMP CMyCarEx::Run() 
  19.     // TODO: Add your implementation code here 
  20.     std::vector<std::thread> vThreads; 
  21.     for (int i = 0; i < 1000; ++i) 
  22.     { 
  23.         std::thread t(&CMyCarEx::WorkThread,this); 
  24.         vThreads.push_back(std::move(t)); 
  25.     } 
  26.  
  27.     for (auto& t: vThreads) 
  28.     { 
  29.         t.join(); 
  30.     } 
  31.  
  32.     this->Fire_NeedMoreGas(m_nMiles); 
  33.  
  34.     return S_OK; 


当客户端调用Run函数的时候,可以得到如下结果:

英里数只有999,正确结果应该是1000.如果再运行一次,可能是998了。问题就是1000个线程并发运行,导致结果出错。不是说STA COM对象是保证串行化的吗?为什么会错呢?

实际上,这个问题跟是这样的:

无论客户端是单线程环境还是多线程环境,STA对象的运行线程一定处于STA套间。(如果客户端是单线程环境,那么STA对象就在客户创建的线程里面运行,如果是多线程环境,那么就在default STA套间里面运行)。

Run()函数在一个STA套间线程里面运行,从上面的代码可以看到WorkThread线程本身并没有初始化COM,那么它就是一个普通线程,我们尝试在普通线程里面调用一个STA对象,上面的例子线程函数是成员函数,所以直接来操作数据成员了。这里就涉及到1个问题:普通线程访问STA对象.这就会导致不可预测的后果。MSDN上写的很清楚

http://msdn.microsoft.com/en-us/library/windows/desktop/ms680112(v=vs.85).aspx

对于STA对象,每一个调用它的线程都必须初始化COM。那么我们需要把上面的线程初始化COM,这样就存在STA对象跨套间的问题了,一定需要marshal。


跨套间Marshal

我们需要把上面的代码改一下。首先为了避免线程函数直接访问COM对象数据的问题,我们把线程函数弄成普通函数。

然后再把COM对象marshal一下传递给线程,并且引入消息循环。代码如下:

[cpp] view plaincopyprint?
  1. // MyCarEx.cpp : Implementation of CMyCarEx 
  2.  
  3. #include "stdafx.h" 
  4. #include "MyCarEx.h" 
  5.  
  6. #include <thread> 
  7. #include <vector> 
  8. // CMyCarEx 
  9.  
  10. void WorkThread(LPSTREAM pStream) 
  11.     ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 
  12.  
  13.     std::this_thread::sleep_for(std::chrono::milliseconds(10)); 
  14.  
  15.     CComPtr<IMyCarEx> spCar; 
  16.     HRESULT hr = CoGetInterfaceAndReleaseStream(pStream, IID_IMyCarEx, (LPVOID*)&spCar); // unmarshal to get a com object   
  17.  
  18.     spCar->AddMile(1); 
  19.  
  20.  
  21.     ::CoUninitialize(); 
  22.  
  23. STDMETHODIMP CMyCarEx::Run() 
  24.     // TODO: Add your implementation code here 
  25.     std::vector<std::thread> vThreads; 
  26.     for (int i = 0; i < 1000; ++i) 
  27.     { 
  28.         LPSTREAM pStream = nullptr; 
  29.         IMyCarEx* car = nullptr; 
  30.         QueryInterface(IID_IMyCarEx, (void**)&car); 
  31.         CoMarshalInterThreadInterfaceInStream(IID_IMyCarEx, car, &pStream); // marshal   
  32.         car->Release(); 
  33.  
  34.         std::thread t(WorkThread, pStream); 
  35.         vThreads.push_back(std::move(t)); 
  36.     } 
  37.  
  38.     MSG msg; 
  39.     int index = 0; 
  40.     while (GetMessage(&msg, NULL, 0, 0)) 
  41.     { 
  42.         index++; 
  43.         printf("msg index: %d\n", index); 
  44.  
  45.         TranslateMessage(&msg); 
  46.         DispatchMessage(&msg); 
  47.  
  48.         if (index >= 1000) 
  49.         { 
  50.             break
  51.         } 
  52.     } 
  53.  
  54.     for (auto& t: vThreads) 
  55.     { 
  56.         t.join(); 
  57.     } 
  58.  
  59.     this->Fire_NeedMoreGas(m_nMiles); 
  60.  
  61.     return S_OK; 
  62.  
  63.  
  64.  
  65. STDMETHODIMP CMyCarEx::AddMile(LONG mile) 
  66.     // TODO: Add your implementation code here 
  67.     m_nMiles += mile; 
  68.  
  69.     return S_OK; 

运行一下就可以得到如下结果:

这次就得到了正确结果。

OK, 其实在COM组件内部创建线程和客户端创建线程也是一样的。只要涉及到跨套间的问题,就一定需要marshal。要不然就会得到错误的结果。

当然,在COM组件内部创建线程的时候,有个可能性就是,客户端创建了MTA环境,那么如果组件内部线程不指定套间的话,这个线程默认就属于MTA套间。

完整客户端代码:

[cpp] view plaincopyprint?
  1. // ConsoleApplication4.cpp : Defines the entry point for the console application. 
  2. // 
  3.  
  4. #include "stdafx.h" 
  5.  
  6. #include <thread> 
  7. #include <atlbase.h> 
  8. #include <atlcom.h> 
  9. #include <algorithm> 
  10.  
  11. #include "../MyCom/MyCom_i.h" 
  12. #include "../MyCom/MyCom_i.c" 
  13.  
  14. class CSink : 
  15.     public CComObjectRoot, 
  16.     public _IMyCarExEvents 
  17.     BEGIN_COM_MAP(CSink) 
  18.         COM_INTERFACE_ENTRY(IDispatch) 
  19.         COM_INTERFACE_ENTRY(_IMyCarExEvents) 
  20.     END_COM_MAP() 
  21.  
  22. public
  23.     virtual ~CSink(){ 
  24.     } 
  25.     STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) {return E_NOTIMPL; } 
  26.     STDMETHODIMP GetTypeInfo(UINT iTInfo,LCID lcid, ITypeInfo **ppTInfo)   { return E_NOTIMPL; } 
  27.     STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)  {return E_NOTIMPL; } 
  28.  
  29.     STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid,  
  30.                         LCID lcid,WORD wFlags, DISPPARAMS *pDispParams,  
  31.                         VARIANT *pVarResult, EXCEPINFO *pExcepInfo,  
  32.                         UINT *puArgErr) 
  33.     { 
  34.         printf("sink, id: %d, parm: %d", dispIdMember, pDispParams->rgvarg[0].lVal); 
  35.  
  36.         return S_OK; 
  37.     } 
  38. }; 
  39.  
  40. CComModule commodule;  
  41. int _tmain(int argc, _TCHAR* argv[]) 
  42.     CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); 
  43.     { 
  44.         CComPtr<IMyCarEx> spCar; 
  45.         HRESULT hr = spCar.CoCreateInstance(CLSID_MyCarEx, NULL, CLSCTX_INPROC_SERVER); 
  46.          
  47.         CComObject<CSink>* sink = nullptr; 
  48.         CComObject<CSink>::CreateInstance(&sink); 
  49.  
  50.  
  51.         DWORD cookie = 0; 
  52.         AtlAdvise(spCar, sink, __uuidof(_IMyCarExEvents), &cookie); 
  53.  
  54.         spCar->Run(); 
  55.  
  56.         spCar.Release(); 
  57.  
  58.     } 
  59.  
  60.     CoUninitialize(); 
  61.      
  62.     return 0; 


 

 

0 0
原创粉丝点击