进程外服务器实现帮助程序

可以使用进程外服务器可调用的四个帮助程序函数来简化编写服务器代码的工作。 COM 客户端和 COM 进程内服务器通常不会调用它们。 这些函数旨在帮助防止在服务器具有多个单元或多个类对象时服务器激活中出现争用情况。 但是,它们也可以方便地用于单线程对象服务器和单类对象服务器。 函数如下所示:

若要正确关闭,COM 服务器必须跟踪已实例化的对象实例数以及 IClassFactory::LockServer 方法的调用次数。 仅当这两个计数达到零时,服务器才能关闭。 在单线程 COM 服务器中,根据消息队列序列化的传入激活请求协调关闭决定。 服务器在收到其最终对象实例的发布并决定关闭时,会在调度更多激活请求之前撤销其类对象。 如果激活请求在此后出现,COM 将识别出类对象被撤销,并将向服务控制管理器 (SCM) 返回一个错误,这将导致本地服务器进程运行一个新实例。

但是,在单元模型服务器中,不同类对象在不同单元中注册,并且在所有自由线程服务器中,必须根据激活请求跨多个线程协调此关闭决定,从而服务器的一个线程不决定关闭,而服务器的另一个线程正忙于分发类对象或对象实例。 解决此问题的一种经典但繁琐的方法是让服务器在撤销其类对象后重新检查其实例计数并保持活动状态,直到释放所有实例。

为了方便服务器编写器处理这类争用情况,COM 提供两个引用计数函数:

当每个进程的全局引用计数达到零时,COM 会自动调用 CoSuspendClassObjects,从而阻止任何新的激活请求传入。 然后,服务器可以在空闲时从其各种线程中取消注册其各种类对象,而无需担心另一个激活请求可能会传入。 此后,SCM 会处理所有新的激活请求,从而启动本地服务器进程的新实例。

fLock 参数为 TRUE 时,本地服务器应用程序使用这些函数最简单的方法是在每个实例对象的构造函数中和各种 IClassFactory::LockServer 方法中调用 CoAddRefServerProcess。 当 fLock 参数为 FALSE 时,服务器应用程序还应在其每个实例对象的析构函数中以及每个 IClassFactory::LockServer 方法中调用 CoReleaseServerProcess

最后,服务器应用程序应注意 CoReleaseServerProcess 的返回代码,如果返回 0,服务器应用程序应启动清理程序,对于具有多个线程的服务器,这通常意味着应向各种线程发出信号以退出其消息循环并调用 CoAddRefServerProcessCoReleaseServerProcess。 如果使用服务器进程生存期管理功能,则必须在对象实例和 LockServer 方法中使用它们;否则,服务器应用程序可能会过早关闭。

发出 CoGetClassObject 请求时,COM 会联系服务器,封送类对象的 IClassFactory 接口,返回客户端进程,拆收 IClassFactory 接口,并将其返回客户端。 此时,客户端通常使用 TRUE 调用 LockServer,以防止服务器进程关闭。 但是,类对象封送与客户端调用 LockServer 会间隔一段时间,这时另一个客户端可以连接到同一服务器,获取一个实例并释放该实例,从而导致服务器关闭并让第一个客户端因断开连接的 IClassFactory 指针而陷入困境。 为了防止这种争用情况,COM 在封送 IClassFactory 接口时,为类对象增加了值为 TRUELockServer 隐式调用,而在客户端释放IClassFactory 接口时,为类对象增加了值为 FALSELockServer 隐式调用。 因此,不需要远程 LockServer 调用回服务器,而 LockServer 的代理只需返回 S_OK,而无需实际远程处理调用。

初始化进程外服务器进程期间,还会出现另一种与激活相关的争用情况。 注册多个类的 COM 服务器通常使用 REGCLS_LOCAL_SERVER 为其支持的每个 CLSID 调用 CoRegisterClassObject。 针对所有类执行此操作后,服务器将进入其消息循环。 对于单线程 COM 服务器,所有激活请求都会被阻止,直到服务器进入消息循环。 但是,对于在不同单元中注册不同类对象的单元模型服务器和所有自由线程服务器,可以提前发出激活请求。 对于单元模型服务器,可以在任何一个线程进入其消息循环后立即发出激活请求。 对于自由线程服务器,可以在注册第一个类对象后立即发出激活请求。 激活可能提前发生,因此最终版本也有可能会在服务器的其余部分完成初始化之前激活(因此导致服务器开始关闭)。

若要消除这些争用情况并简化服务器编写器的工作,任何想要向 COM 注册多个类对象的服务器都应使用 REGCLS_LOCAL_SERVER | REGCLS_SUSPENDED 为服务器支持的每个不同 CLSID 调用 CoRegisterClassObject。 注册所有类并且服务器进程可随时接受传入的激活请求后,服务器应对 CoResumeClassObjects 进行一次调用。 此函数告知 COM 通知 SCM 所有已注册的类,并开始允许激活请求进入服务器进程。 使用这些函数具有以下优势:

  • 无论注册多少个 CLSID,仅对 SCM 进行一次调用,从而减少总注册时间(因此缩短服务器应用程序启动时间)。
  • 如果服务器具有多个单元,并且在不同的单元中注册不同的 CLSID,或者服务器是自由线程服务器,则在服务器调用 CoResumeClassObjects 之前,不会传入任何激活请求,这让服务器有机会注册其所有 CLSID 并正确设置,然后才能处理激活请求和可能的关闭请求。

COM 服务器职责