OLE 线程模型的说明和工作
本文介绍 OLE 线程模型。
原始产品版本: OLE 线程模型
原始 KB 数: 150777
总结
COM 对象可在进程的多个线程中使用。 术语“单线程单元”(STA)和“多线程单元”(MTA)用于创建一个概念框架,用于描述对象和线程之间的关系、对象的并发关系、方法调用传递到对象的方式,以及用于在线程之间传递接口指针的规则。 组件及其客户端选择 COM 目前支持的以下两种单元模型:
单线程单元模型(STA):进程中的一个或多个线程使用 COM,COM 对 COM 对象的调用由 COM 同步。 接口在线程之间封送。 单线程单元模型的退化情况,其中给定进程中只有一个线程使用 COM,称为单线程模型。 以前有时将 STA 模型称为“单元模型”。
多线程单元模型 (MTA):一个或多个线程使用 COM,对与 MTA 关联的 COM 对象的调用由与 MTA 关联的所有线程直接进行,而无需在调用方和对象之间进行任何系统代码交错。 由于多个同时客户端可能同时调用对象(同时在多处理器系统上),因此对象必须自行同步其内部状态。 接口不会封送在线程之间。 以前有时将此模型称为“自由线程模型”。
STA 模型和 MTA 模型都可以在同一进程中使用。 这有时称为“混合模型”过程。
MTA 在 NT 4.0 中引入,在 Windows 95 中使用 DCOM95 提供。 STA 模型存在于 Windows NT 3.51 和 Windows 95 以及具有 DCOM95 的 NT 4.0 和 Windows 95 中。
概述
COM 中的线程模型为使用不同线程体系结构的组件提供协同工作的机制。 他们还向需要它们的组件提供同步服务。 例如,特定对象可以设计为仅由单个线程调用,并且不能同步来自客户端的并发调用。 如果多个线程并发调用此类对象,它将崩溃或导致错误。 COM 提供了处理线程体系结构这种互操作性的机制。
即使是线程感知组件也通常需要同步服务。 例如,具有图形用户界面(GUI)(例如 OLE/ActiveX 控件、就地活动嵌入和 ActiveX 文档)的组件需要 COM 调用和窗口消息的同步和序列化。 COM 提供这些同步服务,以便无需复杂的同步代码即可编写这些组件。
一个“公寓”有几个相互相关的方面。 首先,它是一个逻辑构造,用于考虑并发,例如线程与一组 COM 对象的关系。 其次,这是一组程序员必须遵守的规则,才能接收来自 COM 环境的并发行为。 最后,它是系统提供的代码,可帮助程序员管理 COM 对象的线程并发。
术语“公寓”来自一个隐喻,其中一个过程被设想为离散实体,如“建筑”,细分为一组相关但不同的“区域设置”,称为“公寓”。单元是一个“逻辑容器”,用于在对象之间创建关联,在某些情况下,是线程。 线程不是单元,尽管在逻辑上可能存在与 STA 模型中单元关联的单个线程。 对象不是单元,尽管每个对象都与一个单元相关联,但只有一个单元。 但公寓不仅仅是一个逻辑构造:其规则描述 COM 系统的行为。 如果未遵循单元模型的规则,COM 对象将无法正常运行。
更多详细信息
单线程单元 (STA) 是一组与特定线程关联的 COM 对象。 这些对象通过线程创建来关联单元,或者更确切地说,这些对象首先通过封送在线程上公开给 COM 系统(通常通过封送)。 STA 被视为对象或代理“生活”的位置。如果对象或代理需要由另一单元(在同一个或不同的进程中)访问,则其接口指针必须封送至在其中创建新代理的公寓。 如果遵循单元模型的规则,则同一进程中的其他线程不允许直接调用该对象;这违反了给定单元中的所有对象在单个线程上运行的规则。 该规则存在,因为如果其他线程上运行,在 STA 中运行的大多数代码将无法正常工作。
与 STA 关联的线程必须调用 CoInitialize
或 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
必须检索和调度窗口消息,以便关联的对象接收传入调用。 COM 使用窗口消息调度和同步对 STA 中的对象的调用,如本文稍后所述。
“main STA”是调用或首先在给定进程中调用 CoInitialize
的 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
线程。 在完成所有 COM 工作之前,进程的主要 STA 必须保持活动状态,因为某些进程内对象始终加载到主 STA 中,如本文后面部分所述。
Windows NT 4.0 和 DCOM95 引入了一种称为多线程单元(MTA)的新单元类型。 MTA 是一组与进程中的线程关联的 COM 对象,这样任何线程都可以直接调用任何对象实现,而无需系统代码的交错。 可以在与 MTA 关联的线程之间传递指向 MTA 中的任何对象的接口指针,而无需封送。 调用过程中 CoInitializeEx(NULL, COINIT_MULTITHREADED)
的所有线程都与 MTA 相关联。 与上述 STA 不同,MTA 中的线程不需要检索和调度关联的对象接收传入调用的窗口消息。 COM 不会同步对 MTA 中的对象的调用。 MTA 中的对象必须通过多个同时线程的交互来保护其内部状态免受损坏,并且不能对不同方法调用之间线程本地存储剩余常量的内容做出任何假设。
一个进程可以具有任意数量的 STA,但最多可以有一个 MTA。 MTA 由一个或多个线程组成。 STA 各有一个线程。 线程最多属于一个单元。 对象属于一个单元和一个单元。 接口指针应始终在单元之间封送(尽管封送处理的结果可能是直接指针而不是代理)。 请参阅下面的 CoCreateFreeThreadedMarshaler
信息。
进程选择 COM 提供的线程模型之一。 STA 模型进程具有一个或多个 STA,并且没有 MTA。 MTA 模型进程具有一个或多个线程的 MTA,并且没有任何 STA。 混合模型过程有一个 MTA 和任意数量的 STA。
单线程单元模型
STA 的线程必须调用 CoInitialize
或 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
必须检索和调度窗口消息,因为 COM 使用窗口消息来同步和调度对此模型中对象的调用的传递。 有关详细信息,请参阅下面的 REFERENCES 部分。
支持 STA 模型的服务器:
在 STA 模型中,COM 以与发布到窗口的窗口消息同步的方式同步对对象的调用。 调用使用窗口消息传递到创建对象的线程。 因此,对象的线程必须调用 Get
/PeekMessage
和 DispatchMessage
接收调用。 COM 将创建一个与每个 STA 关联的隐藏窗口。 COM 运行时使用发布到此隐藏窗口的窗口消息将 COM 运行时从 STA 外部对对象的调用传输到对象的线程。 当与对象的 STA 关联的线程检索并调度消息时,隐藏窗口的窗口过程(也由 COM 实现)会收到它。 COM 运行时使用窗口过程“挂钩”与 STA 关联的线程,因为 COM 运行时位于从 COM 拥有的线程到 STA 线程的调用的两端。 COM 运行时(现在在 STA 线程中运行)通过 COM 提供的存根调用“up”到对象的相应接口方法。 从方法调用返回的执行路径将反转“up”调用;该调用返回到存根和 COM 运行时,该运行时通过窗口消息将控制权传回 COM 运行时线程,然后通过 COM 通道返回到原始调用方。
当多个客户端调用 STA 对象时,通过传输 STA 中使用的控制机制自动在消息队列中排队调用。 每次对象的 STA 检索和调度消息时,该对象都会接收调用。 由于调用以这种方式由 COM 同步,并且由于调用始终在与对象的 STA 关联的单个线程上传递,因此该对象的接口实现不需要提供同步。
注意
如果接口方法实现在处理方法调用时检索和调度消息,导致同一 STA 将另一个调用传递到对象,则可以重新输入该对象。 这种情况的一种常见方法是,如果 STA 对象使用 COM 进行外出(跨单元/跨进程)调用。 这与在处理消息时检索和调度消息时可以重新输入窗口过程的方式相同。 COM 不会阻止在同一线程上重新进入,但会阻止并发执行。 它还提供一种可管理与 COM 相关的重新进入的方法。 有关详细信息,请参阅下面的 REFERENCES 部分。 如果方法实现不调用其单元或其他检索和调度消息,则不会重新输入该对象。
STA 模型中的客户端职责:
在进程和/或线程中运行的客户端代码(使用 STA 模型)必须通过使用 CoMarshalInterThreadInterfaceInStream
和 CoGetInterfaceAndReleaseStream
封送单元之间对象的接口。 例如,如果客户端中的单元 1 具有接口指针,而单元 2 需要使用,则单元 1 必须使用该接口 CoMarshalInterThreadInterfaceInStream
。 此函数返回的流对象是线程安全的,其接口指针应存储在单元 2 可访问的直接内存变量中。 单元 2 必须将此流接口传递给 CoGetInterfaceAndReleaseStream
对基础对象上的接口进行解封,并返回指向代理的指针,通过该代理可以访问该对象。
给定进程的主单元应保持活动状态,直到客户端完成所有 COM 工作,因为某些进程内对象被加载到主单元中。 (有关详细信息,请参阅下文)。
多线程单元模型
MTA 是由已调用 CoInitializeEx(NULL, COINIT_MULTITHREADED)
的进程中的所有线程创建或公开的对象集合。
注意
COM 的当前实现允许未显式初始化 COM 的线程成为 MTA 的一部分。 不初始化 COM 的线程仅在之前调用 CoInitializeEx(NULL, COINIT_MULTITHREADED)
过至少一个线程之后开始使用 COM 时,才会成为 MTA 的一部分。 (如果尚未显式执行任何客户端线程,则 COM 本身可能已初始化 MTA;例如,与 STA 调用 CoGetClassObject
/CoCreateInstance[Ex]
关联的线程在标记为“ThreadingModel=Free”的 CLSID 上,COM 隐式创建加载类对象的 MTA。请参阅下面有关线程模型互操作性的信息。
但是,这是一种配置,在某些情况下可能会导致问题,例如访问冲突。 因此,建议每个需要执行 COM 工作的线程通过调用 CoInitializeEx
,然后在完成 COM 工作时调用 CoUninitialize
COM 来初始化 COM。 “不必要的”初始化 MTA 的成本是最小的。
MTA 线程不需要检索和调度消息,因为 COM 不会在此模型中使用窗口消息来传递对对象的调用。
支持 MTA 模型的服务器:
在 MTA 模型中,COM 不会同步对对象的调用。 多个客户端可以同时在不同线程上调用支持此模型的对象,并且该对象必须使用事件、互斥体、信号灯等同步对象在其接口/方法实现中提供同步。MTA 对象可以通过属于对象的进程的 COM 创建的线程池从多个进程外客户端接收并发调用。 MTA 对象可以在与 MTA 关联的多个线程上从多个进程内客户端接收并发调用。
MTA 模型中的客户端责任:
在进程和/或线程中运行的客户端代码(使用 MTA 模型)不必在自身和其他 MTA 线程之间封送对象的接口指针。 相反,一个 MTA 线程可以使用从另一个 MTA 线程获取的接口指针作为直接内存指针。 当客户端线程调用进程外对象时,它会挂起,直到调用完成。 调用可能会到达与 MTA 关联的对象,而与 MTA 关联的所有应用程序创建的线程都会在外出调用时被阻止。 在这种情况下,通常,传入调用是在 COM 运行时提供的线程上传递的。 消息筛选器 (
IMessageFilter
) 在 MTA 模型中不可用。
混合线程模型
支持混合线程模型的进程将使用一个 MTA 和一个或多个 STA。 接口指针必须在所有单元之间封送,但可以在 MTA 内不使用封送处理。 对 STA 中的对象的调用由 COM 同步,仅在 MTA 中的对象调用时在一个线程上运行。 但是,从 STA 到 MTA 的调用通常通过系统提供的代码,并在传递到对象之前从 STA 线程切换到 MTA 线程。
注意
有关可以使用直接指针的情况,以及 STA 线程如何直接调用与 MTA 关联的对象,反之亦然,请参阅下面的 SDK 文档 CoCreateFreeThreadedMarshaler()
和该 API 的讨论。
选择线程模型
组件可以选择使用混合线程模型来支持 STA 模型、MTA 模型或两者的组合。 例如,执行大量 I/O 的对象可以选择支持 MTA,通过允许在 I/O 延迟期间进行接口调用来向客户端提供最大响应。 或者,与用户交互的对象几乎总是选择支持 STA,以便将传入 COM 调用与其 GUI 操作同步。 由于 COM 提供同步,因此支持 STA 模型更容易。 由于对象必须实现同步,因此支持 MTA 模型更为困难,但对客户端的响应更好,因为同步用于较小的代码部分,而不是用于 COM 提供的整个接口调用。
STA 模型还由Microsoft事务服务器(MTS(以前为代码命名为“Viper”)使用,因此,计划在 MTS 环境中运行的基于 DLL 的对象应使用 STA 模型。 为 MTA 模型实现的对象通常在 MTS 环境中正常工作。 但是,它们将效率较低,因为它们将使用不必要的线程同步基元。
标记 Proc 服务器支持的线程模型
如果线程调用 CoInitializeEx(NULL, COINIT_MULTITHREADED)
或使用 COM 而不初始化它,则线程将使用 MTA 模型。 如果线程调用CoInitialize
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
或调用,则线程使用 STA 模型。
CoInitialize
API 为客户端代码和打包的对象提供单元控件。EXE,因为 COM 运行时的启动代码可以根据需要初始化 COM。
但是,基于 DLL 的 COM 服务器不会调用 CoInitialize
/CoInitializeEx
,因为这些 API 将在加载 DLL 服务器时调用。 因此,DLL 服务器必须使用注册表通知 COM 它支持的线程模型,以便 COM 可以确保系统以与其兼容的方式运行。 调用 ThreadingModel
的组件的 CLSID\InprocServer32 键的命名值用于此目的,如下所示:
ThreadingModel
值不存在:支持单线程模型。ThreadingModel=Apartment
:支持 STA 模型。ThreadingModel=Both
:支持 STA 和 MTA 模型。ThreadingModel=Free
:仅支持 MTA。
注意
ThreadingModel
是一个命名值,而不是 InprocServer32 的子项,因为 Win32 文档的某些早期版本中未正确记录。
本文稍后将讨论过程内服务器的线程模型。 如果代理服务器提供许多类型的对象(每个对象都有自己的唯一 CLSID),则每种类型可以具有不同的 ThreadingModel
值。 换句话说,线程模型是每个 CLSID,而不是每个代码包/DLL。 但是,对于支持多个线程的任何进程内服务器(这意味着ThreadingModel
单元、两者或免费值)来说,“启动”和查询所有进程内服务器DLLGetClassObject()
DLLCanUnloadNow()
所需的 API 入口点必须是线程安全的。
如前所述,进程外服务器不会使用 ThreadingModel 值标记自身。 相反,它们使用 CoInitialize
或 CoInitializeEx
。 希望使用 COM 的“代理项”功能的基于 DLL 的服务器(如系统提供的代理项DLLHOST.EXE)遵循基于 DLL 的服务器的规则;在这种情况下,没有任何特殊注意事项。
当客户端和对象使用不同的线程模型时
即使使用不同的线程模型,客户端和进程外对象之间的交互也是直接的,因为客户端和对象位于不同的进程中,COM 也参与将调用从客户端传递到对象。 由于 COM 在客户端和服务器之间交错,因此它提供用于线程模型互操作的代码。 例如,如果多个 STA 或 MTA 客户端并发调用 STA 对象,COM 会将相应的窗口消息放在服务器的消息队列中来同步调用。 每次检索和调度消息时,对象的 STA 都会收到一个调用。 允许线程模型互操作性的所有组合,并在客户端和进程外对象之间完全支持。
客户端与使用不同线程模型的代理对象之间的交互更为复杂。 尽管服务器在代理中,但在某些情况下,COM 必须在客户端和对象之间交错自身。 例如,旨在支持 STA 模型的进程内对象可由客户端的多个线程并发调用。 COM 不允许客户端线程直接访问对象的接口,因为该对象不是为此类并发访问而设计的。 相反,COM 必须确保调用是同步的,并且仅由与“包含”对象的 STA 关联的线程进行。 尽管增加了复杂性,但客户端和过程内对象之间允许线程模型互操作性的所有组合。
进程外服务器(基于 EXE)服务器的线程模型
以下是三类进程外服务器,每个服务器都可以由任何 COM 客户端使用,而不管该客户端使用的线程模型如何:
STA 模型服务器:
服务器在一个或多个 STA 中工作。 传入调用由 COM 同步,并由与在其中创建对象的 STA 关联的线程传递。 类工厂方法调用由与注册类工厂的 STA 关联的线程传递。 对象和类工厂不需要实现同步。 但是,实现程序必须同步对多个 STA 使用的任何全局变量的访问。 服务器必须使用
CoMarshalInterThreadInterfaceInStream
和CoGetInterfaceAndReleaseStream
封送接口指针(可能来自其他服务器)在 STA 之间。 服务器可以选择在每个 STA 中实现IMessageFilter
,以控制 COM 调用传递的各个方面。 退化情况是一个单线程模型服务器,该服务器在一个 STA 中执行 COM 工作。MTA 模型服务器:
服务器在一个或多个线程中工作,所有这些线程都属于 MTA。 COM 不会同步调用。 COM 在服务器进程中创建线程池,客户端调用由其中任何线程传递。 线程不需要检索和调度消息。 对象和类工厂必须实现同步。 服务器不需要封送线程之间的接口指针。
混合模型服务器:
有关详细信息,请参阅本文中标题为“混合线程模型”的部分。
客户端中的线程模型
有三类客户端:
STA 模型客户端:
客户端在与一个或多个 STA 关联的线程中工作。 客户端必须使用
CoMarshalInterThreadInterfaceInStream
CoGetInterfaceAndReleaseStream
和封送 STA 之间的接口指针。 退化情况是一个单线程模型客户端,该客户端在一个 STA 中执行 COM 工作。 客户端的线程在发出传出调用时进入 COM 提供的消息循环。 客户端可用于IMessageFilter
在等待外出调用和其他并发问题时管理窗口消息的回调和处理。MTA 模型客户端:
客户端在一个或多个线程中工作,所有这些线程都属于 MTA。 客户端不需要在其线程之间封送接口指针。 客户端无法使用
IMessageFilter
。 客户端的线程在对进程外对象进行 COM 调用时挂起,并在调用返回时恢复。 传入呼叫抵达 COM 创建的托管线程。混合模型客户端:
有关详细信息,请参阅本文中标题为“混合线程模型”的部分。
基于 DLL 的服务器中的线程模型
有四种类别的代理服务器,每个服务器都可以由任何 COM 客户端使用,而不管该客户端使用的线程模型如何。 但是,如果它们支持线程模型互操作性,则代理内服务器必须为它们实现的任何自定义(非系统定义)接口提供封送代码,因为这通常要求 COM 封送客户端单元之间的接口。 四个类别包括:
支持单线程(“main” STA)的代理服务器 - 无
ThreadingModel
值:此服务器提供的对象需要由创建它的同一客户端 STA 访问。 此外,服务器需要同一线程(与主 STA 关联的入口点)访问其所有入口点,例如
DllGetClassObject
DllCanUnloadNow
和全局数据。 在 COM 中引入多线程之前存在的服务器属于此类别。 这些服务器不是由多个线程访问的,因此 COM 会在进程的主 STA 中创建服务器提供的所有对象,对对象的调用由与主 STA 关联的线程传递。 其他客户端单元通过代理访问对象。 来自其他单元的调用从代理转到主 STA(线程间封送)中的存根,然后转到对象。 此封送处理允许 COM 同步对对象的调用,调用由创建对象的 STA 传递。 与直接调用相比,线程间封送处理速度较慢,因此建议重写这些服务器以支持多个 STA(类别 2)。支持单线程单元模型(多个 STA)的代理服务器 - 标有
ThreadingModel=Apartment
:此服务器提供的对象需要由创建它的同一客户端 STA 访问。 因此,它类似于单线程内服务器提供的对象。 但是,此服务器提供的对象可以在进程的多个 STA 中创建,因此服务器必须设计其入口点,例如
DllGetClassObject
DllCanUnloadNow
以及用于多线程使用的全局数据。 例如,如果进程的两个 STA 同时创建两个进程内对象的实例,DllGetClassObject
则两个 STA 可以同时调用。 同样,必须编写服务器DllCanUnloadNow
,以便在服务器中仍在执行代码时保护服务器不被卸载。如果服务器仅提供类工厂的一个实例来创建所有对象,则类工厂实现也必须设计为多线程使用,因为它由多个客户端 STA 访问。 如果每次调用该类工厂时服务器都会创建类工厂的新实例,则类工厂
DllGetClassObject
不需要线程安全。 但是,实现程序必须同步对任何全局变量的访问。类工厂创建的 COM 对象不需要线程安全。 但是,全局变量的访问必须由实现者同步。 线程创建后的对象始终通过该线程访问,并且 COM 会同步所有对象调用。 与创建对象的 STA 不同的客户端单元必须通过代理访问该对象。 当客户端封送其单元之间的接口时,将创建这些代理。
通过类工厂创建 STA 对象的任何客户端都获取指向该对象的直接指针。 这与单线程中对象不同,其中客户端的主 STA 仅获取指向该对象的直接指针,以及创建该对象的其他所有 STA 都可以通过代理访问该对象。 由于线程间封送处理相对于直接调用速度较慢,因此可以通过更改单线程内服务器来支持多个 STA 来提高速度。
仅支持 MTA 的代理服务器 - 标记为
ThreadingModel=Free
:此服务器提供的对象仅适用于 MTA。 它实现自己的同步,并同时由多个客户端线程访问。 此服务器的行为可能与 STA 模型不兼容。 (例如,通过使用 Windows 消息队列的方式破坏 STA 的消息泵)。此外,通过将对象的线程模型标记为“Free”,对象的实现器指出以下内容:可以从任何客户端线程调用此对象,但此对象还可以将接口指针直接(无需封送)传递给它创建的任何线程,并且这些线程可以通过这些指针进行调用。 因此,如果客户端将接口指针传递给客户端实现的对象(如接收器),则它可以选择从它创建的任何线程通过此接口指针进行回调。 如果客户端是 STA,则来自线程的直接调用与创建接收器对象的线程不同(如上面的 2 所示)。 因此,COM 始终确保与 STA 关联的线程中的客户端仅通过代理访问此类进程内对象。 此外,这些对象不应与自由线程封送处理器聚合,因为这允许它们直接在 STA 线程上运行。
支持单元模型和自由线程的代理服务器 - 标有
ThreadingModel=Both
:此服务器提供的对象实现其自己的同步,并由多个客户端单元同时访问。 此外,此对象在 STA 或客户端进程的 MTA 中直接创建和使用,而不是通过代理使用。 由于此对象直接在 STA 中使用,因此服务器必须在线程之间封送对象(可能来自其他服务器)的接口,因此可以保证其以线程适当的方式访问任何对象。 此外,通过将对象的线程模型标记为“Both”,对象的实现器指出以下内容:可以从任何客户端线程调用此对象,但从此对象到客户端的任何回调将仅在对象接收指向回调对象的接口指针的单元上执行。 COM 允许在 STA 和客户端进程的 MTA 中直接创建此类对象。
由于创建此类对象的任何单元始终获取直接指针而不是代理指针,因此当在 STA 中加载时,
ThreadingModel "Both"
对象会提供ThreadingModel "Free"
对对象的性能改进。由于对象
ThreadingModel "Both"
也是针对 MTA 访问设计的(它在内部是线程安全的),因此它可以通过聚合提供的CoCreateFreeThreadedMarshaler
封送器来加快性能。 此系统提供的对象聚合到任何调用对象,自定义封送会将指向该对象的指针定向到进程中的所有单元中。 然后,任何单元(无论是 STA 还是 MTA)中的客户端可以直接访问对象,而不是通过代理访问对象。 例如,STA 模型客户端在 STA1 中创建 in-proc 对象,并将对象封送至 STA2。 如果对象未与自由线程封送处理器聚合,则 STA2 通过代理获取对对象的访问。 如果这样做,则自由线程封送器为 STA2 提供指向对象的直接指针注意
与自由线程封送处理器聚合时必须小心。 例如,假定标记为
ThreadingModel "Both"
(并且还与自由线程封送器聚合)的对象具有一个数据成员,该成员是指向另一个对象(其为“Apartment”)的ThreadingModel
接口指针。 然后,假设 STA 创建第一个对象,并在创建期间,第一个对象创建第二个对象。 根据上述规则,第一个对象现在持有指向第二个对象的直接指针。 现在假设 STA 将接口指针封送到另一个单元的第一个对象。 由于第一个对象使用自由线程封送处理器聚合,因此会向第二个单元提供指向第一个对象的直接指针。 如果第二个单元通过此指针调用,并且如果此调用导致第一个对象通过接口指针调用第二个对象,则发生了错误,因为第二个对象不是直接从第二个单元调用的。 如果第一个对象持有指向第二个对象的代理的指针,而不是直接指针,这将导致不同的错误。 系统代理也是与一个单元和一个单元关联的 COM 对象。 他们跟踪他们的公寓,以避免某些循环。 因此,与运行该对象的线程关联的代理上调用的对象将接收代理返回RPC_E_WRONG_THREAD,调用将失败。
客户端和进程内对象之间的线程模型互操作性
允许客户端和进程内对象之间实现线程模型互操作性的所有组合。
COM 允许所有 STA 模型客户端通过创建和访问客户端主 STA 中的对象并将其封送给调用 CoCreateInstance[Ex]
的客户端 STA 来与单线程内对象交互。
如果客户端中的 MTA 在代理服务器中创建 STA 模型,COM 会在客户端中启动“主机”STA。 此主机 STA 将创建对象,接口指针被封送回 MTA。 同样,当 STA 在过程内创建 MTA 服务器时,COM 会启动主机 MTA,在该主机 MTA 中创建对象并将其封送回 STA。 单线程模型和 MTA 模型之间的互操作性也处理得类似,因为单线程模型只是 STA 模型的退化情况。
如果想要支持需要 COM 在客户端单元之间封送接口的互操作性,则必须为代理服务器实现的任何自定义接口提供封送代码。 有关详细信息,请参阅下面的 REFERENCES 部分。
线程模型与返回的类工厂对象之间的关系
以下步骤介绍了“加载到”单元中的服务器精确定义:
如果操作系统加载程序之前未加载包含进程内服务器类的 DLL(映射到进程地址空间),则执行该操作,COM 将获取 DLL 导出的函数的地址
DLLGetClassObject
。 如果 DLL 以前已由与任何单元关联的线程加载,则跳过此阶段。COM 使用与“加载”单元关联的线程(或者,在 MTA 中,其中一个线程)调用
DllGetClassObject
DLL 导出的函数,请求所需类的 CLSID。 然后,返回的工厂对象用于创建类对象的实例。第二步(COM 调用
DllGetClassObject
),每次客户端调用CoGetClassObject
/CoCreateIntance[Ex]
时(即使在同一单元内也是如此)。 换句话说,DllGetClassObject
同一单元关联的线程可能会多次调用;这一切都取决于该单元中的客户端尝试访问该类的类工厂对象。
类实现的作者,最终,给定一组类的 DLL 包的作者可以完全自由地决定要返回的工厂对象以响应 DllGetClassObject
函数调用。 DLL 包的作者具有最终发言权,因为单个 DLL 范围的 DllGetClassObject()
入口点的代码“隐藏”是决定操作的第一个和可能最终的权利。 三种典型可能性包括:
DllGetClassObject
为每个调用线程返回一个唯一的类工厂对象(这意味着每个 STA 有一个类工厂对象,并且 MTA 中可能存在多个类工厂)。DllGetClassObject
无论调用线程的身份还是与调用线程关联的单元类型,始终返回相同的类工厂对象。DllGetClassObject
返回每个调用单元的唯一类工厂对象(STA 和 MTA 中的每个单元一个)。
调用 DllGetClassObject
与返回的类工厂对象(如每次调用 DllGetClassObject
的新类工厂对象)之间的关系还有其他可能性,但它们目前似乎没有用处。
Proc 服务器客户端和对象线程模型的摘要
下表总结了当客户端线程首次调用 CoGetClassObject
作为内部服务器实现的类时,不同线程模型之间的交互。
客户端/线程的类型:
- 客户端在与“main”STA(要调用
CoInitialize
的第一个线程或CoInitializeEx
标志COINIT_APARTMENTTHREADED
)关联的线程中运行-调用此 STA0(也称为单线程模型)。 - 客户端在与任何其他 STA [ASCII 150] 关联的线程中运行,调用此 STA*。
- 客户端在与 MTA 关联的线程中运行。
DLL 服务器的类型:
- 服务器没有
ThreadingModel
键--调用此“无”。 - 服务器标记为“Apartment”--call this “Apt”。
- 服务器标记为“免费”。
- 服务器标记为“两者”。
阅读下表时,请记住上面定义的“将服务器加载”到单元中。
Client Server Result
STA0 None Direct access; server loaded into STA0
STA* None Proxy access; server loaded into STA0.
MTA None Proxy access; server loaded into STA0; STA0 created automatically by COM if necessary;
STA0 Apt Direct access; server loaded into STA0
STA* Apt Direct access; server loaded into STA*
MTA Apt Proxy access; server loaded into an STA created automatically by COM.
STA0 Free Proxy access; server is loaded into MTA MTA created automatically by COM if necessary.
STA* Free Same as STA0->Free
MTA Free Direct access
STA0 Both Direct access; server loaded into STA0
STA* Both Direct access; server loaded into STA*
MTA Both Direct access; server loaded into the MTA
参考
SDK 文档 CoRegisterMessageFilter()
和 IMessageFilter
接口。