top header
interfaces><img src=

 

home contents introduction com as a better C++ interfaces classes apartments labs lab setup quizzes
 
previousnext

 
The COM Type System : IUnknown

A single object can satisfy multiple problem domains by providing different interfaces. The same object can be cast to two different, unrelated types. Some implementation languages have built-in support for discovering types and coercing types from one to another at runtime. COM defines an interface called IUnknown to model this behavior. IUnknown also defines how to manage interface references to control object lifetime.

IUnknown is the impedance match between the type systems of objects written in different languages. It must be implemented by all COM objects. We will see why in the information that follows. In fact, every COM interfaces must derive (directly or indirectly) from IUnknown. This means the three IUnknown methods are the initial 3 entries of every interface vtable. Some languages like Java and Visual Basic can map their native language/runtime support for object and interface management to IUnknown easily without troubling the programmer. In other languages, such as C and C++, the programmer has to write the code to provide IUnknown behavior (although this is often automated by a framework such as MFC or ATL).

The IDL for IUnknown looks like this:

[uuid(00000000-0000-0000-C000-000000000046), object]
interface IUnknown {
    // called by client to request new interface pointer of type identified by riid
    HRESULT QueryInterface([in] REFIID riid,[out, iid_is(riid)] void **ppv);
    // called by client to inform object that reference has  been duplicated
    ULONG AddRef(void);
    // called by client to inform object that extant reference has been invalidated
    ULONG Release(void);
}

IUnknown should be familiar to you. It is the direct COM equivalent of the IAnyInterface interface we developed in the "COM as a better C++" chapter.

IUnknown and object lifetime

Some languages, such as Java and VB, have built-in garbage collection. That is to say, when an object reference goes out of scope it is automatically cleared up. When there are no more outstanding object references then the object is reclaimed. Some languages, such as C++ do not (although it is possible to write code that achieves this).

It would be nice if this feature were supported by the COM runtime for dealing with garbage collection of COM objects. However, at the time of writing, the COM runtime only performs garbage collection of COM objects under limited circumstances. It is, therefore, up to the client to assume the worst and instigate garbage collection. The client must inform the object whenever it coerces one type to another and when object references go out of scope. The client must also inform the object when an interface reference is duplicated without the object knowing. The client does this with IUnknown.

Clients must blindly follow the rules for using IUnknown which are as follows.

  • IUnknown::AddRef() must be called each time an object reference is duplicated.
  • IUnknown::Release() must be called each time an object reference is destroyed or invalidated
    • IUnknown::AddRef() will have been called by any method/function that returns to you an object reference as an output parameter.

The COM object must decide on its lifetime policy and implement IUnknown to reflect this. Object implementations can do whatever they want in their AddRef() and Release(). If an object cares about knowing when it is no longer being used (perhaps to delete itself), it will normally keep a reference count.

As mentioned earlier, some languages/environments, such as Java and VB will implement IUnknown using their native garbage collector and the programmer will never get to see it. In other languages, like C++, the programmer has to code it.

Here are a couple of C++ code examples to show the rules of engagement.

void SomeFunction(IUnknown *pUnkIn) {
    IUnknown *p=NULL;
    if (NULL != pUnkIn)
        // We know that p is NULL so no need to call Release() through it
        // We are then making a copy in p so AddRef() through it
        (p=pUnkIn)->AddRef();
     // No need to AddRef() - copy made on stack of p exists within lifetime of p
    SomeOtherFunction(p);
    // p is going out of scope so call Release() through it
    if (NULL != p)
        p->Release();
}

void SomeFunction2(IUnknown **ppUnkInOut) {
    IUnknown *p=NULL;
    // p will have been AddRef()'d as it is an output parameter
    GetAReference(&p);
    // *ppUnkInOut is going to be overwritten so call Release() through it if it is valid
    if (NULL != *ppUnkInOut)
        (*ppUnkInOut)->Release();
    // We are making a copy in *ppUnkInOut so AddRef() through it
    (*ppUnkInOut=p)->AddRef();
    // p is going out of scope so call Release() through it
    if (NULL != p)
        p->Release();
}

In Java the code would be slightly simpler because Java has built-in garbage collection.

class xxx {
    ...
    void SomeFunction(com.ms.com.IUnknown UnkIn) {
        com.ms.com.IUnknown p;
        p=UnkIn;
        SomeOtherFunction(p);
    }
   void SomeFunction2(com.ms.com.IUnknown UnkInOut) {
        com.ms.com.IUnknown p;
        p = GetAReference();
        UnkInOut=p;
    }
}

You could easily make C++ code look like this by using a smart pointer class to wrap up an interface pointer. Such a smart pointer class has constructors, destructors, and operators that deal with all situations where AddRef() and Release() would need to be called. See classes like the ATL CComPtr class or the Visual C++ Compiler COM Support Class _com_ptr_t.

Be very careful about the harmful side effects of using smart pointers. Not all developers who write smart interface pointer classes know as much as they need to. They forget that it is non-trivial to write a class that can be used with confidence by a client in all situations. Read of the following articles by Don Box - Smart pointers considered harmful and COM smart pointers considered harmful, which illustrate the problems.

IUnknown and dynamic type discovery

Some languages/environments, such as C++ and Java, support the notion of self-describing types. It is possible to look at a type at runtime and discover its type information. This is sometimes referred to as runtime type information (RTTI). Given this information we can safely cast between types at runtime. Some languages/environments do not support this feature. We need all COM objects to support runtime type discovery and coercion so we mirror this with IUnknown also.

IUnknown::QueryInterface() models RTTI in a language-neutral fashion. For a single COM object it can be used to

  • discover whether an interface reference of one type can be cast to a reference of a different type. If it can,
  • cast an interface reference of one type to a reference of a different type.

The type identifier in each case is the interface GUIDs (IIDs). We have already seen that interface ids are used  to avoid namespace collisions.

Any output parameter from any COM interface method or function that returns an interface reference will have had AddRef() called through it. So this means QueryInterface() will call AddRef() on any interface reference it gives out. The resultant object reference must have Release() called through it when finished with by the caller. QueryInterface() is the semantic equivalent of the Java cast operator. QueryInterface() is almost the semantic equivalent of the C++ dynamic_cast operator, the difference being that QueryInterface() keeps track of the number of object references it has given out.

Raw C++ code would look like this.

struct IFoo {
    virtual void SomeMethod(short *ps) = 0;
};

void SomeFunction(void *p) {
    IFoo *pf = dynamic_cast<IFoo *>(p);
    if (NULL != pf) {
        short s = 10;
        pf->SomeMethod(&s);
    }
}

COM C++ code would look like this.

[object,uuid(DEFACED1-0229-2552-1D11-ABBADABBAD00)]
interface IFoo : IUnknown
{
    HRESULT SomeMethod([in]short *ps);
};

struct IFoo : public IUnknown {
    virtual HRESULT _stdcall SomeMethod(short *ps) = 0;
};
const IID IID_IFoo = {0xDEFACED1, 0x0229, 0x2552,
{ 0x1D, 0x11, 0xAB, 0xBA, 0xDA, 0xBB, 0xAD, 0x00 } };

void SomeFunction(IUnknown *p) {
    IFoo *pf = NULL;
    HRESULT hr = p->QueryInterface(IID_IFoo, (void **)&pf);
    if (SUCCEEDED(hr)) {
        short s = 10;
        pf->SomeMethod(&s);
        pf->Release();
    }
}

The (riid, ppv) combination of parameters appears in many places in COM. It is used to allow a client to obtain a dynamically typed result. It should be ˇ°readˇ± from right to left. The riid parameter describes the ˇ°shapeˇ± of the ppv parameter.

interfaces

IUnknown and type safety

QueryInterface() forces the caller to cast the interface reference output parameter as a void ** (on the basis that it is impossible to predict what it might actually be). This introduces a large hole in the C++ type system. You could write code such as this.

IBar *pb = NULL;
IFoo *pf = NULL;

QueryInterface(IID_IFoo, (void**)&pb);
QueryInterface(IID_IFoo, (void**)pf);

Both of these will compile but are obviously wrong. In the first case the client has said that it wants back an IID_IFoo but has passed the address of an IBar *. The object will return an IFoo vtable to the client, which it will treat as an IBar vtable. In the second case the client has not passed the address of an IFoo *, so the output parameter will get written to the wrong address. In either case, when the client comes to call the interface, things will go terribly wrong.

These situations won't occur in Java. They could have been avoided with a bit of careful coding in C++. If the following macros were used, mistakes could be found at compile-time rather than delaying them until runtime.

#define IID_PPV_ARG(Type, Expr) IID_##Type, reinterpret_cast<void**>(static_cast<Type**>(Expr))
#define SafeQI(Type, Expr) QueryInterface(IID_PPV_ARG(Type, Expr))

SafeQI(IFoo, &pb); // compiler error
SafeQI(IFoo, pf); // compiler error

Let's do another lab that covers the client's use of IUnknown.

Implementing IUnknown

A default implementation of the IUnknown method implementations was not provided by the COM runtime deliberately. By forcing the COM object to implement its own, IUnknown has massive flexibility, allowing an object to define its own policy for lifetime management and casting itself from one type to another.

Languages/environments such as VB and Java implement IUnknown in their "virtual machine." This has the advantage of providing a simple, presumably defect-free implementation, but it is inflexible and limited to the options provided by the language runtime. This trend will continue in COM+, the next generation of the COM runtime.

C++ frameworks, such as ATL, provide many different IUnknown implementations, covering all common policies and implementation techniques for lifetime management and interface provision. As time goes by, it will become less likely that you will need to implement IUnknown yourself.

However, at the moment, if you are programming 'in the raw', C++ rejects convenience in favor of total flexibility. The price is that you have to code IUnknown yourself, which requires you to handcraft about 13-lines of code in the simplest case.

There are many different ways that you could implement IUnknown to cover all possible lifetime policies and casting policies. Most of them are beyond the scope of this discussion. We will show the simplest and most common policy and implementation technique.

// Use multiple inheritance to define type relationships.
class Snafu : public IFoo, public ISnafu {
    LONG m_cRef; // reference count
    virtual ~Snafu(void) {;}
public:
    Snafu(void);

    // IUnknown methods
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);
    // IFoo methods
    STDMETHODIMP SomeMethod(short *ps) { return S_OK; }
    // ISnafu methods
    STDMETHODIMP UsefulMethod(void) { return S_OK; }
};

Snafu::Snafu() : m_cRef(0) {}

// Heap based implementation that keeps a reference count and deletes itself when the reference count is 0.
// Notice this is thread-safe.
STDMETHODIMP_(ULONG) Snafu::AddRef(void) {
    return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) Snafu::Release(void) {
    LONG r = InterlockedDecrement(&m_cRef);
    if (r == 0)
        delete this;
    // Careful to return a stack-based variable.
    // m_CRef will be invalid if the object has deleted itself.
    return r;
}

STDMETHODIMP Snafu::QueryInterface(REFIID riid, void **ppv) {
    // Use static_cast to pass back (compile time) type-safe reference
    if (riid == IID_IFoo)
        *ppv = static_cast<IFoo*>(this);
    else if (riid == IID_ISnafu)
        *ppv = static_cast<ISnafu*>(this);
    else if (riid == IID_IUnknown)
        // IUnknown cast is ambiguous because Snafu derives from ISnafu and IFoo that both derive from IUnknown
        *ppv = static_cast<IFoo*>(this);
    else {
        // Return an error code
        *ppv = 0;
        return E_NOINTERFACE;
    }
    // Cast the output parameter to IUnknown (it will be of course) and call AddRef() through it
    reinterpret_cast<IUnknown*>(*ppv)->AddRef();
    // Return a success code
    return S_OK;
}

A C++ implementation must use STDMETHODIMP macros to ensure proper calling convention. Notice how QueryInterface() simulates dynamic_cast using static_cast. In this case we assume a heap-based object where the AddRef() and Release() implementations manage a reference count, and where Release() deletes the object when the last client goes away. We could change this to implement a non-heap-based object thusly

// Alternatively a non-heap based implementation that doesn't bother to keep a reference count.
STDMETHODIMP_(ULONG) Snafu::AddRef(void) {
    return 2;
}

STDMETHODIMP_(ULONG) Snafu::Release(void) {
    return 1;
}

Here we are going to do a lab in which we implement some interfaces in a server object.

Now we will go to the next section, COM Classes 1.


Top of document