对于进程间 COM 对象,在不使用 QueryInterface 的情况下将 IDispatch* 转换为 IUnknown* 是不是安全?

Posted

技术标签:

【中文标题】对于进程间 COM 对象,在不使用 QueryInterface 的情况下将 IDispatch* 转换为 IUnknown* 是不是安全?【英文标题】:Is it safe to cast a IDispatch* into an IUnknown*, without using QueryInterface, for interprocess COM objects?对于进程间 COM 对象,在不使用 QueryInterface 的情况下将 IDispatch* 转换为 IUnknown* 是否安全? 【发布时间】:2015-06-10 13:46:42 【问题描述】:

在处理进程间COM 对象时,不使用QueryInterfaceIDispatch* 转换为IUnknown* 是否安全?

这里我们的IDispatch 对象来自另一个进程OtherProcess.exe。 我的一位同事说我应该在IDispatch 上拨打QueryInterface 以获得IUnknown

目前我在做:

void CComThrowDispatch::CheckCOMAvailabilty() const

    IUnknown * pIUnknown = m_spDispatchDriver.p;   
    // is this line above a problem ? 
    // m_spDispatchDriver is an ATL CComDispatchDriver 
    // it handles an object instanciated in another process.
    // m_spDispatchDriver.p is of type IDispatch*

    if (pIUnknown == nullptr) return;
    bool bComObjectReachable = ::CoIsHandlerConnected(pIUnknown) == TRUE;
    if (bComObjectReachable == false)
    
        throw MyException;
    

我对他的建议的问题:我正在处理 OtherProcess.exe 崩溃或被杀死的情况(访问冲突)。似乎在 IDispatch 上调用像 Invoke 这样的任何函数,这些函数封装了不再存在的 OtherProcess.exe 中的任何对象,这会引发这些访问冲突(EDIT: cmets 和答案表明这个最新的假设完全错误!)。

这就是为什么我试图保护应用程序测试::CoIsHandlerConnected(pIUnknown);,它以IUnknown 作为参数。

但是通过在IDispatch 上调用QueryInterface,就像我的同事建议我做的那样,我害怕回到我试图解决的同一个问题:这个IDispatch 处理一个不再存在的对象, 和 QueryInterfaceIUnknown 只是未定义的行为都是一样的(再次EDIT,这个假设也是错误的)。

当我只是做演员时,我真的错了吗? 处理死进程间COM对象的常用方法是什么?

这是 OAIdl.h 中IDispatch 定义的开始,它被声明为派生自IUnknown

MIDL_INTERFACE("00020400-0000-0000-C000-000000000046")
IDispatch : public IUnknown

public:
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( 
        /* [out] */ __RPC__out UINT *pctinfo) = 0;

【问题讨论】:

投射是真的错误。从死服务器调用接口上的方法会导致 RPC 错误,而不是 AVE。您需要获取 RPC_E_SERVERDIED。你需要关注真正的问题,现在你只会让事情变得更糟。 @HansPassant 这很有趣,谢谢。但是,我很难理解为什么将派生自 IUnknown 的 IDispatch 向上转换 到 IUnknown 是真的错误。你能详细说明一下吗?我最初犯的错误是什么? @StephaneRolland:在某些情况下,您希望/需要查询 IUnknown,即使任何接口派生自它并且可以使用转换(请参阅 Objects Must Have Identity section),但您的情况并非如此问题。 @RomanR.yes,您的回答和 Hans 的回答让我看到访问冲突必须来自其他地方。但这是否意味着 COM 不完全兼容 C++? 因为不能保证将 IDispatch 强制转换到它的基类中,所以它被破坏了,因为多态性是不能保证的。我对问题的表述是否正确? @StephaneRolland:不,我不认为这个措辞是不正确的。不确定性来自这样一个事实,即 COM 对象可能一次实现几个 IUnkown、几个 IDispatch,然后QueryInterface 不是强制转换 - 它是一个返回指针的方法,这个指针可能是也可能不是由相同的 C++(或非 C++)类实现。问题中的 API 说“给我 IUnknown”,这就提出了一个问题,即您应该提供哪个 IUnknown,以及作为 IDispatch 的一部分实现的 API 是否足够好。所有提到的都是合法的,没有被破坏。 【参考方案1】:

在 C++ 中将IDispatch 转换为IUnknown(如static_cast<IUnknown*>(pDispatch))会产生完全相同的指针值,因为IDispatch 派生自IUnknown。 OTOH,在pDispatch 上为IID_IUnknown 执行QueryInterface 可能返回不同的指针,但它仍然是合法的操作。事实上,这就是如何获取 COM 对象的身份,例如,检查两个接口是否由同一个 COM 对象实现(一个始终在同一个 COM 单元内工作的硬 COM 规则)。

也就是说,COM 编组器实现的代理 COM 对象可能是缓存接口,因此对 IDispatch::QueryInterface 的调用可能会返回 S_OK 和代理的有效 IUnknown 身份,尽管远程服务器已经关闭。也就是说,这样的操作可能不会导致即时 IPC 调用。

在您的情况下,为了测试 COM 服务器是否仍然正常运行,我只需在您已有的代理对象上调用 IDispatch::GetTypeInfoCount。这实际上会导致 IPC 调用(或者如果服务器在不同的主机上运行,​​则通过网络进行往返)。

如果远程服务器崩溃或不可用,您可能会收到CO_E_OBJNOTCONNECTED 错误(可能是不同的错误代码,但肯定不是S_OK)。

但请注意,根据您的情况,执行额外的 IPC 调用只是为了检查服务器是否可用可能是一项昂贵的操作。

【讨论】:

【参考方案2】:

为了检测对象是否是远程CoIsHandlerConnected 无论如何都会使用QueryInterface 参数(对于IProxyManager 等),因此无论您提供已有的指针,还是额外查询@ 987654324@。您的QueryInterface 调用不会影响远程对象的状态:无论对象是否为远程,远程对象是否已死 - 无论您是否添加了QueryInterfaceCoIsHandlerConnected 对您的结果都是一样的。因此,没有必要这样做。

另一个注意事项是,如果远程对象已死(进程外服务器崩溃等),调用IDispatch::Invoke 仍然是安全的。代理只返回没有未定义行为的错误代码。也就是说,看起来您根本不需要CoIsHandlerConnected,如果您在客户端进程的上下文中遇到访问冲突,那么您可能还有其他问题需要先解决。

【讨论】:

【参考方案3】:

不,你应该永远是你QueryInterface

仅仅因为您提供了IUnknown 接口,并不意味着您可以直接将其转换为IDispatch。 COM 可能给你一个底层对象的代理,这意味着指针与IDispatch 无关。

同样,一个实现可以包装一个实现IDispatch 的对象,当您调用QueryInterface 时,它会委托给这个对象。或者,您可能有一个指向委托给外部IUnknown 的 COM 对象的指针。

所以,基本上,永远不要直接施放,即使你认为它会起作用,因为事情可能会随着时间而改变。调用QueryInterface 很少会成为性能瓶颈,因此不值得避免。

【讨论】:

我有一个 IDispatch* 并将其转换为 IUnkown*。 IDispatch 派生自 IUnkown。你的回答是相反的,我完全同意这是错误的。**向下转型是危险的**,但据我所知,它并不乐观。不是吗? @StephaneRolland - 它仍然不安全。您的IDispatch 可能是整个对象的成员。通过向下转换,您将看到成员的 IUnknown,而不是实际代表 COM 对象的控制 IUnknown

以上是关于对于进程间 COM 对象,在不使用 QueryInterface 的情况下将 IDispatch* 转换为 IUnknown* 是不是安全?的主要内容,如果未能解决你的问题,请参考以下文章

Linux进程间通信 -- 使用命名管道

进程间通信:命名管道

.Net 进程间通信

命名管道

在进程间使用事件对象

2个应用程序之间的进程间通信