对于进程间 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* 是否安全?

这里我们的 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: 评论答案表明这个最新的假设是完全错误的!)。

这就是我试图保护应用程序测试 ::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;

不,你应该总是 QueryInterface

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

无关

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

的 COM 对象的指针

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

为了检测对象是否远程 CoIsHandlerConnected 无论如何都会 QueryInterface 参数(对于 IProxyManager 等),所以你是否提供指针并不重要有,或者您另外查询 IUnknown。您的 QueryInterface 调用对远程对象的状态没有影响:对象是否远程,远程对象是否已死 - CoIsHandlerConnected 对您的结果相同,无论您的附加 QueryInterface。因此,没有必要这样做。

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

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

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

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

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

不过请注意,执行额外的 IPC 调用只是为了检查服务器是否可用可能是一项代价高昂的操作,具体取决于您的情况。