COM Apartment (套间)

COM  Apartment (套间)

套间的由来

最开始的COM库,支持的使用组件的唯一模式是single-thread-per-process模式。这样就避免了多线程的同步,而且组件执行的线程肯定是创建它的线程。
然而组件对象真正的执行环境很复杂。COM组件的执行环境有两种:单线程环境Single-Thread,多线程环境Multi-Thread。单线程要考虑执行线程是否是创建组件的线程;多线程还要考虑并发、同步、死锁、竞争等问题。无论哪种环境,都要编写大量的代码以使COM组件对象正确的运行。
为了使程序员减轻痛苦,COM库决心提供一套机制来帮助程序员。如果我们都遵从这套机制,只要付出较少的劳动,就可以让组件对象和COM库一起完成工作。COM库这套机制的核心技术就是“套间技术”。

COM库的规定

关于多线程问题方面,COM库做出了如下规则(不是COM标准,是COM库为了简化多线程编程中对组件的调用而制定的):
  1. COM库提供两种套间,单线程套间(STA, Single-Threaded Apartment) 和多线程套间(MTA, Multi-Threaded Apartment), COM组件的编写者最好提供对应的属性(线程模型属性, 后面会提到),COM组件的使用者要在套间里创建和调用组件。
  2. COM库对所有的调用进行参数调整(如果需要),不管是对进程内服务器的调用,还是对进程外服务器的调用。
  3. 线程内调用、跨线程调用、跨进程调用都用统一的方式。需要用代理/存根的会用代理/存根。
套间既不是进程,也不是线程。她和进程和线程之间的关联尊崇以下几个原则:
  1. 每个使用COM的进程都有一个或多个套间;
  2. 一个套间只能包含在某一个进程中;
  3. 每个套间可以拥有一个(STA)或多个(MTA)线程;
  4. 一个线程只在某一个套间中执行;
  5. 每个套间可以包含多个对象。
COM规定,只有运行在对象的套间中的线程才能够访问该对象。

STA套间原则:一个进程可以包含0个,1个或者多个STA;每个STA中有且只有一个线程执行;
以上原则决定,驻留在STA中的对象永远也不能被多个线程并发访问,而且只有一个特定的线程可以执行对象的方法。

MTA套间原则:一个进程可以包含0个或者1个MTA;每个MTA中可以有多个线程执行;
以上原则决定,驻留在MTA中对象能够被多个线程并发访问,这在某些情况下可以提供程序的效率,但是作为实现者,你必须处理好线程之间的同步关系。

进程内的主STA套间是进程中第一个调用CoInitialize的线程(主线程)所关联的套间(即进程中的第一个STA套间)。主STA与STA唯一的不同是这是傻瓜型的,连静态和全局变量都可以不用线程保护,因为所有不是安全访问静态和全局变量的对象都通过主线程(第一个调用CoInitialize的线程)的消息派送机制运行,因此不安全的访问都被集中到了一个线程的调用中,因而调用被序列化了,也就实现了对静态和全局变量的线程保护。至于为什么是主线程,因为进程要使用STA,则一定会创建主线程,所以一定可以创建主STA。因此主STA并不是什么第四种套间,只是一个STA套间,不过关联的是主线程而已,由于它可以被用作保护静态和全局变量而被单独提出来说明。因此一个进程内也只有一个主STA套间。

线程分配套间的规则:

如果线程调用CoInitialize,则COM创建新的STA并且将线程放入其中
如果线程调用CoInitializeEx并且传递参数COINIT_APARTMENTTHREADED,则COM创建新的STA并且将线程放入其中
如果线程调用CoInitializeEx并且传递参数COINIT_MULTITHREADED,则COM如果没有MTA,创建新的MTA并且将线程放入其中;如果存在MTA,直接将线程放入其中。

COM对象分配套间的规则:

调用CoCreateInstance或CoGetClassObject等创建对象的函数时,创建的对象将以一个特定规则决定和哪个套间相关联。
决定对象去向的规则如下:当是进程内组件时,根据COM组件注册表项<CLSID>\InprocServer32\ThreadingModel和线程所属套间的不同,列于下表:

Table:COM对象分配套间的规则
 创建COM对象的线程
所关联的套间种类
COM组件的线程模型属性
(ThreadingModel键值)
组件对象最后所在套间
1STA""或Single进程内的主STA套间
2STAApartment创建线程的套间
3STABoth创建线程的套间
4STAFree进程内的MTA套间
5STA""或Single进程内的主STA套间
6STAApartment新建的一个STA套间
7STABoth进程内的MTA套间
8STAFree进程内的MTA套间

跨套间调用需要汇集操作也就是列集→传输→散集

所有线程模型的算法都是通过代理对象实现的。要跨套间时,使用CoMarshalInterface将代理对象的CLSID和其与组件对象建立联系的一些必要信息(如组件对象的接口指针)列集(Marshaling)到一个IStream*中,再通过任何线程间通信手段(如全局变量等)将IStream*传到要使用的线程中,再用CoUnmarshalInterface散集(Unmarshaling)出接口以获得指向代理对象的接口指针。

套间实现规则

STA 当一个组件是STA时,它必须同步保护全局变量和静态变量,即对全局变量和静态变量的访问应该用临界段或其他同步手段保护,因为操作全局和静态变量的代码可以被多个STA线程同时执行,所以那些代码的地方要进行保护。比如对象计数(注意,不是引用计数),代表当前组件生成的对象个数,当减为零时,组件被卸载。此变量一般被类厂对象使用,还好ATL和MFC已经帮我们实现了缺省类厂,这里一般不用担心,但自定义的全局或静态变量得自己处理。

对象之间数据共享通常有三种方式:DLL中声明全局变量;C++类中的静态成员变量;静态局部变量


MTA 必须对组件中的每个成员和全局及静态变量的访问使用同步手段进行保护,还应考虑线程问题,即不是简单地保护访问即可,还应注意线程导致的错误的操作。

NOTE

有3个STA套间,STA1、STA2和STA3。STA1用CoMarshallInterface得到的IStream*传到STA2中通过CoUnmarshalInterface得到的代理和在STA3中同样通过CoUnmarshalInterface得到的代理不同,不能混用。因为当STA2和STA3调用在STA1的对象时,STA1如果回调(连接点技术就是一种回调)调用者,则STA2和STA3的代理能分别正确的指出需要让哪个线程执行回调操作,即向哪个线程发送消息,因此不能混用。

编写可以工作的COM客户端

规则1:客户线程必须调用CoInitialize[Ex]
规则2:STA线程需要消息循环
规则3:不要在套间之间传递原始未列集的接口指针

有没有不列集需要与其他线程共享的接口指针也OK的情况?有。如果两个线程属于同一个套间,则可以共享原始未列集的接口指针,而这只可能在两个线程都属于MTA时发生。如果不确定是否需要,请进行列集。调用CoMarshalInterThreadInterfaceInStream和CoGetInterfaceAndReleaseStream或者使用GIT总是无害的,因为COM只在必要的时候才进行列集。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值