单线程单元
使用单线程单元(单元模型进程)提供一个基于消息的范例,用于处理同时运行的多个对象。 它使你能够编写更有效的代码,方法是允许执行一个线程,同时等待一些耗时的操作完成,以允许执行另一个线程。
初始化为单元模型进程且检索和调度窗口消息的进程中的每个线程都是单线程单元线程。 每个线程位于其自己的单元内。 在单元中,无需封送即可传递接口指针,因此,一个单线程单元线程中的所有对象都直接进行通信。
所有在同一线程上执行的相关对象的逻辑分组,因此必须具有同步执行,可以位于同一单线程单元线程上。 但是,单元模型对象不能驻留在多个线程上。 对其他线程中的对象的调用必须在拥有线程的上下文中进行,因此在调用代理时,分布式 COM 会自动切换线程。
进程间模型和线程间模型相似。 如果需要在同一进程中将接口指针传递给另一单元(在另一线程上)中的对象,则使用不同进程中的对象用于跨进程边界传递指针的相同封送模型。 通过获取指向标准封送对象的指针,可以采用与在进程之间相同的方式跨线程边界(单元之间)封送接口指针。 (接口指针必须在单元之间传递时封送。)
单线程单元的规则很简单,但请务必仔细遵循:
- 每个对象应仅位于一个线程上(在单线程单元中)。
- 为每个线程初始化 COM 库。
- 在单元之间传递对象时,封送所有指向对象的指针。
- 每个单线程单元必须有一个消息循环来处理同一进程中其他进程和单元的调用。 不带对象(仅客户端)的单线程单元也需要消息循环来调度某些应用程序使用的广播消息。
- 基于 DLL 或进程内的对象不调用 COM 初始化函数;而是使用注册表中 InprocServer32 项下的 ThreadingModel 命名值注册其线程模型。 单元感知对象还必须仔细编写 DLL 入口点。 有一些特殊的注意事项适用于进程内服务器的线程处理。 有关详细信息,请参阅进程内服务器线程处理问题。
虽然多个对象可以位于单个线程上,但单元模型对象不可以位于多个线程上。
客户端进程或进程外服务器的每个线程都必须调用 CoInitialize,或调用 CoInitializeEx,并为 dwCoInit 参数指定 COINIT_APARTMENTTHREADED。 主单元是首先调用 CoInitializeEx 的线程。 有关进程内服务器的信息,请参阅进程内服务器线程处理问题。
对对象的所有调用都必须在其线程(在其单元内)上进行。 禁止直接从另一个线程调用对象;以此自由线程方式使用对象可能会导致应用程序出现问题。 此规则的含义是,在单元之间传递时,必须封送指向对象的所有指针。 COM 为此提供以下两个函数:
- CoMarshalInterThreadInterfaceInStream 将接口封送到返回到调用方的流对象中。
- CoGetInterfaceAndReleaseStream 从流对象拆收接口指针并释放。
这些函数整合对 CoMarshalInterface 和 CoUnmarshalInterface 函数的调用,这些函数需要使用 MSHCTX_INPROC 标志。
通常,封送由 COM 自动完成。 例如,将接口指针作为代理上的方法调用中的参数传递到另一单元中的对象时,或者调用 CoCreateInstance 时,COM 会自动执行封送。 但是,在某些特殊情况下,应用程序编写器在单元之间传递接口指针而不使用一般 COM 机制,编写器必须手动处理封送。
如果进程中的一个单元(单元 1)具有接口指针,另一个单元(单元 2)需要其使用,则单元 1 必须调用 CoMarshalInterThreadInterfaceInStream 来封送接口。 此函数创建的流是线程安全的,并且必须存储在单元 2 可访问的变量中。 单元 2 必须将此流传递给 CoGetInterfaceAndReleaseStream 以拆收接口,并返回指向代理的指针,通过该代理可以访问该接口。 主单元必须保持活动状态,直到客户端完成所有 COM 工作(因为某些进程内对象加载在主单元中,如进程内服务器线程处理问题中所述)。 以这种方式在线程之间传递一个对象后,很容易将接口指针作为参数传递。 这样,分布式 COM 将为应用程序执行封送和线程切换。
每个单线程单元必须有一个消息循环来处理同一进程中其他进程和单元的调用。 这意味着线程的工作函数必须具有 GetMessage/DispatchMessage 循环。 如果使用其他同步基元在线程之间通信,则 MsgWaitForMultipleObjects 函数可用于等待消息和线程同步事件。 此函数的文档提供了这种组合循环的示例。
COM 在每个单线程单元中使用 Windows 类“OleMainThreadWndClass”创建隐藏窗口。 作为此隐藏窗口的窗口消息接收对对象的调用。 当对象的单元检索并调度消息时,隐藏窗口将收到消息。 然后,窗口过程将调用对象的相应接口方法。
当多个客户端调用对象时,调用将在消息队列中排队,该对象将在每次其单元检索和调度消息时接收调用。 由于调用由 COM 同步,并且调用始终由属于对象的单元的线程传递,因此对象的接口实现不需要提供同步。 单线程单元可以实现 IMessageFilter,以允许他们在必要时取消调用或接收窗口消息。
如果某个接口方法实现检索和调度消息或向另一个线程发出 ORPC 调用,从而导致另一个调用传递到对象(由同一单元),则可以重新输入该对象。 OLE 不会阻止在同一线程上重新进入,但可以帮助提供线程安全。 这与在处理消息的同时检索和调度消息时可以重新输入窗口过程的方式相同。 但是,调用调用另一个单线程单元服务器的进程外单线程单元服务器将允许重新进入第一台服务器。
相关主题