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

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

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

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

为了便于服务器编写器处理这些类型的争用条件,COM 提供了两个引用计数函数:

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

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

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

发出 CoGetClassObject 请求时,COM 会联系服务器,封送类对象的 IClassFactory 接口,返回到客户端进程,取消对 IClassFactory 接口进行解封,并将此消息返回到客户端。 此时,客户端通常会使用 TRUE 调用 LockServer,以防止服务器进程关闭。 但是,在封送类对象和客户端调用 LockServer 时,有一段时间,其中另一个客户端可以连接到同一服务器、获取实例并释放该实例,从而导致服务器关闭并让第一个客户端保持高,并且使用断开连接的 IClassFactory 指针保持干涸状态。 为了防止此争用条件,COM 在封送 IClassFactory 接口时向类对象添加了对 LockServer的隐式调用,并在客户端释放 IClassFactory 接口时使用 FALSELockServer 进行隐式调用。 因此,不需要远程 LockServer 调用回服务器, 而 LockServer 的代理只需返回S_OK,而无需实际远程处理调用。

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

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

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

COM 服务器责任