In the C++ RectEnumerator sample, I used single inheritance to bring the function signatures for the IEnumRECT interface into the CEnumRect object class. This technique works quite well for single-interface, nonaggregatable objects. However, when you want to implement multiple interfaces, which includes an explicit IUnknown for aggregation, you need to use one of three other techniques: interface implementations, contained interface classes, and multiple inheritance, each of which are each discussed in the following sections. The C++ Query sample in CHAP02\QUERY demonstrates each of these techniques through three equivalent object implementations (CObject1, CObject2, and CObject3). The Query code itself acts as a client for any of these objects, and it creates a small window with a menu through which you can select which object to create, invoke the member functions of their interfaces, and release them.
Each object in this demonstration implements two interfaces, ISampleOne and ISampleTwo, which are defined as follows in INTERFAC.H, using the macros that expand to the appropriate C++ interface declarations for different target platforms:
DECLARE_INTERFACE_(ISampleOne, IUnknown)
{
//IUnknown members
STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
STDMETHOD_(ULONG,AddRef) (THIS) PURE;
STDMETHOD_(ULONG,Release) (THIS) PURE;
//ISampleOne members
STDMETHOD(GetMessage) (THIS_ LPTSTR, UINT) PURE;
};
typedef ISampleOne *PINTERFACEONE;
DECLARE_INTERFACE_(ISampleTwo, IUnknown)
{
//IUnknown members
STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
STDMETHOD_(ULONG,AddRef) (THIS) PURE;
STDMETHOD_(ULONG,Release) (THIS) PURE;
//ISampleTwo members
STDMETHOD(GetString) (THIS_ LPTSTR, UINT) PURE;
};
typedef ISampleTwo *PINTERFACETWO;
The IIDs for these interfaces are given in INC\BOOKGUID.H, as are all GUIDs defined for this book. Both ISampleOne::GetMessage and ISampleTwo::GetString fill a string buffer with some string that the client (QUERY.CPP) displays in its window to prove that it called the correct member function of the correct interface. It is instructive to run Query in a debugger and follow the path of execution through each object.