Let's look first at how we would implement a RectEnumerator object in straight C. To start, we need to define a data structure that will comprise the object, in which, according to the binary interface standard, the first 32 bits of this structure must be a pointer to the interface vtable. You can find this definition in the file ENUMRECT.H:
typedef struct tagRECTENUMERATOR
{
IEnumRECTVtbl *lpVtbl;
DWORD m_cRef; //Reference count
DWORD m_iCur; //Current enum position
RECT m_rgrc[CRECTS]; //RECTS we enumerate
} RECTENUMERATOR, *PRECTENUMERATOR;
This enumerator maintains a reference count, as expected, along with the current index of the enumeration (for implementing Next, Skip, and Reset) and a static array that holds the RECT structures to enumerate. This sample is somewhat contrived in that the number of structures is arbitrarily fixed at 15 (the constant CRECTS) and each structure contains bogus values, but it demonstrates the principles.
In addition to the object structure, we need to declare the member functions that will provide the implementation of the interface. Because we don't have the convenience of declaring member functions as we would in C++, we have to declare each one as an individually named global function:
PRECTENUMERATOR RECTENUM_Constructor(void);
void RECTENUM_Destructor(PRECTENUMERATOR);
STDMETHODIMP RECTENUM_QueryInterface(PENUMRECT, REFIID, PPVOID);
STDMETHODIMP_(ULONG) RECTENUM_AddRef(PENUMRECT);
STDMETHODIMP_(ULONG) RECTENUM_Release(PENUMRECT);
STDMETHODIMP RECTENUM_Next(PENUMRECT, DWORD, LPRECT, LPDWORD);
STDMETHODIMP RECTENUM_Skip(PENUMRECT, DWORD);
STDMETHODIMP RECTENUM_Reset(PENUMRECT);
STDMETHODIMP RECTENUM_Clone(PENUMRECT, PENUMRECT *);
The use of a constructor function and a destructor function is included to closely mimic the structure of the C++ version of this sample (shown below). The constructor is called from within the creation function CreateRectEnumeratorC, which you'll find in ENUMC.C (along with all the other C functions):
BOOL CreateRECTEnumeratorC(PENUMRECT *ppEnum)
{
PRECTENUMERATOR pRE;
HRESULT hr;
if (NULL==ppEnum)
return FALSE;
//Create object.
pRE=RECTENUM_Constructor();
if (NULL==pRE)
return FALSE;
//Get interface, which calls AddRef.
hr=pRE->lpVtbl->QueryInterface(pRE, &IID_IEnumRECT
, (void **)ppEnum);
return SUCCEEDED(hr);
}
RECTENUM_Constructor has most of the interesting stuff regarding a C implementation. This function has to manually create a vtable for the object's interfaces (of which there is only one) and then allocate the object structure and store a pointer to that vtable as the first field:
static IEnumRECTVtbl vtEnumRect;
static BOOL g_fVtblInitialized=FALSE;
PRECTENUMERATOR RECTENUM_Constructor(void)
{
PRECTENUMERATOR pRE;
UINT i;
if (!fVtblInitialized)
{
vtEnumRect.QueryInterface=RECTENUM_QueryInterface;
vtEnumRect.AddRef =RECTENUM_AddRef;
vtEnumRect.Release =RECTENUM_Release;
vtEnumRect.Next =RECTENUM_Next;
vtEnumRect.Skip =RECTENUM_Skip;
vtEnumRect.Reset =RECTENUM_Reset;
vtEnumRect.Clone =RECTENUM_Clone;
fVtblInitialized=TRUE;
}
pRE=(PRECTENUMERATOR)malloc(sizeof(RECTENUMERATOR));
if (NULL==pRE)
return NULL;
//Initialize function table pointer.
pRE->lpVtbl=&vtEnumRect;
//Initialize array of rectangles.
for (i=0; i < CRECTS; i++)
SetRect(&pRE->m_rgrc[i], i, i*2, i*3, i*4);
//Ref counts always start at 0.
pRE->m_cRef=0;
//Current pointer is first element.
pRE->m_iCur=0;
return pRE;
}
The vtable itself needs initialization only once in this program to cover all possible instances of the object, so this structure is stored as the global variable vtEnumRect, with the flag g_fVtblInitialized indicating whether initialization has yet occurred. There is nothing sacred about filling the vtable explicitly like this; you could also declare the global variable along with the names of the functions that fill the table at compile time.
Notice that the reference count from the constructor is set initially to 0. This is because no pointer to the object's interfaces actually exists at this point. So after the constructor returns, CreateRectEnumeratorC will call the object's QueryInterface to obtain its IEnumRECT pointer, which in turn calls AddRef. This query step will call AddRef before returning the pointer, thereby transferring control of the object's lifetime from the creation function to the calling client:
STDMETHODIMP RECTENUM_QueryInterface(PENUMRECT pEnum
, REFIID riid, PPVOID ppv)
{
//Always NULL the out-parameters.
*ppv=NULL;
if (IsEqualIID(riid, &IID_IUnknown)
|| IsEqualIID(riid, &IID_IEnumRECT))
*ppv=pEnum;
if (NULL==*ppv)
return ResultFromScode(E_NOINTERFACE);
//AddRef any interface we'll return.
((LPUNKNOWN)*ppv)->lpVtbl->AddRef((LPUNKNOWN)*ppv);
return NOERROR;
}
STDMETHODIMP_(ULONG) RECTENUM_AddRef(PENUMRECT pEnum)
{
PRECTENUMERATOR pRE=(PRECTENUMERATOR)pEnum;
return ++pRE->m_cRef;
}
You can see here that QueryInterface returns the same pointer for both IUnknown and IEnumRECT requests, which is perfectly normal because IEnumRECT is polymorphic with IUnknown. This shows how you don't need to implement IUnknown explicitly (unless you're supporting aggregation, as we'll see later). Again, the AddRef call made at the end of QueryInterface will increment the object's reference count initially to 1. The client's later Release call through the interface pointer will then destroy the object:
STDMETHODIMP_(ULONG) RECTENUM_Release(PENUMRECT pEnum)
{
PRECTENUMERATOR pRE=(PRECTENUMERATOR)pEnum;
if (0!=--pRE->m_cRef)
return pRE->m_cRef;
RECTENUM_Destructor(pRE);
return 0;
}
This interface member function, like all others, has to know what object is being used through this interface. In C, the first parameter to a member function is the interface pointer through which the client makes the call. Because we know internally that this pointer actually points to our RECTENUMERATOR structure, we can typecast it into our object pointer and access our data members, such as m_cRef. Here in Release, we decrement the reference count (which is trivially incremented in AddRef) and return the new value. If the value is 0, we call our own destructor, which simply deallocates the structure:
void RECTENUM_Destructor(PRECTENUMERATOR pRE)
{
if (NULL==pRE)
return;
free(pRE);
return;
}
There is no need to unravel the global vtable because we initialize it only once per task.
I will leave it to you to examine the implementation of the specific IEnumRECT member functions, which are pretty standard as far as enumerators are concerned. The Clone function, for example, simply calls our own CreateRectEnumeratorC again. This is a little simpler than a working enumerator because normally a cloning process can involve the duplication of the list itself. In cloning, it's important that the clone have the same state as the current enumerator. In our Clone implementation, this means copying the current index pointer.