com中的线程模式

      提及COM的线程模式,实际上指的是两个方面,一个是客户程序的线程模式,一个是组件所支持的线程模式。客户程序的线程模式只有两种,单线程公寓(STA)和多线程公寓(MTA)。组件所支持的线程模式有四种:Single(单线程)、Apartment(STA)、Free(MTA)、Both(STA+MTA)。

  1、公寓只是个逻辑上的概念。一个STA只能包含一个线程,一个MTA可以包含多个线程。一个进程可以包含多个STA,但只能有一个MTA。MTA中各线程可以并行的调用本公寓内实例化的组件,而不需要进行调度。跨公寓调用组件实例必须要进行调度。(除非使用了自由线程调度器)

  2、客户程序的线程是在调用CoInitializeEx()时决定客户线程的类型的。如果以参数COINIT_APARTMENTTHREADED调用,则会创建一个STA公寓,客户线程包含在这个公寓里。如果以参数COINIT_MULTITHREADED调用,则创建一个MTA公寓,把线程加入到这个MTA中;如果进程内已经有了一个MTA,则不创建新的MTA,只把线程加入到已有的MTA。注意每个线程都必须调用CoInitializeEx()才能使用COM组件。

  3、线程最重要的是同步问题。STA是通过窗口消息队列来解决这个问题的。当客户线程以COINIT_APARTMENTTHREADED调用CoInitializeEx()时,将为会该STA创建一个具有OleMainThreadWndClass窗口类的隐含窗口。所有对在这个公寓中建立的COM对象方法的调用都将都放到这个隐含窗口的消息队列中。所以每一个与STA相关联的线程必须用GetMessage、DispatchMessage或类似方法来分派窗口消息。MTA内各线程可并行调用同一个组件对象的实例,从而不保证安全性,所以实现同步访问的责任就落在了组件身上。注意,STA的同步是公寓级的,就是说对公寓内不同组件的访问都要放到同一个消息队列中,对一个实例的方法调用会影响对其他实例的调用,所以并发程度很低。

  4、在不同公寓间传递接口指针必须要经过调度。这主要还是为了同步对组件的调用。通过CoMarshalInterThreadInterfaceInStream和CoGetInterfaceAndReleaseStream实现。很简单。

  5、Single型组件很特殊,它只能在一个单一的线程中执行。首先要说明的是一个进程中第一个以COINIT_APARTMENTTHREADED调用CoInitializeEx()的线程被称作是主STA。每次用CoCreateInstance()创建的Single型组件实际上都是创建在了这个主STA中,而不管是谁调用了CoCreateInstance()这个函数。所有对这个Single组件方法的调用都必须要通过这个主STA。

  6、若STA创建STA型组件,是直接创建,直接调用。若STA创建MTA型组件,系统为组件创建一个MTA,STA通过代理访问组件。若STA创建Both型组件,是直接创建,直接调用。若MTA创建STA型组件,系统为组件创建一个STA,MTA通过代理访问组件。若MTA创建MTA型组件,是直接创建,直接调用。若MTA创建Both型组件,是直接创建,直接调用。可见如果客户程序和组件都支持同样的线程模式,那么COM就允许客户程序直接调用对象,这样将产生最佳性能。

  7、Both型组件已经很好了,无论是STA还是MTA都可以直接创建调用它。但跨公寓的调用仍然要经过代理。为了更进一步以获得最佳性能,可以使用自由线程调度器(FTM)。注意其它类型的组件也可以使用FTM,只是由Both使用FTM可获得是最佳效果。FTM实现了接口IMarshal,当调度那两个调度接口指针的函数时,这两个函数(见5)内部调用IMarshal内的相关函数,并判断如果调度发生在一个进程内的公寓之间则直接返回接口指针;如果调度发生在进程之间或者远程计算机间,则调用标准的调度器,并返回指向代理对象的指针。所以可见使用FTM,即使是公寓之间也不用调度接口指针了!!

  8、FTM虽然好,但使用FTM的组件必须遵守某些限制:使用FTM的对象不能直接拥有没有实现FTM的对象的接口指针;使用FTM的对象不能拥有其他公寓对象代理的引用。

  9、全局接口表(GIT)。作用范围是进程内。可以把接口指针存进表中,然后在别的公寓内把其取出,GIT自动执行公寓间的调度,所以很方便。GIT是通过IGlobalInterfaceTable访问的。通过创建CLSID为CLSID_StdGlobalInterfaceTable的对象可调用它。
1)COM对象为STA,Client线程初始化为COINIT_APARTMENTTHREADED
  COM Runtime将建立STA COM对象,Client的线程进入这个STA并直接得到COM对象指针.

2)COM对象为STA,Client线程初始化为COINIT_MULTITHREADED
  如果这个COM对象没有被建立过,COM Runtime将为这个COM对象建立一个新的线程并在这个新线程中建立STA COM对象, Client线程进入的是MTA,得到的将是新的线程中这个COM对象的被marshal的指针.如果这个COM对象被建立过,COM Client线程得到的将是已经建立的STA线程中COM对象的被marshal的指针.

3)COM对象为MTA,Client线程初始化为COINIT_APARTMENTTHREADED
  如果这个COM对象没有被建立过,COM Runtime将为这个COM对象建立一个新的线程并在这个新线程中建立MTA COM对象, COM Client线程进入STA,得到的将是新的线程中这个COM对象的被marshal的指针.如果这个COM对象被建立过,Client线程得到的将是已经建立的MTA线程中COM对象的被marshal的指针.

4)COM对象为MTA,COM Client为COINIT_MULTITHREADED
  如果这个COM对象没有被建立过,COM Runtime将建立MTA COM对象.COM Client线程进入MTA并直接得到COM对象的指针.如果这个COM对象被初始化过, Client线程进入已经建立的MTA并直接得到COM对象的指针.

5)COM对象为Any,COM Client为COINIT_APARTMENTTHREADED
  如果这个COM对象没有被初始化过,COM Runtime将建立STA COM对象,Client线程进入STA,直接得到COM对象指针.

6)COM对象为Any,COM Client为COINIT_MULTITHREADED
  如果这个COM对象没有被初始化过,COM Runtime将建立MTA COM对象,Client线程进入这个MTA并直接得到COM对象的指针.如果这个COM对象被初始化过,Client线程进入已经建立的MTA并直接得到COM对象的指针.
4. 关于marshal
  在COM中marshal分为三种: 进程内的marshal, 同一计算机中进程间的marshal, 以及不同计算机间的marshal. 进程内和进程间的marshal是通过Local RPC完成的,计算机间的marshal通过DCE RPC来完成. 进程间和计算机间的marshal是必须的,进程内marshal是在不同Apartment之间进行方法调用和传递对象Interface时发生.在同一Apartment内的调用用不着marshal. 举个例子来说, 一个处于MTA中的Client线程,想要调用一个处于STA中的对象时, COM Runtime会走进来, 对这个调用进行marshal.为什么要marshal? 因为MTA本身说明了现在是一个多线程的环境, 而STA中的对象不是Thread-safe的,那么对这个不是Thread-safe的对象的调用必须要序列化(排队).COM Runtime为了保证不是Thread-safe的对象的调用序列化, 必须要截获对该对象的调用, 然后进行排队. marshal就是起这个作用. 实际上,COM Runtime会截获MTA的线程对STA对象的调用(通过Proxy),将这个调用通过消息传递方式传递给STA对象的stub, 在完成调用后由stub将结果传回Proxy. 对于对象的Interface, 也是同样的道理.
       对于COM+的Thread-Neutral Apartment比较特殊, 它是一直需要marshal的, 一个方面是因为它处于不同的进程中.另外一个重要原因是, COM+提供了一系列新的功能, 如Object Pooling, Object Construct String等. COM+必须要截获Client对COM对象的调用才能完成将COM对象从缓冲池中取出以及放回缓冲池等的操作.
       那么marshal是自动还是手工完成的呢? 方法调用是自动完成的.对象的Interface的传递,一般情况下,  是自动完成的,比如你通过调用CoCreateInstanceEx,CoGetClassObject等得到的对象Interface,以及通过方法调用传递的对象Interface.但是有些情况下必须手工marshal. 还是形象写,举个例子: 比如我有一个COM Server, 它监视一个工控装置的信号, 如果信号有异常,它要通知客户端,让客户端进行报警动作.为了实现这个功能, 客户端和我的COM Server通过IConnectionPoint完成事件触发机制.为了提高性能, COM Server的主线程接受客户端通过IConnectionPoint的Advise传来的Interface指针, 并将之放到一个Interface指针表里, 主线程运行在STA中. 另外建立了一个运行于MTA中的线程专门用来监视信号,如果信号异常,它将调用Interface指针表里所有Interface的方法来通知客户端. 现在如果理解COM的机制的人看到我这个实现方法就知道这里面需要对Interface指针进行手工marshal. 为什么, 客户端通过IConnectionPoint传给我的COM Server主线程的Interface指针是自动进行了marshal, 但是, 由于我的COM Server的专门用来监视信号的线程运行在和主线程不同的Apartment之中, 对这个线程来说, 这些Interface指针是没有经过marshal的, 在调用是就会出现RPC_E_WRONG_THREAD错误. 要解决这个问题,有两个办法,
1) 让我的主线程也运行在MTA中.这种方法简单.
2) 手工marshal.
     在主线程中得到客户端的Interface指针后, 调用CoMarshalInterThreadInterfaceInStream, 得到一个IStream的指针,让后将它放到IStream的指针表里, 监视信号的线程要通知客户端时,从IStream的指针表取得IStream指针, 然后调用CoGetInterfaceAndReleaseStream得到marshal后的客户端Interface指针. 这种方法有个缺点, 就是一旦调用CoGetInterfaceAndReleaseStream后这个IStream指针就被释放掉了,下一次就取不到了. 更好的解决方法是采用GIT(global interface table), 主线程将它得到的Interface指针放到GIT, 监视信号的线程从GIT中取到的Interface指针是正确marshal了的. GIT是一个COM对象, 有三个方法提供Interface指针的存取,使用也很简单,这儿就不多说了,具体请参照帮助.

你可能感兴趣的:(com)