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.

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.

|