对于进程间COM对象,不使用QueryInterface,将IDispatch*转换为IUnknown*是否安全?

Is it safe to cast a IDispatch* into an IUnknown*, without using QueryInterface, for interprocess COM objects?

本文关键字:IDispatch QueryInterface 转换 安全 是否 IUnknown 进程 于进程 对象 COM      更新时间:2023-10-16

在处理进程间的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只是未定义行为(编辑再次,这个假设也是假的)。

我真的错了,当我只是做演员吗?处理死进程间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;

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

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

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

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

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

不,你应该总是使用QueryInterface

仅仅因为你已经创建了一个IUnknown接口,并不意味着你可以直接将它强制转换为IDispatch。COM可能给了你一个底层对象的代理,这意味着指针与IDispatch无关。

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

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

为了检测对象是否远程CoIsHandlerConnectedQueryInterface的参数(对于IProxyManager等),所以它并不重要你是否提供你已经拥有的指针,或者你额外查询IUnknown。你的QueryInterface调用对远程对象的状态没有影响:无论对象是否远程,无论远程对象是否死亡- CoIsHandlerConnected对你有相同的结果,不管你额外的QueryInterface。因此,没有必要做这件事。

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