Environment: C/C++
The odd thing about COM, C++, and C…
Like most average people, I talk about COM, use COM, and program COM without knowing what it is—until I decided to go back and see what it is. If you have the same curiosity, join me for some fun.
For years, I’ve known that if I have a pointer to an IUnknown, I can do a lot of things. So, let’s look at the mysterious IUnknown. We can find it in the header file “unknwn.h,” supplied by Microsoft. Don’t try to understand everything in there. In fact, let’s go the reverse way: Ignore everything except the things that we are interested in. And what follows is what we want: {skip it if you don’t like it}.
#if defined(__cplusplus) && !defined(CINTERFACE)
MIDL_INTERFACE(“00000000-0000-0000-C000-000000000046”)
IUnknown
{
public:
BEGIN_INTERFACE
virtual HRESULT STDMETHODCALLTYPE QueryInterface
(REFIID riid, void **ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;
virtual ULONG STDMETHODCALLTYPE Release(void) = 0;
END_INTERFACE
};
#else /* C style interface */
typedef struct IUnknownVtbl
{
BEGIN_INTERFACE
HRESULT (STDMETHODCALLTYPE *QueryInterface)(IUnknown *This,
REFIID riid, void **ppvObject);
ULONG (STDMETHODCALLTYPE *AddRef)(IUnknown *This);
ULONG (STDMETHODCALLTYPE *Release)(IUnknown *This);
END_INTERFACE
} IUnknownVtbl;interface IUnknown
{
CONST_VTBL struct IUnknownVtbl *lpVtbl;
};
#endif
We further ignore !defined(CINTERFACE)
, MIDL_INTERFACE("00000000-0000-0000-C000-000000000046")
, BEGIN_INTERFACE
, and END_INTERFACE
. The HRESULT
and ULONG
are equivalent to a 32-bit integer, so we treat them as unsigned
. The STDMETHODCALLTYPE
is finally resoved to __stdcall
. The REFIID
is resolved to a reference (or a pointer if the code is writing in C) to a structure that identifies the interface ID, so we leave it unchanged. The CONST_VTBL
will resolve to const
. Now what we have is:
#if defined(__cplusplus)
IUnknown
{
public:
virtual unsigned __stdcall QueryInterface(REFIID riid,
void **ppvObject) = 0;
virtual unsigned __stdcall AddRef( void) = 0;
virtual unsigned __stdcall Release( void) = 0;
};
#else
typedef struct IUnknownVtbl
{
unsigned (__stdcall *QueryInterface)(IUnknown *This, REFIID
riid, void **ppvObject);
unsigned (__stdcall *AddRef)(IUnknown *This);
unsigned (__stdcall *Release)(IUnknown *This);
} IUnknownVtbl;interface IUnknown
{
const struct IUnknownVtbl *lpVtbl;
};
#endif
Try to understand the preceding code; simplifying it further, we have:
#if defined(C++)
ISomeInterface
{
public:
some pure function(s)…
};
#else
typedef struct ISomeInterface_Vtbl
{
some function pointer(s)…
} ISomeInterface_Vtbl;interface ISomeInterface
{
const struct ISomeInterface_Vtbl *lpVtbl;
};
#endif
Now our conclusion is: In C++, ISomeInterface is just an abstract class with some pure functions and in C, ISomeInterface is simply a pointer to a struct that contains some function pointers. Drawing the equivalence between them, we have the following core knowledge about COM, C++, and C:
A C++ class/struct (or a COM interface) is simply a pointer to a table of pointers to something. Ah, ha!—How simple it is!
From now on, don’t think COM, don’t think C++, but C pointers. We are already familiar with C pointers. They are small, fast, and can do a lot of things. That’s what I want to say. We’ll then try to find some ways to play with them.
Note: The words table, array, and struct can be used interchangably with each other as long as we agree on their meaning. Also, we don’t care about a class’s data member yet. We’ll deal with that sometime later.
A little bit about the __stdcall
: It is just an agreement between the caller and what is being called, so that the item being called knows how to accept the call, and the caller knows how to make a call. The This
pointer needs a little bit more description, we’ll talk about it next time.