Apartments-----套间

https://docs.microsoft.com/en-us/windows/desktop/com/processes--threads--and-apartments

进程、线程和套间

 

多线程应用程序必须避免两个线程问题,死锁,冲突。COM call control 可以帮助避免两个对象之间的调用时的死锁问题。COM 支持一些功能,这些功能被设计为,帮助进程外服务避免冲突的情况。

 

套间和COM 线程结构

将进程中所有的COM 对象分成组,这些组就称为套间。一个COM 对象存在于一个套间中,此时,它的方法可以被属于这个套间的线程合法的直接调用。其它任何线程如果想调用该对象的方法,必须经过代理。

有两种类型的套间:单线程套间,多线程套间。

1. 单线程套间包含一个线程,STA 中所有的COM 对象只能从属于STA 的线程接收到方法调用。STA 中所有COM 对象的方法调用,都将用STA 的线程的windows 消息队列的方式同步。

2. 多线程套间包含一个或多个线程,因此所有的MTA 中的所有COM 对象能从属于MTA 的任意一个线程直接接收到方法调用。MTA 中的线程使用了一个称作free-threading 的模型。调用MTA 中的对象的方法,将被对象本身同步。

一个进程可以拥有,0+STA,0或1MTA

进程中,第一个被初始化的STA 被称为主STA。套间之间的调用参数将被调整,COM 通过messaging处理它们的同步。如果设置一个进程中的多个线程为free-threaded,所有的free 线程存在于同一个套间,MTA 中任意线程的参数都是直接传递的,coder 必须处理所有的同步问题。一个同时拥有free-threading 和 apartment threading 的进程,所有的free threads 在一个单独的套间,所有其它的套间都是单线程套间。

https://docs.microsoft.com/en-us/windows/desktop/com/single-threaded-apartments

使用STA,提供了一种基于消息的模型,解决多对象同时运行的问题。它使得coder 可以编写更加高效的代码,以允许一个线程,在它等待一个耗时的操作完成的时候,允许另外一个线程执行。

初始化为套间模型进程中的每个线程,以及接收并分发window 消息的线程,都是一个STA 线程。每个线程都在其自己的套间中。套间内所有对象的通信、调用都是直接的,不需要转换。

逻辑上一组的相关的、执行在相同的线程的对象,因此必须同步执行,它们可以存在于一个STA 线程。但,一个套间模型对象,不能存在于多个线程。

对于其它进程中的对象的调用,必须在拥有该对象的进程的上下文中进行,因此,分发COM 将在你调用代理的时候自动的转换线程。

STA 规则:

1. 每个对象,只存在于一个线程

2. 为每个线程初始化COM library

3. 当在套间之间传递对象的指针时,需要转换

4. 每个STA 需要有一个message loop。STA 没有对象(客户),也需要一个message loop,以发送某些应用程序使用的广播信息。

5. DLL-based 或 进程内对象,不调用COM 初始函数;相应的,它们注册它们在注册表键的InprocServer32 子键下的ThreadingModel 值下注册它们的线程模型。可识别套间的对象,必须小心的编写DLL 入口点。

 

客户进程的每个线程或者进程外服务必须调用CoInitialize 或 调用CoInitializeEx 并在dwCoInit参数指定COINIT_APARTMENTTHREADED 。第一个调用CoInitializeEx 的线程为主套间。

对于一个对象的调用,必须在它自己的线程上(它的套间上)。禁止从另外的线程(这里讨论的是STA,因为,线程就是套间)来直接调用一个对象。COM 提供了两个函数来帮助达成这个目的:

1. CoMarshalInterThreadInterfaceInStream 转换一个接口为一个流对象,并将其返回给调用者

2. CoGetInterfaceAndReleaseStream 逆转换一个流对象,得到一个接口指针,之后会释放该流对象。

上面的函数,内部调用CoMarshalInterface 和 CoUnmarshalInterface 函数,并传入MSHCTX_INPROC标志。

通常来说,COM 将自动完成这个转换的过程,比如,将一个指针传递给代理或者调用CoCreateInstance。但有时,coder 使用非常规的方法在套间之间传递指针,此时需要正确的处理转换操作。

如果进程中的一个套间(A1)有一个接口指针,且另一个套间(A2)请求使用它。A1 必须调用CoMarshal* 来转换该接口。生成的流对象是线程安全的,它应该被存储在A2 绑定的一个变量上。A2 必须调用CoGetInterface* ( 流对象),以得一个指向了代理的指针,通过该代理,A2 可以访问该接口。在客户完成COM 工作之前,主套间需要一直alive。以这种方式在线程之前传递对象时,将接口作为参数传递将非常方便,分发COM 将为应用程序做转换和线程转换操作。

 

STA 必须有一个消息循环,如果使用了与其它线程的其它同步方式,使用MsgWaitForMultipleObjects 即可。

COM 为每个STA线程创建了一个windows class 为 "OleMainThreadWndClass"的隐藏窗口。对一个对象的调用,将被以对这个隐藏窗口的消息的形式接受。当一个对象的套间接受并分发消息,隐藏窗口将接收到它。windows 过程将调用对应的对象的接口方法。

多客户调用对象--->消息队列-->一次一个。自动同步。STA 可以实现IMessageFilter 以允许或禁止特定的windows 消息。

windows 窗口消息,是可以重入的,当它处理某个消息的时候,它接受并分发了消息,此时产生重入。STA类似。

 

https://docs.microsoft.com/en-us/windows/desktop/com/multithreaded-apartments

不使用windows 消息,MTA 中创建的对象需要有能力处理来自其它线程任意时间的访问。

可以有效的利用多线程的高性能又是,但接口实现时需要提供同步能力。另外,对象不控制访问它的线程的生命周期,不会在对象上存储线程特定的信息。

需要注意的点:

1. MTA 中making calls时,不会接收到函数调用(在同一个线程)

2. MTA 不会实现输入-同步 调用。

3. 异步调用,被转换为同步调用,在MTA 中

4. MTA 中,任何线程的消息过滤都不被调用

将线程初始化为free-threaded,调用CoInitializeEx(CONINIT_MULTITHREADED)

 

MTA 进程外服务,COM 通过RPC 子系统,在服务进程中创建一个线程池,客户调用,可以通过其中的任意线程在任意时间被提交。

当客户对进程外套间对象进行COM 调用,它将暂时suspend,之后,当call 返回,客户恢复执行。进程间的调用是由RPC 处理的。

 

https://docs.microsoft.com/en-us/windows/desktop/com/in-process-server-threading-issues

进程内服务线程的问题

进程内服务,不通过调用CoInitialize、CoInitializeEx 或 OleInitialize 来标志它的线程模型,需要在注册表中显式的指定其线程模型,如果不指定,默认的,线程模型为:每个进程一个单独的线程。

 

当一个客户的MTA 创建一个STA 进程内服务器,COM 创建一个STA “host” 线程。该host thread 将创建给对象,接口指针,将被marshaled back 到客户的MTA。 类似的,当套间-模型的客户的STA 创建一个MTA  进程内服务,COM 创建一个MTA host thread(对象将在此被创建,其指针被marshaled ,并传回给客户的STA ),

当进程内DLL ThreadingModel 设置为 Both,该DLL 创建的对象可以被创建,并被直接访问(STA或MTA)。但是,只能被它所存在的套间直接访问。为了将其指针传递给其它的套间,该对象必须被marshaled,该DLL 对象必须实现它自己的同步,并实现多线程访问能力。

为了提高MTA 对于 进程内DLL 对象的访问速度,COM 提供CoCreateFreeThreadedMarshaler 函数。该函数创建一个free-threaded marshaling 对象,该对象可以在请求进程内服务对象的时候合并使用。当同一个进程内的客户套间,需要访问另一个套间中的对象,合并的free-threaded marshaler 给客户提供了一个服务对象的直接的指针,而不是代理的指针。客户不需要做任何的同步操作。这个操作,仅限于同一个进程。

 

http://www.ecs.syr.edu/faculty/fawcett/handouts/CSE775/Presentations/Apartments.ppt

套间:

支持三种套间:

1. 单线程套间(STAs)

COM 通过windows 消息循环来序列化所有的外部到套件线程的调用。套间的单线程通过从消息队列中取出消息并执行的方式“服务”其它的方法调用

这意味着,不是线程安全的组件,可以安全的在win32 多线程环境下执行,只要它是被STA 创建的

2. 多线程套间MTA

COM 在MTA中没有条件序列化。任何在MTA 中创建的组件应该提供自己的同步能力,以保证线程安全。

3. 中立线程套间(NTA)

任何线程都可能离开STA 或 MTA 来访问NTA。NTAs 必须是完全的提供自己的同步能力。

 

套间规则

1. 一个进程可以有多个STAs,但只能有一个MTA

2. 每个COM 对象只能属于一个套间

3. 一个线程在某个时间点,只能执行一个套间。当一个线程进入了一个套间,COM 用套间ID 来标记它。

4. 对象只能在那些,执行在该对象所属的套间的线程中被直接的访问。

5. STA 中的对象,将只被创建该STA 的线程访问。因此,对象从来不能在多个STA 中同时访问。

 

创建套间

STA

当客户或基于EXE 的组件调用:

CoInitialize(NULL);

CoInitializeEx(NULL,CONINIT_APARTMENTTHREADED)

 

MTA

当客户或基于EXE 的组件调用

CoInitializeEx(NULL,CONINIT_MULTITHREADED)

 

第一次,新线程在同一进程中的后续调用会导致这些线程加入MTA。

 

加入套间

1. 在注册表中宣布没有线程模型的进程内组件被加载到客户端的主(第一)STA中(如果存在)。否则,COM 会为组件创建主STA。

2. 具有ThreadingModel=Apartment 注册表项的进程内组件被加载到实例化组件的任何客户端STA中。

3. 具有Threadingmodel=Free 注册表项的进程内组件将加载到客户端的MTA 中(如果存在)。否则,COM为组件创建主机MTA。

4. ThreadingModel=Both,加载到创建它的客户套间,STA或MTA

套间内部,以及套间之间的调用

1. 套间之内的调用是直接的---没有封装被调用

----STA中的实例只可以被该STA 中这一个线程直接访问

----一个进程内组件,客户的创建该组件的STA线程

----MTA 中的实例,可以被套间中的任意线程同时直接访问

2. 在另一个套间中,对组件的任何调用将被调整

----在远程机器上的进程间

---同一机器上的不同进程间

----同一进程间的两个套间间

 

进程内组件中线程模型的比较

 

调整 接口指针

  1. 套间之间,接口指针必须被转换
    1. 接口指针是套间相关的,它们仅可以被所属的套间中的线程使用
    2. 调用QueryInterface 时,COM 将服务端的所有的接口指针转换,并返回给客户,如果它们属于不同的套间。
  2. 如果一个服务是,进程内的, 且存在于一个STA 中,就不需要转换,有函数可以实现这个目的,看上面,当然也可以使用转换。

组件激活

对象是被服务控制管理器(SCM)激活的:

1. 进程内,将组件服务的DLL 加载到客户的地址空间

2. 本地,进程外,加载服务的exe,到它自己的本地的进程中

3. 远程,进程外,通知远程机器的SCM 去激活服务的EXE,在它的机器上

4. 进程内DLL ,可以以本地或远程组件的方式加载,远程加载时,使用dllhost.exe 进程,作为宿主进程。

5. 对于所有进程外组件,COM 在客户的地址空间加载proxy.dll ,在组件的地址空间加载stub

SCM

SCM 支持三种激活过程:

1. 绑定到类对象(class factory),使用CoGetClassObject

2. 绑定到类实例,使用CoCreateInstanceEx

3. 绑定到文件中的永久实例,使用CoGetInstanceFromFile

绑定到类对象和实例,导致新创建的对象没有状态历史,除非,class 和 class factory 构造函数做一些工作。

创建单件组件,使所有的客户激活都绑定到同一个类实例是可能的

如果你需要在激活的时候,保留类状态,在必要的时候,可以使用永久绑定

MTA 组件 中的成员调用

当CoInitialized,COM 开启RPC 服务,使得组件称为一个RPC 服务器

1. 因为对象被主机客户进程访问,访问被注册的网络协议,一个RPC 线程缓存被开启。

2. 缓存中的第一个线程监听进来的连接,并分发线程以处理每一个请求。

3. 被分配的线程找到stub 管理器,和接口stub。

4. 线程进入组件的套间,并调用IPrcStubBuffer::Invoke 方法(接口的存根),并进入组件的方法

5. MTA 中,随后的线程可能同时访问该对象,因此,同步对全局和本地静态数据的访问十分重要

STA 组件成员调用

当CoInitialized,COM 开启RPC 服务,使得组件称为一个RPC 服务器

1. 因为对象被主机客户进程访问,访问被注册的网络协议,一个RPC 线程缓存被开启。

2. 缓存中的第一个线程监听进来的连接,并分发线程以处理每一个请求。

3. 没有线程可以进入到该STA,除了第一个调用CoInitialize 的线程,RPC 线程向STA 线程的消息队列,post 一个消息

4. STA 线程的消息循环处理队列(GetMessage、DispatchMessage)

5. 因为所有的调用都在STA 线程上,所有的调用被以windows 消息队列的方式同步了。

转换结构:

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值