COM套间

1. 每一个COM对象都有一个其支持的线程模型的标识,这个标示被称之为套间(apartment),常见的线程模型有单线程,多线程。则套间也被分为单线程套间(STA)和多线程套间(MTA)。如果一个COM对象属于STA,则该COM对象不支持并发操作。如果一个COM对象属于MTA,则该COM对象支持并发和重入,可以多个线程同步访问。
2. 每一个COM对象都必然属于某一个套间,然而一个套间可以被多个对象所共享。每一个进程中最多可以有1个MTA, 但是一个进程可以包含多个STA。
3. 同一时刻,一个线程只在一个套间中执行。一个线程要想使用COM对象,它必须先进入一个套间。
① 什么叫线程进入一个套间?所谓线程进入一个套间就是指,COM库把关于套间的信息存储在线程局部存储(TLS)中。
② 什么叫线程在一个套间中?线程在进入套间时保存在TLS中的信息一致与这个线程相关联,则线程此时就在这个套间中。
③ 什么叫线程离开(退出)一个套间?线程退出一个套间时,COM库把存储在线程TLS中的套间信息解关联,也就是说套间信息和线程没有关联时,我们就称线程退出了这个套间。
3.1 线程进入套间
在调用CreateProcess和CreateThread时,操作系统会首先创建一个线程,对于新创建的线程,此时没有任何套间和它相关联。所以在使用COM之前,新线程必须调用下列3个COM库API,以便进入套间:
HRESULT CoInitializeEx(void*, DWORD);
HRESULT CoInitialize(void*);
HRESULT OleInitialize(void*);
CoInitializeEx函数的第二参数就是用来指定此时的线程将进入何种类型的套间。  COINIT_APARTMENTTHREADED   = 0x2
COINIT_MULTITHREADED       = 0x0
其中COINIT_APARTMENTTHREADED表示线程将进入 STA
COINIT_MULTITHREADED表示线程将进入 MTA。
同时这个参数也决定了这个线程中创建的COM对象的函数调用,将采取何种处理方式。
如果一个线程此时进入STA:则在该STA中创建的对象,其所有方法调用都将序列化进行,实现这种序列化执行的机制是使用窗口消息循环。在STA上创建的COM对象的所有方法调用将以消息的形式被投递到消息队列中,在多线程并发环境下此时的这个消息队列中将函数大量的消息。但是由于队列 FIFO 的特性,这些消息将被串行化(serialization)处理,也就是这些方法调用将被串行化调用,自然就实现了多线程环境下的并发控制。由于单线程套间使用串行化调用,所以我们的COM对象的函数代码中就不需要进行并发控制的代码。虽然STA简化了我们编写COM对象时的代码,但是其串行化的函数调用,却违背了多线程的初衷,这样对程序执行性能有所损害。
如果一个线程此时进入MTA:此时的这个线程就是Multi-threading(也叫 free-threading)。被这个线程所创建的对象的方法调用可以在任何线程中使用。此时的调用就是并发调用,在MTA线程中COM对象可以完全地在并发环境中执行,所以这种并发性使得程序的性能有了极大的提高。由于COM对象完全在并发环境中执行代码,所以COM对象的实现代码必须自行完成函数的并发调用控制,例如对于共享资源的保护,这里就必须使用线程同步的机制来处理,例如使用临界区,信号量,互斥体等线程同步对象。这使得COM对象的实现变得复杂起来,但是程序的执行性能取得到了极大的提高。
3.2 线程在套间中
可以通过使用方法:
获得COM对象指针,然后调用该COM对象的方法
3.3 线程退出套间
对于使用函数:CoInitialize和CoInitializeEx来进入套间的线程,必须使用函数CoUninitialize来退出套间。对于使用函数 OleInitialize 来进入套间的线程,必须使用函数 OleUninitialize 来退出线程。
每一次成功的进入套间,在完成COM对象的使用之后必须调用这些退出函数来退出套间。一个线程退出一个套间之后可以进入另一个套间----也就是说此时再调用CoInitializeEx进入另一个套间是可以成功的, 此时该函数返回 S_OK。但是,如果一个线程此时就是在一个套间中的话,也就是退出套间的函数CoUninitialize还未被调用时,我们再次调用进入套间的函数CoInitializeEx时,此时线程就是重新进入相同的套间,并且返回 S_FALSE。
4. 客户的套间和COM对象的套间
通过上面的分析我们可知,我们使用CoInitializeEx函数来指定我们的线程将进入何种类型的套间,这里我们的指定的套间类型是客户所需要的套间。但是对于一个COM对象在哪种类型的套间中执行,这是由对象实现者来决定的。为了每一个进程内服务器,可以控制他们的套间类型,COM允许每一个CLSID有自己不同的线程模型,我们只需要在本地注册表中用 ThreadingModel 来说明它的线程模型。ThreadingModel这个项,有三个值:a.Both,表示这个类既可以在MTA中执行,也可以在STA中执行。ThreadingModel="Free"
表示这个只能在 MTA中执行。ThreadingModel="Apartment"表示这个类只能在 STA中执行
如果一个CLSID对应的 ThreadingModel 值不存在,则表示该类只能在 主STA 中执行,
主STA 是指这个进程中第一个被初始化的STA。
4.1 有客户套间和CLSID套间,就会出现一个问题,就是客户套间和CLSID套间的兼容性问题。例如:如果客户调用CoInitializeEx函数时使用COINIT_MULTITHREADED ,表示此时线程请求进入多线程套间,然而使用的COM对象的CLSID却标示的是“Apartment”,就是说这个类只能在单线程套间中执行,这时就出现了矛盾。
如果客户的套间和CLSID的套间模型兼容,那么所有针对该才CLSID的进程内激活请求都将直接在客户的套间中构造对象的实例。如果客户的套间和CLSID的套间模型不兼容,那么针对该CLSID的进程内激活请求将迫使 COM 库在另一个套间中构造对象(对客户是透明的),然后客户返回一个代理。

你可能感兴趣的:(com)