0

I've been looking at the BHO tutorial in C++ here: http://www.codeproject.com/Articles/37044/Writing-a-BHO-in-Plain-C

The COM classes CClassFactory and CObjectWithSite must implement IUnknown. CClassfactory also must implement IClassFactory and CObjectWithSite must also implement IObjectWitSite. A single CUnknown class is created so that both can inherit from it and not have to implement IUnknown by themselves.

CUnknown is declared as follows:

template <typename T>
class CUnknown : public T

CClassFactory is declared like this:

class CClassFactory : public CUnknown<IClassFactory>

And CObjectWithSite is declared:

class CObjectWithSite : public CUnknown<IObjectWithSite>

Why is CUnknown extending the type parameter T? Why must we pass the other interfaces to the class constructor?

5
  • Did you look at the remaining declaration of CUnknown? Commented Dec 3, 2014 at 15:41
  • A class like CUnknown is typically an implementation of IUnknown. The IUknown interface is special in COM because it provides object identity: all requests for IUnknown must produce the same pointer. Typically an IUnknown implementation is added at the bottom of the inheritance hierarchy, but I see it's done slightly differently above for the last two classes. Commented Dec 3, 2014 at 15:44
  • Yup. I see that methods are templates too so that we can call the method on the appropriate implementation. But I don't understand why the class extends T. Indeed, IObjectWithSite and IClassFactory both inherit FROM IUnknown Commented Dec 3, 2014 at 15:44
  • "extending an interface" means something else in COM, so perhaps the question should read "Why does CUnknown derive from T". The answer is that CUnknown implements IUnknown for T (or tries to!) so that you don't have to repeat the IUnknown implementation for each of your object's interfaces. The posted answers go into more detail for this Commented Dec 4, 2014 at 16:34
  • 1
    look up "curiously recurring template pattern" for more background info on the technique of deriving from a template parameter Commented Dec 4, 2014 at 16:34

2 Answers 2

5

First, you must understand that COM is a binary standard which has IUnknown at the base of every interface pointer.

An interface pointer is the entry point to an object, which you never see in COM. Although new objects may be created with e.g. CoCreateInstance or some other COM method (CoCreateInstance will ultimately call IClassFactory::CreateInstance), what you actually obtain is a refcounted, possibly proxied, interface pointer. Only the server knows about the actual object.

In Visual C++, and I think in some other Windows C++ compilers, classes are (or may optionally be, in the case of other compilers) implemented using vtables, which are structs of function pointers, where each function pointer points to a virtual method. It's no coincidence that interface pointers are pointers to a vtable pointer, or rather, pointers to a struct with one field, a pointer to a vtable.

Visual C++ class as a struct
----------------------------
struct vtable *
<fields>

When a class inherits multiple "incompatible classes", you get multiple vtables, as many as the incompatibilities (let's define "incompatible classes" as "a classe that is not strictly a superclass/subclass of the other", i.e. one class's hierarchy forks or they don't have anything in common).

Visual C++ class as a struct
----------------------------
struct vtable1 *
struct vtable2 *
...
struct vtableN *
<fields>

For instance, if you inherit IUnknown and IClassFactory, you get 1 vtable, since IClassFactory inherits from IUnknown, so the first three entries of IUnknown are shared with the first three entries of IClassFactory. In fact, IClassFactory's vtable is a valid IUnknown vtable.

class CFoo : IClassFactory
--------------------------
struct IClassFactory_vtable * -\
<fields>                       |
                               |
IClassFactory_vtable <---------/   (IUnknown_vtable)
--------------------               -----------------
QueryInterface                     QueryInterface
AddRef                             AddRef
Release                            Release
CreateInstance
LockServer

The inheritance diagram:

IUnknown
    ^
    |
IClassfactory
    ^
    |
  CFoo

But if you inherit e.g. IDispatch and IConnectionPointContainer, you get 2 vtables, for which the first three entries of each vtable point to the same IUnknown methods, but they are nonetheless two distinct structs.

class CBar : SomethingElse, IDispatch, IConnectionPointContainer
----------------------------------------------------------------
struct SomethingElse_vtable * -----------------------------> ...
struct IDispatch_vtable * ------------------------\
struct IConnectionPointContainer_vtable * --------|--------\
<fields>                                          |        |
                   /------------------------------/        |
                   |                                       |
IDispatch_vtable <-/    IConnectionPointContainer_vtable <-/   (IUnknown_vtable)
----------------        --------------------------------       -----------------
QueryInterface     =    QueryInterface                         QueryInterface
AddRef             =    AddRef                                 AddRef
Release            =    Release                                Release
GetTypeInfoCount        EnumConnectionPoints
GetTypeInfo             FindConnectionPoint
GetIDsOfNames
Invoke

You can search for "diamond problem" to better understand this approach.

SomethingElse
      ^         IUnknown
      |             ^
      |             |
      |       /-----+-----\
      |       |           |
      |  IDispatch     IConnectionPointContainer
      |       ^           ^
      |       |           |
      |       \-----+-----/
      |             |
      \----+-------/
           |
         CBar

However, each vtable is a valid IUnknown vtable. So, when you implement QueryInterface, you must choose which serves as your default IUnknown vtable by casting this to one of the interface pointer types, because static_cast<IUnknown*>(this) is ambiguous in this case.

In the linked article, CUnknown::QueryInterface always returns (void*)this, which is wrong if your first inherited class isn't a COM interface, or if you inherit more than one COM interface hierarchy that you intend to return in QueryInterface. Its author made QueryInterface only know how to handle a class that inherits a single COM interface hierarchy as its first inheritance.

You have to provide the various interface IDs for as many interfaces in the inheritance hierarchy. For instance, if you implement IClassFactory2, your QueryInterface could return the same vtable when asked for IID_IUnknown, IID_ClassFactory and IID_ClassFactory2. As far as I know, there is not enough introspection in VC++ to get all IIDs from an interface pointer type, although there is an extension to get a specified IID, __uuidof.

As such, you must provide the full set of IIDs (or interface types to be provided to __uuidof).

ATL, for instance, will use the first provided interface pointer mapping as the IUnknown interface pointer. It's an arbitrary choice, but a consistent one which makes sure that the identity QueryInterface rule is followed, by always returning the same interface pointer for IID_IUnknown.

Summary

So finally, to answer your specific question, CUnknown extends type T so the given interface pointer's vtable is shared with IUnknown's vtable. Or rather, that CUnknown<T> ends up implementing the boring IUnknown methods.

If you happen to provide a class that doesn't inherit from IUnknown, or more specifically, which doesn't have compatible virtual methods in the inheritance chain, you actually get QueryInterface, AddRef and Release at the end of the class's first vtable, instead of having all COM interfaces point to those three methods.


PS: CClassFactory could also be templated, so you could reuse it with any COM class.

When you master the "basics" of COM, you should definitely take a look at ATL, as it generally takes the template approach when possible and the macro approach when not.

Unfortunately, you won't understand ATL if you don't understand COM. You can just as easily do something that works well, something that works by coincidence or accident, and something that doesn't work and ends up being hard to debug, without knowing COM. What ATL does is saving you from the trouble of implementing lots of boilerplate correctly.

Sign up to request clarification or add additional context in comments.

2 Comments

Of course, if you inherit two IUnknown interfaces via two paths, you must make sure that you get the same result no matter via which path it's accessed. For your example, IConnectionPointContainer::QueryInterface must know about IDispatch.
Yes, as I stated, the IUnknown method pointers in both vtables will typically be the same, so you will only have one QueryInterface method that must know about all interfaces it supports. You have to actively do something to work around the common diamond method override, such as defining utility middle classes with virtual methods with different names, then inheriting from those classes and override the new virtual methods.
3

IMHO the question is moot because CUknown as listed on site is incorrect. Specifically:

template <typename T> STDMETHODIMP CUnknown<T>::QueryInterface(REFIID riid,void **ppvObject)
{
...
  if(IsEqualIID(riid,supported_iids[n])) {
    (*ppvObject)=(void*)this;
...

This implementation does not return a pointer to the desired interface, but a pointer to the start of this. This is incorrect if this implements multiple interfaces (via C++ inheritance). Furthermore, this implementation will always return the same pointer no matter what IID is requested, which cannot possibly be correct for anything more than a trivial object that implements one and only one interface. Not even going into esoteric like COM aggregation and 'outer unknown'.

I recommend you stick with tried and true frameworks like ATL and use CComObject instead. You can look into your SDK atlbase.inl for a correct QueryInterface implementation (see AtlInternalQueryInterface code).

2 Comments

It's correct only for classes that use Single Inheritance, which happens to be the only model supported by COM anyway. It's OK to always return the same interface pointer regardless of the interface requested, as all interfaces share a single vtable. In particular, every reasonable object supports at least its own interface and IUnknown so even that trivial object has two interfaces it should answer to.
@MSalters: I hope Paulo's answer, which goes into more detail, will clarify your misunderstandings.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.