13.浅析COM多线程

论坛 期权论坛 脚本     
匿名技术用户   2020-12-29 02:28   11   0

ATL一节已经接触过多线程一词,COM为了解决多线程问题的思路如下:

1.支持传统多线程访问,组件编写时处理多线程访问(MTA)

2.支持隐藏多线程细节,组件编写时无需多线程访问(STA)

前者是为了灵活,后者是为了方便和兼容,下面我们从这两点展开来说。


1.套间分类

COM引入一个概念——套间(Apartment),具有相同并发和重入访问特性的在同一套间。每个进程可包含多个套间,但是一个套间只能属于一个套间。套间包括两种类型:多线程套间(MTA,multithreaded apartment)和单线程套间(STA,singlethreaded apartment),分别对应前面说的两点。

MTA中多个线程可以并发执行,STA中只有一个线程可以执行,而且这个线程是和这个STA绑定的,STA套间id保存在这个线程的TLS中。

如下,每个进程可有多个STA,但是最多只能有一个MTA



位于MTA中的对象2可以自由访问,从STA或MTA中另一个线程都可以自由访问

位于STA中的对象1只能通过,从MTA或另一个STA中访问都必须通过代理,实际上COM实现如下,它是通过一个消息队列来保证每次只有一个方法调用的。



2.套件中的指针和对象

在使用COM时,新线程必须使用如下三个API之一,以便进入套间。

WINOLEAPI  CoInitialize(__in_opt LPVOID pvReserved);
WINOLEAPI  CoInitializeEx(__in_opt LPVOID pvReserved, __in DWORD dwCoInit);
WINOLEAPI  OleInitialize(IN LPVOID pvReserved);
三个函数的第一个参数都是保留参数,传0即可。

CoInitializeEx是最底层API,指明要进入的套间类型,COINIT_MULTITHREADED指明进入MTA,COINIT_APARTMENTTHREADED指明进入STA。其他两个都是默认进入STA。

套间是接口指针的属性——调用API或某个方法返回一个接口指针时,调用该API或方法的线程决定了“结果接口指针将属于哪个套间”。如果COM对象在当前调用线程套间中,那么返回的是对象接口指针,否则是接口代理指针。这里可以看到进程外组件访问其实是跨套间访问的特例


组件对象指明了自己将运行在何种套间中

之前在编写ATL COM时可以看到需要需要我们指定当前组件的线程模型,如下:


实际选择对应注册表中ThreadingModel键值,类似如下:



可选择值如下四个:

1.主STA 键值为空,组件只能运行在主STA(进程的第一个STA线程)中运行

2.Apartment 键值为"Apartment",组件只能在STA中执行

3.Free 键值为“Free”,组件只能在MTA中运行

4.Both 键值为“Both”,组件可以在MTA或STA中运行


创建组件对象时,如果当前线程套件模型和组件可运行的套间兼容,那么就会直接返回组件接口指针;否则返回的是组件接口代理

比如组件指明在STA中运行,我们在MTA中创建组件对象,此时COM会自动新建一个STA,在其中创建对象,返回给我们的是接口代理。


3.跨套间访问

前面说过,进程外组件是跨套间访问的特例。跨套件访问的时候同样需要列集合散集,需要代理/存根。这里使用标准列集演示跨套件访问,如前所说我们采用OLE已有接口IDispatch,这里可以避免自己编写代理存根,否则就需要自己编写代理存根了。


如下我们创建一个Apartment类型的组件,在主STA中创建,然后创建线程,在新的STA中访问组件接口。

如下

 {
  CComPtr<IMyCircle> spCircle;
  HRESULT hr = spCircle.CoCreateInstance(CLSID_MyCircle, NULL, CLSCTX_INPROC);
  
  if (SUCCEEDED(hr))
  {
   CComBSTR bstrColor(L"红色");
   spCircle->Draw(bstrColor);

   UseInterfaceInThread3(spCircle);
   UseInterfaceInThread2(spCircle);
   UseInterfaceInThread(spCircle);
   MessageLoop();
  }
 }

注意这里必须开启线程消息队列,因为之前我们说过STA是通过消息队列来保证STA中方法串行执行的。


执行结果如下,前面数值为当前方法执行的线程id:


这里我们在UseInterfaceInThread3中,直接访问接口指针,可以看到实际方法在调用线程中执行,不能保证串行访问。

在UseInterfaceInThread2我们通过全局表列集散集,在UseInterfaceInThread中我们直接列集散集,可以看到都能保证串行执行。

所以一定要记住,为了跨套间访问的安全性,必须保证列集散集使用


对应的各个方法代码如下:

//非散集、列集
UINT __stdcall TestThread3(LPVOID lpParam)
{
 CoInitialize(NULL);

 {
  CComPtr<IMyCircle> spCircle = (IMyCircle*)lpParam;
  if (spCircle)
  {
   spCircle->Draw(CComBSTR(L"蓝色"));
  }
 }

 CoUninitialize();
 return 0;
}

VOID UseInterfaceInThread3(CComPtr<IMyCircle> &spCircle)
{
 HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, &TestThread3, spCircle, 0, NULL);
 if (hThread)
 {
  CloseHandle(hThread);
 }
}

//全局接口表散集、列集
UINT __stdcall TestThread2(LPVOID lpParam)
{
 CoInitialize(NULL);

 {
  if (g_Table)
  {
   IMyCircle *p = NULL;
   HRESULT hr = g_Table->GetInterfaceFromGlobal(g_dwTable, IID_IMyCircle, (LPVOID*)&p);
   CComPtr<IMyCircle> spCircle = p; 

   if (SUCCEEDED(hr) && spCircle)
   {
    spCircle->Draw(CComBSTR(L"蓝色"));

    g_Table->RevokeInterfaceFromGlobal(g_dwTable);//用完之后释放全局表
    g_dwTable = 0;
   }
  }
 }

 CoUninitialize();
 return 0;
}

VOID UseInterfaceInThread2(CComPtr<IMyCircle> &spCircle)
{
 CoCreateInstance(CLSID_StdGlobalInterfaceTable, NULL, CLSCTX_INPROC, IID_IGlobalInterfaceTable, (LPVOID*)&g_Table);
 if (g_Table && spCircle)
 {
  g_Table->RegisterInterfaceInGlobal(spCircle, IID_IMyCircle, &g_dwTable);
 }

 HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, &TestThread2, spCircle, 0, NULL);
 if (hThread)
 {
  CloseHandle(hThread);
 }
}

//散集、列集
UINT __stdcall TestThread(LPVOID lpParam)
{
 CoInitialize(NULL);

 {
  CComPtr<IMyCircle> spCircle;
  HRESULT hr = CoGetInterfaceAndReleaseStream((LPSTREAM)lpParam, IID_IMyCircle, (LPVOID*)&spCircle);  // unmarshal to get a com object
  if (SUCCEEDED(hr))
  {
   spCircle->Draw(CComBSTR(L"蓝色"));
  }
 }

 CoUninitialize();

 Sleep(100000);
 PostThreadMessage(g_dwMainThread, WM_QUIT, 0, 0);
 return 0;
}

VOID UseInterfaceInThread(CComPtr<IMyCircle> &spCircle)
{
 LPSTREAM pStream = NULL;
 CoMarshalInterThreadInterfaceInStream(IID_IMyCircle, spCircle, &pStream);

 HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, &TestThread, pStream, 0, NULL);
 if (hThread)
 {
  CloseHandle(hThread);
 }
}

如果这里我们去掉MessageLoop呢,可以实际调试运行下,实际上此时UseInterfaceInThread2和UseInterfaceInThread都不会输出了。


完整演示代码下载链接,注意工程中的代理/存根模块本文是没用到的,因为我们使用的是标准列集IDispatch。

原创,转载请注明来自http://blog.csdn.net/wenzhou1219

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP