views:

78

answers:

4

Assume I have a class implementing two or more COM interfaces (exactly as here):

class CMyClass : public IInterface1, public IInterface2 { 
};

QueryInterface() must return the same pointer for each request of the same interface (it needs an explicit upcast for proper pointer adjustment):

if( iid == __uuidof( IUnknown ) ) { 
    *ppv = static_cast<IInterface1*>( this );
    //call Addref(), return S_OK 
} else if( iid == __uuidof( IInterface1 ) ) {
    *ppv = static_cast<IInterface1*>( this );
    //call Addref(), return S_OK 
} else if( iid == __uuidof( IInterface2 ) ) {
    *ppv = static_cast<IInterface2*>( this );
    //call Addref(), return S_OK 
} else {
    *ppv = 0;
    return E_NOINTERFACE;
}

now there're two IUnknowns in the object - one is the base of IInterface1 and the other is the base of IInterface2. And they are in different subobjects.

Let's pretend I called QueryInterface() for IInterface2 - the pointer returned will be different from the pointer returned when I call QueryInterface() for IUnknown. So far so good. Then I can pass the retrieved IInterface2* into any function accepting IUnknown* and thanks to C++ implicit conversion the pointer will be accepted, but it will be not the same pointer that QueryInterface() for IUnknown* would retrieve. In fact if that function calls QueryInterface() for IUnknown immediately upon being called it will retrieve a different pointer.

Is this legal in terms of COM? How do I handle situations when I have a pointer to a multiply-inherited object and I allow an implicit upcast?

+1  A: 

It seems there are a small misunderstanding. Interfaces IInterface1 and IInterface2 are pur abstract. There are no separate QueryInterface() for IInterface1 and IInterface2. There are only a declaration, that class CMyClass will implement all methods from IInterface1 and IInterface2 (the set of methods of CMyClass is the set of methods IInterface1 and IInterface1 together).

So you implement in the class CMyClass one QueryInterface(), one AddRef() and one Release() method. In the QueryInterface() you cast the pointer to instance of class CMyClass to static_cast<IUnknown*>.

UPDATED: Hi! I had to drive away immediately after writing my answer. Only now I could read all other answers and can add something to my answer.

OK. You say that if you cast IInterface1 to IUnknown and if you cast IInterface2 to IUnknown you receive two different pointers. You are right! But nevertheless it does not matter. If you compare the contains of both pointers (which addresses has QueryInterface(), AddRef() and Release() in both cases) you will see that both pointer has the same contain. So I am also right!

There are a lot of good examples how to implement COM in pure C. In this case you need define a static structs with pointers to the virtual functions like QueryInterface(), AddRef() and Release() and give the pointer of such struct back as a result of QueryInterface(). It shows one more time, that only the contain which you gives back is important for COM and not a pointer (it is not important which vtable you give back).

One more small remark. In some comments you write about the possibility to have many implementations of methods QueryInterface(), AddRef() and Release(). I disagree here. The reason is that interfaces are pure abstract classes and if you define a class which implement some interfaces you have no typical class inheritance. You have only one class with one implementation of all functions from all interfaces. If you do this in C++, then compiler create and fill static vtables with the corresponding pointers to the only implementation of the functions QueryInterface(), AddRef(), Release() and so on, but all vtables points to the same functions.

To reduce of the number of vtables Microsoft introduced __declspec(novtable) or ATL_NO_VTABLE, but it is not the part of your questions.

Oleg
Yes, I know that. But there's a formal problem. When I implicitly upcast from `IInterface2` to `IUnknown` I get a pointer different from the one I get from `QI()`. The whole questionn is about one thing: is that `IUnknown` "legal" in terms of COM?
sharptooth
There are many implementations of these methods, even if all but one forward the call to the single version that implements it.
Ben Voigt
+1  A: 

As Hans points out, your implementation of QueryInterface is correct.

It is the responsibility of the user of a COM object to use QueryInterface at all times. The reason is exactly to prevent the scenario you have pointed out: that casting an IInterface1* or IInterface2* pointer to the IUnknown* pointer will yield different physical values.

In C++, it is not possible for the implementer to prevent the user from doing wrong.

Whether it will cause failure in an application depends on whether the application cares about comparing COM objects for identity.

MSDN: The Rules of the Component Object Model

Object identity. It is required that any call to QueryInterface on any interface for a given object instance for the specific interface IUnknown must always return the same physical pointer value. This enables calling QueryInterface(IID_IUnknown, ...) on any two interfaces and comparing the results to determine whether they point to the same instance of an object (the same COM object identity).

As Oleg points out, the failure of object identity will have a rather limited effect, because the calling of COM interface members is essentially unaffected - each virtual table entry will point to the same function address if the function signature matches.

All COM smart pointer implementations use QueryInterface when casting to a different interface, or when the current interface is dubious. Their comparison operators automatically use QueryInterface(IID_IUnknown, ...) on each input pointer in order to get physical IUnknown* pointers which can be directly compared. The failure of object identity will start to affect your application if you opt to use raw pointers throughout your application.

One special case where the failure will not manifest is when the class does not have any diamond inheritance. However, implicit casting is always illegal in COM, regardless of whether it crashes an application or not.

rwong
I've read The Rules, but... They say that `QueryInterface()`, not anything else, must follow the identity requirement. How does that really mean that an implicit upcast is illegal? `QI()` is `QI()`, C++ conversions are C++ conversions.
sharptooth
I disagree. The quote talks about *object identity*, not the same thing as interface identity.
Hans Passant
@sharptooth: When thinking in COM, try to stay away from the thinking that IInterface1 inherits from IUnknown. A more precise way is to say: IInterface1 contains the members AddRef, Release and QueryInterface as well as its own functions.
rwong
@Hans: I admit that there may be a confusion. The title of this question says "implicit conversion for an upcast instead of QueryInterface". Inside the question @sharptooth asks if it is illegal to *allow* this to happen. (I'll edit my answer to clarify)
rwong
Anyway the rule says that two QI calls for the same interface on the same object must return the same pointer. This requirement is not violated with an upcast - QI will still work fine.
sharptooth
@Hans: I deleted my old comment because I did not clearly say which side (QI implementer or the COM user) should/should not use C++ casting. I edited my answer again to say that @sharptooth's QI is correct.
rwong
I realize my assertion was too strict. I found that even in official code, it is allowed to cast to IUnknown. The burden of removing the IUnknown ambiguity falls onto the function which compares object identity. For this reason, the comparison function will always call QueryInterface(IID_IUnknown) even if you are passing in an IUnknown.
rwong
+2  A: 

COM has no rules regarding interface identity, only of object identity. The first rule of QI says that a QI on IID_Unknown on two interface pointers must return the same pointer if they are implemented by the same object. Your QI implementation does this correctly.

Without a guarantee for interface identity, a COM method cannot assume that it gets the same IUnknown pointer passed that it will retrieve when it calls QI on that pointer. So if object identity needs to be proven then a separate QI is required.

Hans Passant
A: 

If you have interface IInterface1 : IDispatch and interface IInterface2 : IDispatch then QI for IUnknown on IInterface1 and IInterface2 must return the same pointer per object identity rule

but...

QI for IDispatch on IInterface1 can return a different implementation compared to QI for IDispatch on IInterface2.

So the answer is (again) it depends. Negative for upcasting to IUnknown, could be positive for upcasting to anything else.

wqw