1

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;
Stephane Rolland
  • 38,876
  • 35
  • 121
  • 169
  • 2
    It is *really* wrong to cast. Calling a method on an interface from a dead server causes an RPC error, not an AVE. You need to get RPC_E_SERVERDIED. You need to focus on the real problem here, right now you are just making it worse. – Hans Passant Jun 10 '15 at 14:06
  • @HansPassant That's interesting, thanks. However I have hard time understanding why it's *really wrong* to **upcast** an IDispatch, which derives from IUnknown, into a IUnknown. Could you elaborate about that ? What is the initial mistake I am doing ? – Stephane Rolland Jun 10 '15 at 14:58
  • 2
    @StephaneRolland: In some cases you want/need to query `IUnknown` even though any interface derive from it and cast is available (see [Objects Must Have Identity section](https://msdn.microsoft.com/en-us/library/windows/desktop/ms686590%28v=vs.85%29.aspx)), however it is not the case here in your question. – Roman R. Jun 10 '15 at 15:07
  • @RomanR.yes, your answer and Hans's one made me see that the access violation must come from somewhere else. But does it mean that COM is not fully C++ compatible ? **That it is broken because polymorphism is kind of not guaranteed because a cast of IDispatch into its base class is not guaranteed**. Is my wording of the problem right ? – Stephane Rolland Jun 10 '15 at 15:13
  • 1
    @StephaneRolland: No, I don't think this wording is incorrect. The uncertainty comes from the fact that COM object might implement several IUnkown's, several IDispatch'es at a time, and then `QueryInterface` is not a cast - it is a method that returns a pointer, and this pointer might be or might not be implemented by the same C++ (or not C++) class. The API in questions says "give me IUnknown" and this raises the question which exactly IUnknown you should provide, and whether the one implemented as a part of IDispatch is good enough. And all mentioned is legal, not broken. – Roman R. Jun 10 '15 at 15:50

3 Answers3

2

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.

Sean
  • 60,939
  • 11
  • 97
  • 136
  • I have an IDispatch* and I cast it into IUnkown*. **IDispatch derives from IUnkown**. You're answer talks about the other way around, which I completely agree is wrong.**Downcast is dangerous**, but not upcast as far as I know. Isn't it ? – Stephane Rolland Jun 10 '15 at 14:56
  • @StephaneRolland - it's still not safe. Your `IDispatch` may be a member of the overall object. By downcasting you'll see the `IUnknown` of the member, not the controlling `IUnknown` that actually represents the COM object. – Sean Jun 10 '15 at 15:35
2

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.

Roman R.
  • 68,205
  • 6
  • 94
  • 158
2

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.

noseratio
  • 59,932
  • 34
  • 208
  • 486