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

1.7k views Asked by At

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

There are 3 answers

0
noseratio On BEST ANSWER

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.

0
Roman Ryltsov On

In order to detect whether the object is remote CoIsHandlerConnected would QueryInterface the argument anyway (for IProxyManager etc), so it does not matter whether you provide the pointer you already have, or you additionally query for IUnknown. Your QueryInterface call has not effect on the status of the remote object: whether the object is remote or not, whether remote object is dead or not - CoIsHandlerConnected has the same result for you regardless of your additional QueryInterface. Hence, there is no need to do it.

Then another note is that it is still safe to call IDispatch::Invoke if remote object is dead (out-of-process server crashed etc). The proxy simply returns error code without undefined behavior. That is, it looks like you don't need CoIsHandlerConnected at all, and if you experience access violations in context of client process, then you probably have other issues to resolve first.

2
Sean On

No, you should always you QueryInterface.

Just because you've com an IUnknown interface it doesn't mean you can directly cast it to IDispatch. COM may have given you a proxy to the underlying object, which means that the pointer has nothing to do with IDispatch.

Likewise, an implementation may wrap an object that implements IDispatch and when you call QueryInterface it delegates to this object. Or you may have a pointer to a COM objects that delegates to an outer IUnknown.

So, basically, never directly cast, even if you think it'll work, because things may change over time. Calling QueryInterface is rarely a performance bottleneck and therefore not worth avoiding.