检查COM接口是否仍然存活?

10

在COM中,如何验证指向COM对象的指针在另一端仍然有效?

我遇到了一个问题,即以下代码尝试检查m_pServer指针是否仍然存在,但当公开该接口的应用程序被关闭时,这段代码会导致应用程序崩溃。 有人能否建议如何在使用指针之前检查它?

if (FAILED(m_pServer->StillAlive())) { // do something }

如果m_pServer不再位于内存中,该代码将失败。

编辑:

异常:在Client40.exe中的0x7728fbae(kernel32.dll)处第一次机会异常:0x800706BA:RPC服务器不可用。

调用堆栈:

    kernel32.dll!RaiseException()  + 0x58   
    rpcrt4.dll!RpcRaiseException()  + 0x3e  
    rpcrt4.dll!NdrProxyErrorHandler()  + 0x28   
    rpcrt4.dll!NdrProxySendReceive()  + 0xa4    
    rpcrt4.dll!NdrProxySendReceive()  + 0x119   
    rpcrt4.dll!NdrComplexArrayMarshall()  + 0x26d   
--> Client40.exe!SlaveDriver::run()  Line 97 + 0x14 C++  //Runs while loop, to handle requests
    Client40.exe!DThread::tfunc(void * thisptr=0x0047e694)  Line 56 + 0xd   C++
    Client40.exe!_threadstartex(void * ptd=0x01b20e00)  Line 241 + 0xd  C
    kernel32.dll!BaseThreadInitThunk()  + 0x12  
    ntdll.dll!RtlInitializeExceptionChain()  + 0x63 
    ntdll.dll!RtlInitializeExceptionChain()  + 0x36 

不清楚为什么会抛出异常。进入代码(F11)-也许有一些包装代码会抛出异常。你是否#import了一个.tlb或.dll文件? - sharptooth
1
当我关闭暴露此m_pServer接口的应用程序并且现在该应用程序尝试调用不再可用的方法时,会抛出异常。我正在尝试防止抛出此异常。 - Tony The Lion
你应该在那一行设置一个断点,然后使用F11逐步执行,直到抛出C++异常。 - sharptooth
cmd Ping.exe m_pServer 就可以了。 - Jonathan
FYI,该问题最近在 Raymond Chen 的博客上进行了讨论:http://blogs.msdn.com/b/oldnewthing/archive/2011/11/16/10237502.aspx “如何判断远程对象的 COM 指针是否仍然有效?” - Roman R.
显示剩余12条评论
7个回答

6
你在这里尝试做的事情根本不可能。因为 m_pServer 存在于另一个进程中,你实际上是在问以下问题:

进程 XXX 是否仍在运行?

在 Windows(或 Linux / Unix)世界中,这个问题根本无法回答。你永远无法可靠地回答这个问题,因为一旦问题得到答案,它的结果就可以被无效化。进程可以在任何时间点终止,包括在你检查和访问 COM 对象之间。
然而,一个略微不同版本的这个问题是可以回答的:

进程 XXX 是否曾经运行过?

唯一的方法是执行相关操作并期望它失败。如果成功了,那太好了,你已经回答了修改后的问题。如果失败了,那么你需要解释失败以确定操作是否失败还是进程已经消失。
这是正确处理这种情况的唯一方法。

1

COM对象的生命周期由您来管理。只要您拥有接口的活动指针,您就必须在接口上至少调用一次AddRef()。最后的Release()调用将删除对象并使指针失效。之后继续使用它会随机崩溃程序,通常是AV错误。没有办法检测它是否失效。

当您进行最后的Release()调用时,可以将m_pServer设置为NULL。


那么,如果暴露此接口的应用程序崩溃了,那么使用它的应用程序在尝试使用它之前永远不会知道吗???还是有一种方法可以防止这种情况发生??? - Tony The Lion
2
我猜他的问题是外部进程服务器被强制终止了。 - sharptooth
@Tony,你会从RPC得到一个HRESULT错误代码,通常是RPC_E_SERVERDIED。杀死服务器并不会杀死它的代理。你可以通过不杀死服务器来防止这种情况发生。 - Hans Passant

1

COM 中的第一次异常被代理捕获并报告为错误代码。如果您继续执行第一次异常,您应该看到代理返回 RPC_SERVER_UNAVAILABLE,您应该根据需要进行处理。


0

1 - 在您的代码中检查HRESULT - 不仅仅是一般的FAILED - 这将为您提供可以向用户报告更多信息或启动替代操作(如优雅地关闭应用程序)的位置。实际的HRESULT可能与RPC_SERVER_UNAVAILABLE完全相同,也可能不同 - 因此首先将其分配给一个变量,以便您可以在调试器中看到它(不要在第一次机会异常时跳转 - 它们用于调试较低层的代码)。

2 - 如果您的C++代码实际上抛出异常,则首先在其周围放置一个通用catch,然后在调试器中查看它。这种情况可能来自某种智能指针试图过于聪明:-)

无论如何,留下一个标志以了解发生了什么,并优雅地关闭COM引用 - 只是因为服务器死了并不意味着您必须泄漏:-)


0
基本上,你永远不会检查这个。它只能给出无用的答案。考虑以下假设性例子:
if (CoCheckAlive(ptr)) {
  ptr->Foo();
}

即使CoCheckAlive返回了true,这个结果也不能保证远程服务器能够保持足够长的时间来进行下一次调用。这就是为什么你只需要进行调用,并在之后处理失败情况(包括RPC_E_SERVERDIED)。

所以 FAILED(m_pServer->StillAlive()) 基本上是无用的吗? - Tony The Lion
如果客户端(out of proc com server)的服务器不再可用,如何正确终止它? - Tony The Lion
是的,“->StillAlive”检查方式同样无用。它只告诉你服务器还活着,而不是在你进行真正调用时它是否仍然存活。我不理解你的第二个问题,但它似乎是一个好问题。清晰地表达出来,然后搜索StackOverflow。很有可能已经有答案了,否则你就有一个好问题。 - MSalters
2
这种行为有其有效的使用情况,目的是提供明确的反馈而不是保证后续调用。考虑一个空闲客户端,它带有一个定时器检查,当服务器连接丢失时会写入错误日志或弹出对话框提示。 - morechilli
它并不是无用的,而是一个谨慎的暗示。一个合适的名称可能是 IsServerDead(ptr)true 意味着服务器已经死了。IsServerDead(ptr) == false 并不意味着服务器是否存活。 - Mitch

0

0
只需编写一个包装方法,检查失败并捕获异常,并将其报告为失败。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接