A basic requirement of all COM classes is that they must have a class object. A class object is a per-class singleton that implements the instanceless functionality of the class. A class object acts as the metaclass for a given implementation, and the methods it implements fill the role of static member functions from C++. Logically, there is only one class object per class; however, given the distributed nature of COM, a given class may have one class object per host machine, per user account, or per process, depending on how the class is deployed. The first point of entry into a class’s implementation is through its class object.
Class objects are very useful programming abstractions. Class objects can act as well-known objects (where their CLSID acts as the name of the object) that allow multiple clients to bind to the same object based on a given CLSID. While entire systems could be built using class objects exclusively, class objects are often used as brokers to create new instances of a class or to find existing instances based on some well-known object name. When used in these capacities, the class object will usually expose only one or two intermediary interfaces that allow clients to create or find the instances that ultimately will do the interesting work. For example, consider the interface IApe described previously. It would not violate the laws of COM for a class object to expose the IApe
interface:
class GorillaClass : public IApe {
public:
// class objects are singletons, so don’t delete
IMPLEMENT_UNKNOWN_NO_DELETE(GorillaClass)
BEGIN_INTERFACE_TABLE(GorillaClass)
IMPLEMENTS_INTERFACE(IApe)
END_INTERFACE_TABLE()
// IApe methods
STDMETHODIMP EatBanana(void);
STDMETHODIMP SwingFromTree(void);
STDMETHODIMP get_Weight(long *plbs);
};
Given that this C++ class will act as a singleton (as do all COM class objects), there can be only one gorilla at any given instant. For some domains, singletons are all that is needed. In the case of gorillas, however, it is highly likely that clients may want to build applications that use multiple distinct gorillas simultaneously. To support this usage, the gorilla class object should not export the IApe interface but instead should export a new interface that allows clients to create new gorillas and/or find well-known gorillas by their name. This will require the implementor to define two C++ classes: one to implement the class object and one to implement the actual instances of the class. For the gorilla implementation, the C++ class that defines gorilla instances will implement IApe:
class Gorilla : public IApe {
public:
// instances are heap-based, so delete when done
IMPLEMENT_UNKNOWN(Gorilla)
BEGIN_INTERFACE_TABLE(Gorilla)
IMPLEMENTS_INTERFACE(IApe)
END_INTERFACE_TABLE()
// IApe methods
STDMETHODIMP EatBanana(void);
STDMETHODIMP SwingFromTree(void);
STDMETHODIMP get_Weight(long *plbs);
};
A second interface will be needed to define the operations that the Gorilla class object will implement:
[object,uuid(753A8AAC-A7FF-11d0-8C30-0080C73925BA)]
interface IApeClass : IUnknown {
HRESULT CreateApe([out, retval] IApe **ppApe);
HRESULT GetApe([in] long nApeID,
[out, retval] IApe **ppApe);
[propget] HRESULT AverageWeight([out, retval] long *plbs);
}
Given this interface definition, the class object would implement the IApeClass methods by either creating new instances of the C++ class Gorilla (in the case of CreateApe) or mapping an arbitrary object name (in this case an integer) to a particular instance (in the case of GetApe):
class GorillaClass : public IApeClass {
public:
IMPLEMENT_UNKNOWN_NO_DELETE(GorillaClass)
BEGIN_INTERFACE_TABLE(GorillaClass)
IMPLEMENTS_INTERFACE(IApeClass)
END_INTERFACE_TABLE()
STDMETHODIMP CreateApe(IApe **ppApe) {
if (*ppApe = new Gorilla) == 0)
return E_OUTOFMEMORY;
(*ppApe)->AddRef();
return S_OK;
}
STDMETHODIMP GetApe(long nApeID, IApe **ppApe) {
// assume that a table of well-known gorillas is
// being maintained somewhere else
extern Gorilla *g_rgWellKnownGorillas[];
extern int g_nMaxGorillas;
// assert that nApeID is a valid index
*ppApe = 0;
if (nApeID > g_nMaxGorillas || nApeID < 0)
return E_INVALIDARG;
// assume that the ID is simply the index into the table
if ((*ppApe = g_rgWellKnownGorillas[nApeID]) == 0)
return E_INVALIDARG;
(*ppApe)->AddRef();
return S_OK;
}
STDMETHODIMP get_AverageWeight(long *plbs) {
extern Gorilla *g_rgWellKnownGorillas[];
extern int g_nMaxGorillas;
*plbs = 0; long lbs;
for (int i = 0; i < g_nMaxGorillas; i++) {
g_rgWellKnownGorillas[i]->get_Weight(&lbs);
*plbs += lbs;
}
*plbs /= g_nMaxGorillas;
return S_OK;
}
};
Note that this code assumes that an external table of well-known gorillas is being maintained, either by the Gorilla instances themselves or by some other agent.