在加载进程内服务器对象实例时防止进程外服务器关闭

Posted

技术标签:

【中文标题】在加载进程内服务器对象实例时防止进程外服务器关闭【英文标题】:Prevent out-of-proc server shutdown while it has loaded in-proc server object instances 【发布时间】:2014-08-08 23:59:17 【问题描述】:

我有一个应用程序,它使用进程外 COM 服务器来访问由进程内 COM 服务器创建的 COM 对象。这意味着进程外 COM 服务器必须加载进程内 COM DLL 以创建最终对象,然后它将返回。

例如:

// Create an object which resides in the out of process COM server
container.CoCreateInstance("HelperServerProcess");
// Grab a reference to an object which resides in an in process COM server DLL, 
// hosted by the out of process COM server
object = container.GenerateResults();
// Release the object instantiated by the out of process server
container = NULL; // or return, or go out of scope, etc
// This call will fail, because the out of process server has shutdown unloading
// the inproc DLL hosting <object>
object.DoStuff();

然而,一旦容器对象被释放,最终的服务器进程引用(在 CoReleaseServerProcess 中)被释放,并且服务器关闭。这会在尝试使用结果对象时导致 E_RPC_SERVER_UNAVAILABLE 错误。同时,此 EXE 服务器中托管的进程内 DLL 仍有未完成的对象,因此从 CanUnloadNow 返回 S_FALSE。

我认为将 IExternalConnection 添加到 EXE 服务器的类工厂以手动对远程引用进行引用计数将无济于事,因为 DLL in-proc 服务器注册的对象将使用 DLLs 类工厂并尝试使用 IExternalConnection on这家工厂。此外,如果服务器在其进程空间中生成交互式子对象,则无论如何都不会触发 IExternalConnection。

也无法修改 DLL 的引用计数以使用 CoAddRefServerProcess / CoReleaseServerProcess,因为 DLL 无法访问容器的关闭代码以防触发最后一个版本,并且无法更改第三方 DLL无论如何。

我能想到的唯一可行的方法是在服务器引用计数达到零后添加一个循环,该循环调用 CoFreeUnusedLibraries,然后以某种方式枚举所有加载的 COM DLL 并等待直到没有加载并确保服务器引用计数仍然零。如果加载的 DLL 没有正确实现 CanUnloadNow,这将泄漏进程,并且涉及到我想避免的低级 COM 实现细节。

有没有更简单的方法来确保进程内服务器的类工厂实例化的 COM 对象保持进程处于活动状态,或者枚举加载到当前进程中的 DLL 的类工厂并查询它们的引用数量?

更新:另一种可能有效的方法,但听起来很像你不应该做的事情:拦截进程中的每个生成的线程,通过 CoRegisterInitializeSpy 注册一个 CoInitialize 钩子,并为每个进程添加服务器进程引用当前已初始化 COM 的线程。

【问题讨论】:

对此的恰当称呼是“错误”。如果您无法修复服务器,那么您最好坚持使用接口参考。请联系服务器的作者或供应商寻求建议。 @HansPassant:我可以修复服务器,它目前正在使用 CAtlExeModuleT 来管理它的生命周期。然而,只要它通过自己的类工厂创建的 COM 对象的实例存在,这个类只会让服务器保持活动状态。我只是不知道如何扩展 Lock / UnLock 行为以尊重通过 CoGetClassObject / CoCreateInstance 动态加载的 DLL 的私有引用计数,DLL 在 DLLCanUnloadNow 中使用这些引用。 【参考方案1】:

进程外 EXE 可以委托 DLL 对象而不是直接返回它。让GetResults() 返回一个 EXE 实现的对象,并让该实现根据需要在内部使用 DLL。这样,在调用者释放 EXE 的对象之前,EXE 不会被释放,从而保持 EXE 自己的引用计数处于活动状态。 EXE 的对象可以实现与 DLL 对象实现的接口相同的接口。这样,调用者就不会知道(或关心)正在使用委托。

【讨论】:

只有在有一种通用的方法来拦截对子对象的所有可能的调用(这些调用可以创建/返回更多子对象)时才有效,否则必须包装的对象树将无限增长,并且运行时定义IDispatch 方法也可以逃脱。这还需要将所有包装器映射到真实对象以处理反向引用。 如果有问题的对象公开了对子对象的访问权限,是的。必须包装整个树,以便维护 EXE 自己的引用计数。 +1 这样的包装可以通过在根对象上实现IMarshal 来自动化(尽管那将是一项乏味的任务)。

以上是关于在加载进程内服务器对象实例时防止进程外服务器关闭的主要内容,如果未能解决你的问题,请参考以下文章

什么决定了进程外COM服务器注意到客户端已经死亡的时间?

如何加载 COM dll 模块并将其接口公开为进程外服务器

由于启动用户实例的进程时出错,导致无法生成 SQL Server 的用户实例。该连接将关闭。

如何从 Excel VBA 释放进程内 COM 服务器对象

在一个ORACLEA实例中最多可以启动多少个DBWR后台进程?

64 位托管进程:进程外 32 位 COM 服务器非默认接口不可用