RectEnumerator in C++: ENUMCPP.CPP

Now let's look at the equivalent implementation of the C object done in C++. First the object can be defined using a C++ class that inherits the function signatures from the interface. Because the interface is an abstract base class, we have to override every member function explicitly (ENUMRECT.H):


class CEnumRect : public IEnumRECT
{
private:
DWORD m_cRef; //Reference count
DWORD m_iCur; //Current enum position
RECT m_rgrc[CRECTS]; //RECTS we enumerate

public:
CEnumRect(void);
~CEnumRect(void);

STDMETHODIMP QueryInterface(REFIID, PPVOID);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);

//IEnumRECT members
STDMETHODIMP Next(ULONG, LPRECT, ULONG *);
STDMETHODIMP Skip(ULONG);
STDMETHODIMP Reset(void);
STDMETHODIMP Clone(PENUMRECT *);
};

typedef CEnumRect *PCEnumRect;

Creating the object, which happens in CreateRectEnumeratorCPP (ENUMCPP.CPP), performs the same steps as CreateRectEnumeratorC by using the C++ new operator to allocate the object, calling its constructor automatically:


BOOL CreateRECTEnumeratorCPP(PENUMRECT *ppEnum)
{
PCEnumRect pER;
HRESULT hr;

if (NULL==ppEnum)
return FALSE;

//Create object.
pER=new CEnumRect();

if (NULL==pER)
return FALSE;

//Get interface, which calls AddRef.
hr=pER->QueryInterface(IID_IEnumRECT, (void **)ppEnum);
return SUCCEEDED(hr);
}

§

CEnumRect::CEnumRect(void)
{
UINT i;

//Initialize array of rectangles.
for (i=0; i < CRECTS; i++)
SetRect(&m_rgrc[i], i, i*2, i*3, i*4);

//Ref counts always start at 0.
m_cRef=0;

//Current pointer is first element.
m_iCur=0;

return;
}

The big difference is that in C++ you do not have to create the vtable manually: that step is performed automatically by virtue of creating a C++ object that inherits it from an interface. What we did in C is similar to the code that the C++ compiler creates transparently. This is the second reason (apart from concise interface calling) that C++ is more convenient than C: the compiler does some of the work for you.

Let's also look at the implementation of QueryInterface and Release and show how the C++ functions automatically receive a this pointer instead of an extra first parameter to each member call:


STDMETHODIMP CEnumRect::QueryInterface(REFIID riid, PPVOID ppv)
{
*ppv=NULL;

if (IID_IUnknown==riid || IID_IEnumRECT==riid)
*ppv=this;

if (NULL==*ppv)
return ResultFromScode(E_NOINTERFACE);

((LPUNKNOWN)*ppv)->AddRef();
return NOERROR;
}

STDMETHODIMP_(ULONG) CEnumRect::AddRef(void)
{
return ++m_cRef;
}

STDMETHODIMP_(ULONG) CEnumRect::Release(void)
{
if (0!=--m_cRef)
return m_cRef;

delete this;
return 0;
}

In C++, QueryInterface can use the overloaded == operator for IIDs instead of calling IsEqualIID. In addition, we return our this pointer as the interface pointer because the two are equivalent. If you singly inherit an interface into a C++ object class, you do not need explicit typecasting when assigning this to a void pointer, as we're doing here. The CEnumRECT pointer is an IEnumRECT pointer in such a case.

In the preceding code, the pointer returned for IUnknown is the same as the pointer returned for IEnumRECT, so it is tempting to think that a client that obtained an IUnknown pointer could typecast it into an IEnumRECT type. This is utterly dangerous and entirely illegal because it breaks the idea of QueryInterface completely. Furthermore, it cannot possibly work when a process or machine boundary separates client and object. A client's use of a pointer in this way represents intimate knowledge of the object's implementation. This goes against encapsulation and polymorphism and is a capital crime in object-oriented programming. In short, don't do it.

As we'll see later, you also must be careful with multiple inheritance. But before we look at those issues, notice that the implementation of Release has the odd statement delete this. This is C++ suicide, if you will: the object destroys itself, calling the destructor. This may look strange, but it is perfectly legal and is used frequently in OLE object implementations.

Note: When an object can have multiple simultaneous clients, it is best to protect Release from reentrancy—that is, if you're already doing delete this, ignore any subsequent Release calls (as well as most other interface members, but this is the important one), just in case. This applies to C implementations as well. The RectEnumerator objects here do not do this because an instance has only one possible client.

Viewing both the C and the C++ implementations, you can see that what is important to OLE is that the interface be a structure that contains a pointer to a vtable that contains pointers to the implementations of the member functions. How you choose to create this structure is irrelevant: C++ is more convenient, but the same thing can be accomplished almost as easily in C. If you examine the C++ code in the ENUMRECT.CPP file, you'll see that a C++ client can make calls to a C object as easily as to a C++ object—language is no barrier in OLE.