Search code examples
c++comatldcomdangling-pointer

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


When dealing with interprocess COM objects, is it safe to cast a IDispatch* into an IUnknown*, without using QueryInterface ?

Here our IDispatch object comes from an other process OtherProcess.exe. And a colleague of mine says that I should call QueryInterface on the IDispatch so as to get an IUnknown.

Currently I'm doing:

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;
    }
}

My problem with his suggestion: I am dealing with cases (access violations) when the OtherProcess.exe has crashed or has been killed. It seems calling any functions like Invoke on the IDispatch that encapsulates any objects from this no longer exisiting OtherProcess.exe provokes these access violations (EDIT: comments and answers reveals that this latest assumption was completely false!).

That's why I'm trying to protect the application testing ::CoIsHandlerConnected(pIUnknown); which takes an IUnknown as parameter.

But by calling QueryInterface on the IDispatch, like my colleague advises me to do, I am afraid to fall back in the same problem I am trying to solve: This IDispatch handles an object that no longer exists, and QueryInterface to an IUnknown would just be Undefined Behaviour all the same (EDIT again, this assumption is also false).

Am I really wrong when I just do the cast ? What is the common way to deal with dead interprocess COM objects ?

This is the begining of the definition of IDispatch in OAIdl.h, which is declared as deriving from IUnknown.

MIDL_INTERFACE("00020400-0000-0000-C000-000000000046")
IDispatch : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( 
        /* [out] */ __RPC__out UINT *pctinfo) = 0;

Solution

  • Casting IDispatch to IUnknown in C++ (like static_cast<IUnknown*>(pDispatch)) yields exactly the same pointer value, because IDispatch derives from IUnknown. OTOH, doing QueryInterface for IID_IUnknown on pDispatch may return a different pointer, but it's still a legit operation. In fact, this is how to get the identity of a COM object, say, to check if two interfaces are implemented by the same COM object (a hard COM rule which always work inside the same COM apartment).

    That said, the proxy COM object implemented by the COM marshaller may be caching interfaces, so the call to IDispatch::QueryInterface may return S_OK and a valid IUnknown identity of the proxy, despite the remote server already went down. That is, such operation might not be causing an instant IPC call.

    In your case, to test if the COM server is still alive and well, I'd simply call IDispatch::GetTypeInfoCount on the proxy object you already have. That would actually cause an IPC call (or a round-trip over the wire, if the server runs on a different host).

    In case the remote server has crashed or is unavailable, you'd likely receive a CO_E_OBJNOTCONNECTED error (could perhaps be a different error code, but certainly not S_OK).

    Note though, doing an extra IPC call just to check if the server is available might be a costly operation, depending on your scenario.