The typical COM+ component creates a new class object for each client, and the client uses the IClassFactory::CreateInstance method to instantiate new objects. This is a good design for most objects, but some objects—such as objects that do not store any information about their clients—fit better in the singleton model. A singleton creates only one instance of a coclass and offers that object to all clients.7 For example, a singleton object might plausibly implement the following interface:
interface IPrime : IUnknown { HRESULT IsPrime(long testnumber, [out, retval] boolean* retval); } |
The IPrime interface does not store a client state between method calls. Implementing the IPrime interface in a typical multi-instance component is not difficult, but instantiating a new Prime object for each client that connects when all clients could be sharing a single instance of the object does consume unnecessary resources. In theory, the singleton design would enable a prime number component to scale better when accessed by a large number of clients concurrently. While COM+ does not offer any special APIs or interfaces designed specifically for implementing singletons, building a singleton object does not present any special challenges either. Arguably the simplest way to implement a singleton object is to return the same instance from every call to the IClassFactory::CreateInstance method, as shown here:
HRESULT CFactory::CreateInstance(IUnknown* pUnknownOuter, REFIID riid, void** ppv) { if(pUnknownOuter != NULL) return CLASS_E_NOAGGREGATION; static CPrime SingletonPrime; return SingletonPrime.QueryInterface(riid, ppv); } |
This practical technique works acceptably well, but aesthetically it leaves much to be desired. Returning a pointer to the same object each time the CreateInstance method is called seems almost unethical. Whereas the CreateInstance method is obviously designed to create a new instance of a class, the CoGetClassObject function is designed only to obtain a pointer to a class object, not necessarily to create one. Thus, a second way to design singleton objects in COM+ is to implement a custom interface directly in the class object.8
While this technique is semantically better than altering the behavior of the CreateInstance method as just described, it is not without its shortcomings. As we discussed previously, building a custom class object in an executable component requires that you implement the IExternalConnection interface. It also means that CoCreateInstance will no longer work, which in turn means that client applications written in Microsoft Visual Basic or Java cannot access the singleton object using the new operator or Visual Basic's CreateObject function.
The solution to this problem, at least for Visual Basic clients, is to use the class moniker via the GetObject function.
Whatever mechanism you choose to expose a singleton COM+ object, be aware that reference counting is normally disabled for such objects—because the final Release method should not execute a delete this statement! Typically, singleton objects stay in memory for the lifetime of the process they live in, so reference counting is not terribly important. A common way to implement this type of object is to code the AddRef and Release methods so that they simply return dummy values, as shown here:
ULONG CPrime::AddRef() { return 2; } ULONG CPrime::Release() { return 1; } |