什么是COM组件?
   COM组件是以WIN32动态链接库(DLL)或可执行文件(EXE)形式发布的可执行代码组成。
   COM组件是遵循COM规范编写的   COM组件是一些小的二进制可执行文件
   COM组件可以给应用程序、操作系统以及其他组件提供服务
   自定义的COM组件可以在运行时刻同其他组件连接起来构成某个应用程序
   COM组件可以动态的插入或卸出应用
   COM组件必须是动态链接的
   COM组件必须隐藏(封装)其内部实现细节 
   COM组件必须将其实现的语言隐藏
   COM组件必须以二进制的形式发布
   COM组件必须可以在不妨碍已有用户的情况下被升级
   COM组件可以透明的在网络上被重新分配位置
   COM组件按照一种标准的方式来宣布它们的存在 
套间:
一、线程、Apartment和进程  说道COM的线程模型,大家就会想到各种Apartment模型。但Apartment究竟是什么?如何建立一个Apartment呢?  Apartment就是线程的容器,线程中有关COM的操作必须在Apartment中进行。Apartment分为STA和MTA两种,STA是只能容纳一个线程的容器,MTA是能容纳多个线程的容器。COM规定,一个进程中可以有多个STA,但最多只能有一个MTA。线程调用CoInitializeEx(NULL,COINIT_APARTMENTTHREADED)后,这个线程就建立并且进入了一个STA,线程调用CoInitializeEx(NULL,COINIT_MULTITHREADED)后,这个线程就进入了进程公用MTA。一个线程不能同时进入两个Apartment。线程调用CoUninitialize()后,这个线程就退出了它所在的Apartment。设计COM对象时设定的“Apartment模型”就是指这个COM对象可以呆在那种Apartment中。一个线程建立的COM对象自动地呆在这个线程所在的Apartment中。要是这个线程建立了很多个COM对象,那这些对象都呆在这个线程所在的Apartment中。  一个线程可以直接访问它所在的Apartment中的COM对象,但要访问另一个Apartment中的COM对象就必须经过调度。因为STA中只有一个线程,别的线程要访问这个线程建立的COM对象就必须让这个线程代劳了,如此一来,对这个Apartment中所有的COM对象的访问都是序列化的,这些COM对象就不用担心有好几个线程同时访问它的麻烦事。MTA中的COM对象就没这么舒服了,它们必须考虑到可能会有好几个线程同时访问它们。MTA之外的一个线程访问MTA中的一个COM对象时,系统会从COM系统线程池中取出一个线程进入MTA,由它来代表客户线程访问这个COM对象。(COM系统线程池的机理是怎么样的?池中有几个线程?)  二、客户与服务器  COM对象位于服务器中,服务器分为进程内服务器、进程外服务器、远程服务器三种。进程内服务器是一个DLL文件,进程外服务器是一个EXE文件,远程服务器是另一台计算机上的一个DLL文件或EXE文件。远程服务器如果是一个DLL文件的话,由一个被称为“Surrogate”的代理程序调用它。  进程内服务器中的COM对象的Apartment模型如果与客户线程所在的Apartment相配合的话,客户线程建立COM对象时会直接建立在客户线程所在的Apartment中。比如Apartment模型与STA、Free模型与MTA,Both模型与STA或MTA。这样客户线程就可以直接调用COM对象而不用调度。否则就会专门建立一个线程,然后由这个线程建立COM对象,COM对象和客户线程就分处在两个Apartment中。进程外服务器和远程服务器中的COM对象一定不会建立在客户线程所在的Apartment中。对它们的调用一定要经过调度的。  三、在C++Builder下建立一个多Apartment的进程外服务器  由于不必考虑并行的问题,COM对象一般设成使用Apartment线程模型。进程内服务器还没什么问题,如果你试着建了一个进程外服务器,并且让几个客户同时访问服务器中的对象的话,就会发现这些访问不是同时进行的。如果有一个访问特别费时间,它后面的访问就要等很久才能进行。这是因为服务器中只有一个STA,虽然每个线程都建立了自己的COM对象,但这些对象都在这个STA中,当然无法并行执行。  克服这个问题的办法很简单,打开Borland\CBuilder5\Include\Atl\Atlmod.h文件,把第266行的:  typedef TATLModule TComModule;  改成:  #ifdef __DLL__  typedef TATLModule TComModule;  #else  typedef TATLModule > TComModule;  #endif  再打开Borland\CBuilder5\Include\Atl\Atlcom.h文件,把第3214行的:  DECLARE_CLASSFACTORY()  改成:  #ifdef __DLL__  DECLARE_CLASSFACTORY()  #else  DECLARE_CLASSFACTORY_AUTO_THREAD()  #endif  就可以了。重新编译你的程序,同时开两个客户试一试,是不是并发执行了?  先别高兴得太早,如果你同时开了五个客户,并且其中四个在执行费时的访问,你就会发现第五个客户的访问要等待一段时间。这种现象与C++Builder的实现代码有关。  作了前面的修改后,服务器启动后会预先生成几个线程,这些线程各自进入一个STA中。当服务器接到客户的访问要求后,会循环指定一个线程负责这个客户的建立COM对象、访问COM对象的事务。  比如第一个客户要求建立一个COM对象,服务器就给一号线程发消息,让这个线程建立一个COM对象并把这个COM对象的接口传给客户,以后第一个客户对这个COM对象的访问就全由一号线程代理。而第二个客户的建立COM对象、访问COM对象的事务就由服务器指定二号线程来办,如果客户太多,线程用完了,服务器又会让一号线程负责客户的要求,依次循环。如果客户很多,线程可能会负责几个客户的访问要求,而由同一个线程服务的客户的访问就会顺序执行。预先生成的线程数缺省为系统的CPU个数乘以四,也就是四个(除非你的机器有好几个CPU)。  只能同时服务四个客户当然是不行的,让我们继续修改。打开主CPP文件,可以看到下面两行代码:  TComModule ProjectModule(0);  TComModule &_Module = ProjectModule;  改为:  TComModule ProjectModule(MyInitATLServer);  TComModule &_Module = ProjectModule;  其中“MyInitATLServer”是一个新加的函数,定义如下:  void __fastcall MyInitATLServer()  {  if (_Module.SaveInitProc)  _Module.SaveInitProc();  _Module.Init(ObjectMap, Sysinit::HInstance, NULL, 6);//注意这个6  _Module.m_ThreadID = ::GetCurrentThreadId();  _Module.m_bAutomationServer = true;  _Module.DoFileAndObjectRegistration();  AddTerminateProc(_Module.AutomationTerminateProc);  }  看到那个6没有,这代表服务器启动后会预先生成6个线程,也就能同时服务6个客户。这个6可以改成别的数,当然不要太大了,不然机器垮了可别怪我。  改到现在你可能比较满意了,但其实这个服务器还是有缺陷:一开始就生成所有线程是不是太浪费了?循环分配线程好象也不太合理,更重要的是,如果客户程序中途垮了,没有Release它建立的COM对象,那这个COM对象将一直存在下去,占用的资源无法收回。  要解决这些问题就比较麻烦了,建议大家看一看ATL源代码,编写自己的TComModule类和CComThreadAllocator类。  四、编写多线程客户程序时要注意的问题  建立客户程序时必须包含的*_ATL.h文件中有一个很好的COM对象包装类。比如我建立了一个ComLib服务器,里面有一个MyComObj对象,那么在ComLib_ATL.h文件中有一个TCOMIMyComObj类,它很好的封装了MyComObj对象。写单线程程序时可以这样建立它:  TCOMIMyComObj aComObj = CoMyComObj::CreateInstance();  (CoMyComObj是定义在在ComLib_ATL.h文件中的一个辅助类)然后就可以使用aComObj了,不必调用CoInitializeEx()和CoUninitialize(),也不必释放aComObj。假设MyComObj对象中定义了一个方法fun(),一个属性num,可以这样使用:  aComObj.fun();  aComObj.num = 14;  int val = aComObj.num;  注意到num的访问方法了吗?C++Builder灵活运用了特有的__property关键字,不必调用get_num()和set_num()了。  如果在写多线程客户程序时也这样就会出问题:除了第一个线程正常外,后面的的线程无法建立COM对象了。  问题出在CoMyComObj里面,它保证了会调用CoInitializeEx()和CoUninitialize()并且在整个进程中只会调用一次。而在多线程客户程序中,每个线程都必须调用CoInitializeEx()和CoUninitialize()一次。因此,除了第一个线程成功进入了Apartment,别的线程都失败了。  可以这样建立TCOMIMyComObj对象:  CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);  IMyComObj *pComObj;  OleCheck(CoCreateInstance(CLSID_MyComObj, NULL, CLSCTX_LOCAL_SERVER  , IID_IMyComObj, (void **)(&pComObj)));  TCOMIComObjInExe aComObj(pComObj);  ……使用aComObj……  CoUninitialize();  注意,这段代码必须写在TThread::Execute()中,因为只有TThread::Execute()里的代码才是真正运行在新线程中的。另外决不能调用pComObj->Release()。  后记  学COM的念头起于看李维写的那三本书中的第一本的时候,李维描述了建立多线程服务器的重要性,但具体方法只是一笔带过。后来我看了Delphi带的例子,想用在C++Builder中,却无从下手。在关于COM的部分,Delphi和C++Builder相差太大了,而又没有这方面的C++Builder的书,网上的资料也很少,只好自己摸索。
套间的提出是为了组件在多线程环境下安全执行,因为有跨线程调用同一个组件方法的状况存在。若该组件接口是线程安全的,则无须套间,否则需要套间的协助,就如窗口过程函数一样,
窗口过程本身并不是线程安全的,但是消息队列的机制,保证了窗口过程总是在一个线程中执行,串行地处理消息。
Windows的消息机制是通过窗口来实现的,那么一个线程要接收消息,也应该有一个窗口。
COM API的设计者在它们的API函数中实现了一个隐藏的窗口。在我们调用CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)的时候,会生成这个窗口。该窗口是隐藏的,有了这个窗口,就可以支持消息机制,就有办法来实现对象中函数的逐一执行。这样当对象指针被传到其它线程的时候,从外部调用该对象的方法的时候,就会先发一个消息到原线程,
而不再直接访问对象。
这样设计的机制保证COM组件对象中的方法总是在一个线程中被调用(串行的),是线程安全的。
1.套间与线程
   CoInitializeEx是一个创建套间的过程,我们使用CoInitializeEx(NULL, COINIT_MULTITHREADED)后,会创建一个MTA套间。CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)创建个STA套间。一个进程可以包含多个STA,但只能有一个MTA。 一个STA只能包含一个线程,一个MTA可以包含多个线程。
2.COM组件的线程模型(Threading Model)共有四种:Single Apartment Both Free(可以通过修改注册表直接改变这个组件的Treading Model) 
   2.1 Single:当一个组件的线程模型被标识为Single,说明进程中这个组件的实例 都必须在同一个套间线程中。(该套间线程就是进程创建的第一个STA套间线程)。无论在进程中创建多少个组件实例,接口调用都是在同一个线程中完成的。这 其中当然涉及到给隐藏窗口发送消息和等待,需要注意的是避免线程间的死锁问题。2.2 Apartment:当一个组件的线程模型被标识为Apartment,当创建了一个组件,并把组件的某个接口传递给另一个线程时,在这个两个线程中调用这个接口提供的方法,最终都是在同一个线程中完成的。说明该组件不是线程安全的,需要套间的协助。所处的套间线程必须是COINIT_APARTMENTTHREADED。2.3 Free说明组件是线程安全的,当发生2.2的状况时,调用是在不同线程中完成的。但是所处套间必须是MTA(COINIT_MULTITHREADED)2.4 Both说明组件是线程安全的,所处的套间MTA(COINIT_MULTITHREADED)和STA(COINIT_APARTMENTTHREADED)都可以。
3.缺省套间线程当在创建组件时所处的线程(所创建的套间)与该组件的线程模型不匹配。这种状况下系统就会把组件对象放入缺省的套间中(当然是运行在一个缺省的线程中).
4.套间的本质An apartment is neither a process nor a thread; however, apartments share some of the properties of both。套间是保存在线程的TLS中的一个数据结构,借用该结构使套间和线程之间建立起某种关系,通过该结构可以帮助不同的套间之间通过消息机制来实现函数的调用,以保证多线程环境下,数据的同步。

关于COM及套间(Apartment)知识相关推荐

  1. com之套间(Apartment)

    com之套间 参考资料:<Essential COM>.<MSDN> 什么是套间 套间(Apartment),形象点,他就是一个用来装com对象的容器,一个套间内可以有多个Co ...

  2. COM和套间(Apartments)

    COM技术过时了吗?这句话也对也不对.从技术上讲,确实COM的使用率在下降,但是从思想上来说,COM的面向接口的思想正在被Java和.NET发扬光大.那我们还需要和COM打交道吗?这取决于你工作的领域 ...

  3. [转载] COM 套间

    http://www.vckbase.com/index.php/wv/1315 简序 大学毕业前的最后一学期,在一家公司实习,当时的工作需要用到一些操作系统提供的组件.那时候只知道COM这个名词,并 ...

  4. 理解套间(涉及进程、线程、COM线程模型)(转载)

    简序 大学毕业前的最后一学期,在一家公司实习,当时的工作需要用到一些操作系统提供的组件.那时候只知道COM这个名词,并不知道到底是怎么回事,只知道上网到处找别人的源码解决自己的问题:那段日子到现在回忆 ...

  5. ATL之深入浅出书评(潘爱民)

    ATL之深入浅出 介绍一本关于ATL的书<ATL Internals> 潘爱民,5月7日,2000年 北京大学计算机研究所,100871 引言 面对计算机图书市场的繁荣景象,我经常感叹今天 ...

  6. 结构 win32_COM编程攻略(十五 持久化与结构化存储)

    前情提要: Froser:COM编程攻略(十四 连接点与其ATL实现)​zhuanlan.zhihu.com 这一篇主要来说一下持久化和结构化存储. 本篇文章和上一篇没有关系. 一.什么是持久化(Pe ...

  7. 理解COM的线程套件(转)

    junguo 下载源代码 一篇技术文章,给我更多的却是感慨.和作者一样,愿与大家共勉. 简序 大学毕业前的最后一学期,在一家公司实习,当时的工作需要用到一些操作系统提供的组件.那时候只知道COM这个名 ...

  8. 我的原创漏洞CVE-2020-1066

    引用 这个漏洞属于Windows CardSpace服务未正确处理符号链接对象导致的任意文件替换的本地权限提升漏洞 申明 作者poc仅供研究目的,如果读者利用本poc从事其他行为,与本人无关 目录 文 ...

  9. 程序开发小白需了解的一些概念

    程序开发小白需要了解的基本概念 源代码到程序 动态库和静态库 动态库 动态库介绍 动态库使用 静态库 静态库介绍 静态库使用 API接口 API介绍 SDK是什么? 本科学的是机械,虽然上过C编程,但 ...

最新文章

  1. mysql逻辑运算符的优先顺序_PL/SQL运算符优先级
  2. iView 3.3.0 发布,基于 Vue.js 的企业级 UI 组件库
  3. 编程之美-最大公约数问题方法整理
  4. iOS响应事件传递, nextResponder研究
  5. 线程池 Executors2
  6. discuz手机客户端java,Discuz!7.2让高级搜索更全面
  7. 经典书单 —— 人文社科
  8. 在ie7中overflow:hidden失效问题及解决方案
  9. 创建、删除swap分区
  10. stm32f407+rtthread 使用 ec20 ppp拨号
  11. Cinema 4d 软件介绍
  12. MYSQL存储过程详解(创建、变量的定义、异常、错误输出、游标、调试)
  13. wps-doc文件输出为pdf文件时目录报错“错误!未定义书签”解决方法
  14. matlab等距偏置曲线,144 偏置曲线命令详解
  15. 百度飞桨EasyDL X 韦士肯:看轴承质检如何装上“AI之眼”
  16. 这表白代码让我虎躯一震!
  17. 第二十四天 小丁三战链表
  18. Centos 修改镜像源为阿里云
  19. SD 格式化錯誤提示Windows無法完成格式化
  20. OSGI框架的功能和设计思想

热门文章

  1. 马走日问题(Java版)
  2. Postgresql数据库介绍8——使用
  3. 特斯拉供应链全景图详细分析!
  4. 【Lua】常见知识点汇总(包含常见面试考点)
  5. OrCAD中编辑元件属性时元件白色区域位号与黄色区域位号不一样的解决办法
  6. SpringBoot(24) 整合七牛云实现文件上传
  7. java视频转换_Java视频格式转化的实现方法
  8. java emptyqueueexception,阅读以下说明和Java代码,将应填入(n)处的字句写在答题纸的对应栏内。说明 类Queue表示队列,类中的 - 赏学吧...
  9. 转一个有意思的利用存储过程备份恢复PostgreSQL
  10. 计算机启动时滴滴两声,请问电脑开机时嘀嘀两声是什么故障啊?