Why Exactly Do I Need an Explicit Upcast When Implementing Queryinterface() in an Object with Multiple Interfaces()

Why exactly do I need an explicit upcast when implementing QueryInterface() in an object with multiple interfaces()

The problem is that *ppv is usually a void* - directly assigning this to it will simply take the existing this pointer and give *ppv the value of it (since all pointers can be cast to void*).

This is not a problem with single inheritance because with single inheritance the base pointer is always the same for all classes (because the vtable is just extended for the derived classes).

However - for multiple inheritance you actually end up with multiple base pointers, depending on which 'view' of the class you're talking about! The reason for this is that with multiple inheritance you can't just extend the vtable - you need multiple vtables depending on which branch you're talking about.

So you need to cast the this pointer to make sure that the compiler puts the correct base pointer (for the correct vtable) into *ppv.

Here's an example of single inheritance:

class A {
virtual void fa0();
virtual void fa1();
int a0;
};

class B : public A {
virtual void fb0();
virtual void fb1();
int b0;
};

vtable for A:

[0] fa0
[1] fa1

vtable for B:

[0] fa0
[1] fa1
[2] fb0
[3] fb1

Note that if you have the B vtable and you treat it like an A vtable it just works - the offsets for the members of A are exactly what you would expect.

Here's an example using multiple inheritance (using definitions of A and B from above) (note: just an example - implementations may vary):

class C {
virtual void fc0();
virtual void fc1();
int c0;
};

class D : public B, public C {
virtual void fd0();
virtual void fd1();
int d0;
};

vtable for C:

[0] fc0
[1] fc1

vtable for D:

@A:
[0] fa0
[1] fa1
[2] fb0
[3] fb1
[4] fd0
[5] fd1

@C:
[0] fc0
[1] fc1
[2] fd0
[3] fd1

And the actual memory layout for D:

[0] @A vtable
[1] a0
[2] b0
[3] @C vtable
[4] c0
[5] d0

Note that if you treat a D vtable as an A it will work (this is coincidence - you can't rely on it). However - if you treat a D vtable as a C when you call c0 (which the compiler expects in slot 0 of the vtable) you'll suddenly be calling a0!

When you call c0 on a D what the compiler does is it actually passes a fake this pointer which has a vtable which looks the way it should for a C.

So when you call a C function on D it needs to adjust the vtable to point to the middle of the D object (at the @C vtable) before calling the function.

When implementing several COM interfaces at once how do I upcast to IUnknown?

Mark Ransom already gave the correct answer - any will do, as long as it's consistent - but picking the first one has one minor advantage. Due to layout rules, the IUnknown* of the first interface will point to the start of the object. Any other IUnknown* will point to subsequent vtable pointers elsewhere in the object. For debugging purposes, it's very useful to know where ano object begins in memory.

Is using implicit conversion for an upcast instead of QueryInterface() legal with multiple inheritance?

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.

Is there any reason against directly calling AddRef() inside QueryInterface() implementation?

AddRef is pure virtual in IUnknown, and none of the other interfaces implement it, so the only implementation in your program is the one you write in CMyClass. That one method overrides both IInterface1::AddRef and IInterface2::AddRef. IUnknown doesn't have any data members (such as a reference count), so the diamond problem doesn't cause your class to be susceptible to a problem such as different calls to AddRef acting on different data in the same class.

Calls to this->AddRef() are going to be routed to the same place as static_cast<IUnknown*>(*ppv)->AddRef(). I see no reason for the more verbose style.

How does implementing multiple COM interfaces work in C++?

Yes, there are multiple v-tables, one for each inherited interface. The static_cast<> returns it. The compiler makes sure that common methods in the inherited interfaces are shared, it fills each v-table slot with the a pointer to the same function. So you only need one implementation of AddRef, Release, QueryInterface. Just what you want. None of this is an accident.

This is only ever a problem when a coclass implements multiple interfaces with the same method that you don't want to give the same implementation. The IConnectionPoint::Advise() method is a notorious example. Or was it DAdvise()? Unfortunately, I don't remember what it clashed with and how it was solved, it was covered by ATL Internals. Very good book btw.

Good techniques for keeping COM classes that implement multiple interfaces manageable

Assuming that you don't need the handler objects to be separate C++ instances from the overall object, then the following may work...

If I Remember my COM Correctly... - why not just leave FooHandler as a partially abstract base class - leave the IUnknown portion unimplemented. Have MyCOMClass derive from all the necessary handlers; and then implement IUnknown only in that most-derived class. The AddRef/Release/QI you supply there will be used for all the base classes. (Typically can just forward AddRef/Release to the CUnknown class or some base that does the refcounting housekeeping, but likely need to implement QI manually since this is the only place that you know fully what set of interfaces you want to expose.)

In other words, keep doing what you are doing; but you don't need to do the delegation part manually: the compiler actually does similar for you behind the scenes anyhow due to how Multiple Inheritance of interfaces (specifically, classes with virtual methods) works in C++. The magic part is that methods declared in the most-derived interface by default override all those of the same name and parameter signature in the base classes; so any base class that calls AddRef or QI in their own IUnknown will end up actually calling the version you supply in the most-derived class.

Code likely looks something like:

class MyCOMClass
, public CUnknown // Assume this handles refcounting (and has a virtual dtor!)
, public CFooHandler // Implements IFoo
, public CBarHandler // Implements IBar
{
... add any interfaces that MyCOMClass itself is implementing...

// Actual impl of IUnknown...
STDMETHOD_(ULONG, AddRef)(); { return CUnknown::AddRef(); }
STDMETHOD_(ULONG, Release)(); { return CUnknown::Release(); }

STDMETHOD(QueryInterface)(IN REFIID riid, OUT void** ppv)
{
*ppv = NULL;

// IUnknown can require extra casting to pick out a specific IUnknown instance
// otherwise compiler will complain about an ambiguous cast. Any IUnknown will do,
// we know they're all the same implementation, so even casting to CFooHandler then IUnknown is fine here.
// Here am assuming that CUnknown implements IUnknown
if(riid == __uuidof(IUnknown))
*ppv = static_cast<IUnknown*>(static_cast<CUnknown*>(this));
else if(riid == __uuidof(IFoo))
*ppv = static_cast<IFoo*>(this);
else if(riid == __uuidof(IBar))
*ppv = static_cast<IBar*>(this);
else
return E_NOINTERFACE;

// Usually you call AddRef on the interface you are returning; but
// we know we're using the same ref count for the whole object, so this
// is appropriate for this specific implementation strategy.
AddRef();
}

As a for-what-it's-worth, if you do want to implement the handlers on separate objects, then you will need to do the delegation that you are proposing - it's essentially a form of aggregation. But you don't need to implement the interfaces on the MyCOMClass and write lots of forwarders: all MyCOMClass has to do is implement QI such that the returned value - whether the same 'this' object or some separate object - is properly cast to the requested interface. But if you don't need separate objects, the above technique should work fine.

Why do IUnknown* pointers retrieved through different interfaces of the same COM object have the same value?

That's so called "object identity" requirement that states that whenever you request IUnknown from two objects you get distinct pointers if those are distinct objects and equal pointers if that's the same object.

Every QueryInterface() implementation must fulfill this requirement. This is usually done by choosing which one IUnknown to return and sticking to it:

HRESULT CA::QueryInterface( REFIID iid, void** ppv )
{
if( iid == __uuidof( IUnknown ) ) {
// Explicitly cast to one of base class subobjects.
// Doesn't matter which one is chosen - it just has to be
// the same base class subobject each time IUnknown is requested.
IUnknown* selected = static_cast<IX*>( this );
*ppv = selected;
AddRef();
return S_OK;
} else {
continue for other interfaces
}
}

Is there any case when dynamic_cast should be used in the QueryInterface implementation?

It would be necessary in those implementations of QueryInterface where the "supported interface ids" aren't known. E.g. if you decide to implement QueryInterface in a base class, and not override it for every derived class.

A case where this would happen is the situation where you have a lot of similar types, where "similar" means "implementing many of the same interfaces". I.e. you have object types Derived1... DerivedN which all implement some subset of Interface1...InterfaceM.

This could be the case for a game engine where game entities all implement a subset of IMoveable, IScriptable, IFactory, IShoots, IPlayerControlled, IStealthy, ISensor, etcetera. Of course, by COM rules you must be able to call IFactory::QueryInterface and get an IMovable* if and only if the factory also implements IMovable.

How are you going to implement all those QueryInterface methods? It's easiest to insert an GameObject base class between IUnknown and IFactory, and implement GameObject::QueryInterface using dynamic_cast checks. In this way, you need only one implementation, instead of one per interface of a concrete type.

C++ COM design. Composition vs multiple inheritance

Multiple inheritance is a very common way to do COM interfaces, so yes it's possible.

However QueryInterface must still cast the pointer for each interface. One interesting property of multiple inheritance is that the pointer may get adjusted for each class type - a pointer to IDispatch won't have the same value as a pointer to IDocHostUIHandler, even though they both point to the same object. Also make sure that a QueryInterface for IUnknown always returns the same pointer; since all the interfaces derive from IUnknown you'll get an ambiguous cast if you try just to cast directly to it, but that also means you can use any interface as an IUnknown, just pick the first one in the parent list.



Related Topics



Leave a reply



Submit