Don Box Don Box is
cofounder of DevelopMentor, a training firm for COM and Windows
NT-based development. He also wrote the upcoming book Creating
Components Using DCOM and C++ for Addison-Wesley. Don can be
reached at dbox@develop.com. QI have a C++ class that simply cannot have a
default constructor and requires explicit constructor parameters
to initialize properly. How do I provide my clients using Visual
Basic ® and C++ with a mechanism for creating my objects
correctly? ACoCreateInstance is one of the first API functions
that COM programmers learn. The routine is easy to understand,
but too often programmers try to shoehorn their entire world into
this one fairly limited function. One of the primary limitations
of CoCreateInstance is its complete lack of flexibility in terms
of initialization parameters. If your C++ programs consist of
lots of calls to the new operator using default constructors,
then CoCreateInstance is great. If your constructors require
arguments to properly initialize new objects, then you are out of
luck. Consider the following extremely
simple C++ class: Assume that it is illegal to use
the object unless the client has explicitly initialized its data
members. The fact that the C++ class does not have a default
constructor enforces this at compile time. In COM, explicit steps
must be taken to make the same guarantees. One standard approach for
supporting object initialization is to export an explicit
initialization method that takes the same parameters as the
objects constructor: This implies that Visual Basic
clients would write the code shown below, which translates to the following C++ code: To support this client-side
code, the object needs to have a default constructor and to
postpone initialization until the Init method is called. The
underlying C++ class must now have a default constructor,
although this constructor will only be called internally by the
server. This requirement might render the technique useless in
some domains (such as classes that make extensive use of C++
references as data members), but there are many C++ classes where
this technique could be applied without excessive reengineering. The approach shown above is
based on a two-phase construction, which has several drawbacks.
First, it is possible that the client will neglect to invoke the
second phase: For the Color class,
this may not be fatal. For many real-world classes, however,
using uninitialized objects could cause serious faults in a
program that can be very difficult to track down. To make diagnosing such
errors simpler, the object could add a data member that keeps
track of whether the object has been initialized and returns an
error if the object is used before proper initialization has been
performed: Performing this
bookkeeping requires additional per-object memory and per-method
processing that was not necessary in the original C++ class. Another potential cause
of errors would be for the client to call the initialization
method more than once: For the Color class,
this error is not fatal, but for many classes reinitialization
could be disastrous. The Init method could be extended to catch
this error at runtime: Again, this bookkeeping
was not necessary in the original C++ class. A solution that does
not require the object implementor to worry about uninitialized
objects is to expose a custom activation interface from the class
object. Often, COM class objects are viewed simply as the
necessary glue that allows COM to create instances of a class. It
turns out that class objects are fairly powerful programming
abstractions for representing the instanceless operations of your
COM class. Class objects can expose any custom interface they
choose, and the methods of these interfaces act as the COM
version of C++ static methods. Clients can bind any type of
pointer to a class object using the low-level API function
CoGetClassObject: Note that the
server-side registration function, CoRegisterClassObject, does
not require the class object to export Armed with an
understanding of class objects, it is now possible to enforce the
explicit initialization of objects by, instead of exporting
IClassFactory, exporting a custom activation interface that
requires the client to explicitly pass the necessary
initialization parameters: This interface would be
exposed from a distinct C++ class that would be used to create
the initial class object. The implementation of this class would
create new initialized instances of the class Color in its
CreateColor method: Since the class object
does not expose the IClassFactory interface, the CreateColor
method is the only way clients can create Color objects. This
means that the object implementor does not need to worry about
uninitialized instances since CreateColor properly initializes
every object. This also means that the C++ class does not need to
provide a default constructor. To use the custom
activation interface shown above, clients need to call
CoGetClassObject instead of CoCreateInstance (see Figure 1).
Beyond the semantic benefits of preventing uninitialized objects,
this approach also yields a big performance win if more than one
object is needed. Using the two-phase construction approach, if
four objects are needed, four calls to CoCreateInstance and four
calls to the Init method would be required, resulting in a total
of eight logical client-server round-trips: Figure 1 Using
CoGetClassObject Using the custom
activation interface IColorClass, only one call to
CoGetClassObject is needed, followed by four calls to the
CreateColor method, resulting in just six client-server
round-trips: In addition to
requiring fewer logical round-trips, the CreateColor method calls
will be more efficient than the calls to CoCreateInstance simply
because they do not have to pass through the client or
server-side SCMs. Using a custom
activation interface from C++ is fairly straightforward.
Accessing a custom activation interface from Visual Basic
presents more of a challenge. Visual Basic allows programmers to
call CoCreateInstance using the New keyword. Unfortunately,
Visual Basic does not offer a similar keyword for calling
CoGetClassObject. This is not a major obstacle, as Visual Basic
does offer access to
a far more powerful activation API that ultimately is a superset
of CoGetClassObject. This activation API is
MkParseDisplayName/BindToObject, and it is exposed to Visual
Basic programmers via the GetObject intrinsic function. MkParseDisplayName is
one of the least-appreciated API functions in all of COM.
MkParseDisplayName is a generic, extensible API function that
translates arbitrary text strings into monikers that can be used
to locate, find, or create the objects that they name. The Visual
Basic function GetObject calls MkParseDisplayName internally to
convert a string into a moniker. GetObject then calls the
resultant monikers BindToObject method to dereference the
moniker and locate the object named by the moniker. The code
shown in Figure 2 emulates the behavior of Visual
Basics GetObject. The actual GetObject function from Visual
Basic takes an optional second parameter that is not relevant to
the discussion at hand. Figure 2 Emulating
Visual Basic GetObject MkParseDisplayName acts
as the main entry point into the namespace of COM. All object
activation can be performed via MkParseDisplayName and
IMoniker::BindToObject. This namespace is extensible and allows
developers to integrate new object activation algorithms or
policies simply by implementing a custom moniker.
MkParseDisplayName determines the type of moniker to create based
on the prefix of the presented string. If the string begins with
a valid ProgID followed by a colon MkParseDisplayName
calls CLSIDFromProgID to map the ProgID (foo) onto the CLSID of
the moniker. (Remember, monikers are dynamically created COM
objects with CLSIDs stored in the registry, just like any other
COM class.) MkParseDisplayName then uses the IParseDisplayName
interface of the monikers class object to create a new
moniker. The monikers class object simply creates a new
moniker object based on the presented string. How this new
moniker uses the string to implement BindToObject is completely
under the control of the moniker implementor. The techniques used
by an implementation of BindToObject
are of no concern to the client. The client simply
calls BindToObject and uses the resultant interface pointer. This
separation of interface from implementation allows clients to use
a single uniform mechanism for activation that
dynamically selects the activation policy based on the
content of the string. One very important
moniker that is preinstalled as part of COM (as of Windows NT
4.0) is the class moniker. Class monikers keep a CLSID as their
state and use the clsid prefix: If this string is
passed to MkParseDisplayName, the clsid prefix is parsed as a
ProgID (which confusingly happens to map to the registry key
HKEY_CLASSES_ROOT\CLSID). The GUID that is found at the
corresponding CLSID subkey
is used to create the moniker. The GUID at HKEY_ At the time of this
writing (Windows NT® 4.0 Service Pack 2), the class
moniker does not use the COSERVERINFO that may be present in the
bind options. While there is no way
to get the current implementation of the class moniker to
redirect the activation request to another host machine through a
COSERVERINFO, the class moniker does support composition to its
left. If the class moniker is composed to the right of another
moniker, the class moniker expects the object named by the
moniker to its left to export the IClassActivator interface. When composed to the
left of another moniker, the class moniker uses the
IClassActivator::GetClassObject method to find the class object
instead of calling the API function CoGetClassObject directly
(see ActiveX/COM Q & A
class Color {
short m_red; short m_green; short m_blue;
public:
Color(short r, short g, short b)
: m_red(r), m_green(g), m_blue(b) {}
short Red(void) const { return m_red; }
short Green(void) const { return m_green; }
short Blue(void) const { return m_blue; }
};
interface IColor : IUnknown {
HRESULT Init([in] short r, [in] short g,
[in] short b);
[propget] HRESULT Red([out, retval] short *p);
[propget] HRESULT Green([out,retval] short *p);
[propget] HRESULT Blue([out, retval] short *p);
}
coclass Color {
interface IColor;
}
Function GetPink() as Long
Dim pink as IColor
Set pink = new Color
pink.Init 255, 100, 100
GetPink = RGB(pink.Red, pink.Green, pink.Blue)
End Function
COLORREF GetPink(void) {
IColor *pink = 0; short r, g, b;
HRESULT hr = CoCreateInstance(CLSID_Color, 0,
CLSCTX_ALL, IID_IColor,
(void**)&pink);
if (SUCCEEDED(hr)) {
pink->Init(255, 100, 100);
pink->get_Red(&r); pink->get_Green(&g);
pink->get_Blue(&b);
pink->Release();
}
return RGB(r,g,b);
}
Function GetPink() as Long
Dim pink as IColor
Set pink = new Color
' danger: using uninitialized object
GetPink = RGB(pink.Red, pink.Green, pink.Blue)
End Function
class Color : public IColor {
short m_red; short m_green; short m_blue;
bool m_bIsInited;
public:
Color(void) : m_bIsInited(false) {}
STDMETHODIMP Init(short r, short g, short b){
m_bIsInited=true; m_red=r; m_green=g; m_blue=b;
return S_OK;
}
STDMETHODIMP get_Red(short *ps) {
if (!m_bIsInited) return E_UNEXPECTED;
*ps = m_red; return S_OK;
}
: : :
};
Function GetPink() as Long
Dim pink as IColor
Set pink = new Color
pink.Init 0, 0, 0
' danger: reinitializing object
pink.Init 255, 100, 100
GetPink = RGB(pink.Red, pink.Green, pink.Blue)
End Function
STDMETHODIMP Color::Init(short r,short g,short b){
if (m_bIsInited)
return E_UNEXPECTED;
m_bIsInited=true; m_red=r; m_green=g; m_blue=b;
return S_OK;
}
HRESULT CoGetClassObject(REFCLSID rclsid,
DWORD dwClsCtx,
COSERVERINFO *pcsi,
REFIID riid,
void **ppv);
any interface other than IUnknown. HRESULT CoRegisterClassObject(REFCLSID rclsid,
IUnknown *pUnk,
DWORD dwClsCtx,
DWORD dwRegCls,
DWORD *pdwReg);
interface IColorClass : IUnknown {
HRESULT CreateColor([in] short r, [in] short g,
[in] short b,
[out, retval] IColor **ppc);
}
class ColorClass : public IColorClass {
STDMETHODIMP CreateColor(short r, short g,
short b, IColor **ppc){
if ((*ppc = new Color(r, g, b)) == 0)
return E_OUTOFMEMORY;
(*ppc)->AddRef();
return S_OK;
}
};
CoCreateInstance(CLSID_Color,...,&c1);
c1->Init(...);
CoCreateInstance(CLSID_Color,...,&c2);
c2->Init(...);
CoCreateInstance(CLSID_Color,...,&c3);
c3->Init(...);
CoCreateInstance(CLSID_Color,...,&c4);
c4->Init(...);

COLORREF GetPink(void) {
IColorClass *pcc = 0; short r, g, b;
HRESULT hr = CoGetClassObject(CLSID_Color, CLSCTX_ALL, 0,
IID_IColorClass,(void**)&pcc);
if (SUCCEEDED(hr)) {
IColor *pink = 0;
hr = pcc->CreateColor(255, 100, 100, &pink);
if (SUCCEEDED(hr)) {
pink->get_Red(&r); pink->get_Green(&g);
pink->get_Blue(&b);
pink->Release();
}
pcc->Release();
}
return RGB(r,g,b);
}
CoGetClassObject(CLSID_Color,..., &cco);
cco->CreateColor(..., &c1);
cco->CreateColor(..., &c2);
cco->CreateColor(..., &c3);
cco->CreateColor(..., &c4);
cco->Release();
IUnknown *GetObject(LPCOLESTR wszObjectName) {
IUnknown *pUnk = 0;
IBindCtx *pbc = 0;
HRESULT hr = CreateBindCtx(&pbc, 0);
if (SUCCEEDED(hr)) {
ULONG cch;
IMoniker *pmk = 0;
hr = MkParseDisplayName(pbc, wszObjectName,
&cch, &pmk);
if (SUCCEEDED(hr)) {
hr = pmk->BindToObject(pbc, 0, IID_IUnknown,
(void**)&pUnk);
pmk->Release();
}
pbc->Release();
}
return pUnk;
}
foo:ObjectName
clsid:12345678-1234-1234-1234-123412341234
CLASSES_ROOT\CLSID\CLSID corresponds to the system-provided class
moniker. The class monikers implementation of BindToObject
simply calls CoGetClassObject as shown in the following
pseudocode: HRESULT
CStdClassMoniker::BindToObject(IBindCtx *pbc,
IMoniker *pmkToLeft,
REFIID riid, void**ppv){
BIND_OPTS2 bo; bo.cbStruct = sizeof(bo);
pbc->GetBindOptions(&bo);
if (pmkToLeft == 0) {
// m_clsid is the guid parsed at init time
return CoGetClassObject(m_clsid, bo.dwClassContext,
0, riid, ppv);
}
// else deal with moniker to left
}
interface IClassActivator : IUnknown {
HRESULT GetClassObject(
[in] REFCLSID rclsid,
[in] DWORD dwClassContext,
[in] LCID locale,
[in] REFIID riid,
[out, iid_is(riid)] void **ppv);
}
Figure 3 Class Moniker BindToObject Pseudocode
HRESULT
CStdClassMoniker::BindToObject(IBindCtx *pbc, IMoniker *pmkToLeft,
REFIID riid, void**ppv){
BIND_OPTS2 bo; bo.cbStruct = sizeof(bo);
pbc->GetBindOptions(&bo);
if (pmkToLeft != 0) { // we are being composed
IClassActivator *pca = 0;
// bind the activation context to our left
hr = pmkToLeft->BindToObject(pbc, 0, IID_IClassActivator,
(void**)&pca):
if (SUCCEEDED(hr)) {
// ask the activator for a class object
hr = pca->GetClassObject(m_clsid, bo.dwClassContext,
bo.locale, riid, ppv);
pca->Release();
}
return hr;
}
else return CoGetClassObject(m_clsid, bo.dwClassContext,
0, riid, ppv);
}
Consider a custom moniker that names an object that can perform load balancing between a collection of host machines. If this moniker uses the prefix/ProgID "lb," the following display name describes a composite moniker that would activate a class object using the load-balancing moniker to give the class moniker an activation context:
lb:any!clsid:12341234-1234-1234-1234-123412341234
The runtime model of this composite moniker is shown in
Figure 4. If the client program were to load the string from the registry instead of hardcoding it into the source code, system administrators could inject a new host-selection policy simply by changing the prefix of the display name to use a different moniker type.
Figure 4 Composite Monikers
With an understanding of the class moniker in place, using the custom activation interface IColorClass is simple:
Function GetColor() As Long
Dim cc as IColorClass
Dim pink as IColor
Dim sz as String
sz ="clsid:12341234-1234-1234-1234-123412341234"
' bind to the class object for Color
Set cc = GetObject(sz)
' use class object to create a new instance
Set pink = cc.CreateColor(255, 100, 100)
GetColor = RGB(pink.Red, pink.Green, pink.Blue)
End Function
Using the class moniker, Visual Basic can access any interface that a class object exposes provided the interface uses only VARIANT-compatible parameter types. Ironically, this means that the IClassFactory interface is off-limits to programmers using Visual Basic.
One aspect of exporting
custom interfaces like IColorClass from class objects is that, if
you are building an out-of-process server, you must implement
IExternalConnection if you elect to not implement IClassFactory.
This is because of the strange relationship between class object
reference counting and server lifetime. Your class objects
implementation of IExternalConnection::AddConnection should
perform the equivalent of IClassFactory::LockServer(TRUE), and
your implementation of IExternalConnection::Re-
leaseConnection should perform the equivalent of
IClassFactory::LockServer(FALSE). This prevents your server from
terminating while there are outstanding proxies to your class
objects.
Implementing a custom class object in raw C++ is very straightforward since you are in complete control at all times. Some frameworks, such as MFC, make it extremely difficult to export a class object that is anything other than the standard implementation of IClassFactory or perhaps IClassFactory2. Fortunately, the Active Template Library (ATL) makes custom class objects trivial. The DECLARE_CLASSFACTORY_EX macro allows you to provide your own custom C++ class to use for your class object. Figure 5 shows the complete implementation of the Color class and class object in ATL. Ironically, while ATL makes it easy to use custom class objects, you cannot use ATLs CComObject family of classes if you dont provide a default constructor. As Figure 5 illustrates, this is only an inconvenience and not an insurmountable problem.
Figure 5 ColorClass ATL Implementation
////////////////////////////////////////////
//
// ATLColor.h - 1997, Don Box
//
// An ATL-based inplementation of IColor/IColorClass
//
#ifndef __COLOR_H_
#define __COLOR_H_
#include "resource.h" // main symbols
/////////////////////////////////////////////////////////////////////////////
// Color
class Color :
public CComObjectRootEx<CComMultiThreadModelNoCS>,
public IColor
{
BEGIN_COM_MAP(Color)
COM_INTERFACE_ENTRY(IColor)
END_COM_MAP()
short m_red; short m_green; short m_blue;
public:
// we need to explicitly lock/unlock module because CComObject<>
// will not work with classes that do not have default constructors
Color(short r, short g, short b)
: m_red(r), m_green(g), m_blue(b)
{
_Module.Lock();
}
~Color(void)
{
_Module.Unlock();
}
// IUnknown methods (needed due to CComObject-incompatibility)
STDMETHODIMP QueryInterface(REFIID iid, void ** ppvObject)
{return _InternalQueryInterface(iid, ppvObject);}
STDMETHODIMP_(ULONG) AddRef(void)
{return InternalAddRef();}
STDMETHODIMP_(ULONG) Release(void){
ULONG l = InternalRelease();
if (l == 0)
delete this;
return l;
}
// IColor methods
STDMETHODIMP get_Red(/*[out, retval]*/ short *pval)
{ *pval = m_red; return S_OK; }
STDMETHODIMP get_Green(/*[out, retval]*/ short *pval)
{ *pval = m_green; return S_OK; }
STDMETHODIMP get_Blue(/*[out, retval]*/ short *pval)
{ *pval = m_blue; return S_OK; }
// ColorClass will act as the class object for our class
class ColorClass :
public CComObjectRootEx<CComMultiThreadModelNoCS>,
public IColorClass,
public IExternalConnection
{
BEGIN_COM_MAP(ColorClass)
COM_INTERFACE_ENTRY(IColorClass)
COM_INTERFACE_ENTRY(IExternalConnection)
END_COM_MAP()
// IColorClass methods
STDMETHODIMP CreateColor(short r, short g, short b,
IColor **ppc) {
if ((*ppc = new Color(r, g, b)) == 0)
return E_OUTOFMEMORY;
(*ppc)->AddRef();
return S_OK;
}
// IExternalConnection methods
STDMETHODIMP_(DWORD) AddConnection(DWORD extconn, DWORD) {
if (extconn&EXTCONN_STRONG) _Module.Lock();
return 2;
}
STDMETHODIMP_(DWORD) ReleaseConnection(DWORD extconn, DWORD, BOOL) {
if (extconn&EXTCONN_STRONG) _Module.Unlock();
return 1;
}
};
// make ColorClass our class object C++ class
DECLARE_CLASSFACTORY_EX(ColorClass)
DECLARE_REGISTRY_RESOURCEID(IDR_COLOR)
static const CLSID& WINAPI GetObjectCLSID() {return CLSID_Color;}
static LPCTSTR WINAPI GetObjectDescription() {return NULL;}
typedef CComFailCreator<E_FAIL> _CreatorClass;
};
#endif //__COLOR_H_
QI want to implement a singleton object in COM. How should I do it?
ASingletons are typically used to limit the number of instances of a class to one. Singletons are useful for modeling generic services (such as time of day or scheduling) or any functionality that does not require a distinct state to be maintained for each client. Classic RPC is great for modeling such services, but most programmers prefer using COM due to its better tool and language integration and its potential for in-process execution. While COM does not have any explicit API support for singletons, there are several ways to go about implementing this common programming idiom.
To grasp the concept of singletons, it helps to start with a concrete example. Consider an object that supports getting the current time of day. Such an object would export an interface similar to the following:
interface ITimeOfDay : IUnknown {
HRESULT GetCurrentTimeOfDay([out, retval] DATE *p);
}
While it would not cause any semantic errors to export this interface from a normal multi-instance COM class, having each client call CoCreateInstance to create a new COM object would consume considerably more resources than having the same number of clients simply connect to one singleton object. The increased resource consumption is due to the fact that COM needs to manage the internal state for each unique COM identity that is exported (marshaled) from a process. Since this interface relies solely on temporal information and requires no per-object state to operate correctly, its a prime candidate for deployment as a singleton. This is semantically more appropriate and will also scale to thousands of clients better than an instance-based server, especially if most of the operations on the object are read-only and dont require locks.
Dim sz as String
Dim tod as ITimeOfDay
sz = "clsid:56785678-5678-5678-5678-567856785678"
' use class moniker to call CoGetClassObject
Set tod = GetObject(sz)
MsgBox tod.GetCurrentTime()
Because this client
uses moniker activation via MkParseDisplayName, it is possible to
load arbitrary
display names/monikers for the singleton at runtime.
This allows users or administrators to redirect where the time of
day is read from simply by composing the class moniker with a
moniker that identifies a machine or group of machines.
Figure 6 TimeOfDay
//
// TimeOfDay.h - 1997, Don Box
//
// A singleton implementation of ITimeOfDay
//
#ifndef __TOD_H_
#define __TOD_H_
class TimeOfDay :
public ITimeOfDay,
public IExternalConnection
{
public:
double m_offset;
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
if (riid == IID_IUnknown || riid == IID_ITimeOfDay)
*ppv = static_cast<ITimeOfDay*>(this);
else if (riid == IID_IExternalConnection)
*ppv = static_cast<IExternalConnection*>(this);
else
return (*ppv = 0), E_NOINTERFACE;
((IUnknown*)*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) AddRef(void)
{ return 2;}
STDMETHODIMP_(ULONG) Release(void)
{ return 1; }
// ITimeOfDay methods
STDMETHODIMP GetCurrentTimeOfDay(DATE *pval)
{
SYSTEMTIME st;
// convert system time to a date
GetLocalTime(&st);
SystemTimeToVariantTime(&st, pval);
// factor in timezone offsets
*pval += m_offset;
return S_OK;
}
// IExternalConnection methods
STDMETHODIMP_(DWORD) AddConnection(DWORD extconn, DWORD) {
extern void LockModule();// some module locking routine
if (extconn&EXTCONN_STRONG)
LockModule();
return 2;
}
STDMETHODIMP_(DWORD) ReleaseConnection(DWORD extconn, DWORD, BOOL) {
extern void UnlockModule();// some module unlocking routine
if (extconn&EXTCONN_STRONG)
UnlockModule();
return 1;
}
};
#endif
Another popular technique for implementing singletons is to use the class objects IClassFactory::CreateInstance method to return a pointer to a single global object in the server. Given the TimeOfDay class shown in Figure 6, this technique would require the following class factory method
STDMETHODIMP
CTODClassFactory::CreateInstance(IUnknown * puo,
REFIID riid,
void **ppv) {
*ppv = 0;
if (puo) return CLASS_E_NOAGGREGATION;
static TimeOfDay s_tod; // declare a singleton
return s_tod.QueryInterface(riid, ppv);
}
assuming the following client code:
Dim tod as ITimeOfDay
Set tod = new TimeOfDay
MsgBox tod.GetCurrentTime()
This technique, while commonly deployed in the field, has several deficiencies when compared to using the class object directly.
The first deficiency is that it seems semantically wrong for a method called CreateInstance to get an object instead of create an object. Second, because CoCreateInstance is a generic API function, it is impossible for the supporting COM runtime to provide any optimizations based on prior calls to CoCreateInstance on the same CLSID. Since the semantics of the IClassFactory::CreateInstance method assume that a new instance will be returned on each call, CoCreateInstance must return to the server for each activation request. In contrast, the semantics of CoGetClassObject default to that of a singleton. This means that the results of the first CoGetClassObject request are valid for any subsequent CoGetClassObject requests, which implies that future runtime environments could cache commonly used class objects to avoid excessive round-trips to the server without compromising the semantic integrity of the programming model. Also, client programs can safely do their own explicit caching of class object pointers based on their own usage patterns.
Another disadvantage to using CoCreateInstance explicitly to bind to singleton objects is that it is not possible to change the binding policy simply by replacing a single text string as was possible when the class moniker was used via GetObject or MkParseDisplayName. Beyond the load-balancing and remote activation possibilities mentioned previously, another advantage of monikers is that they allow activation of named objects.
To grasp why named objects are useful, consider the ITimeOfDay interface described previously. At any given moment, there is only one time of day at any given location. However, in various regions of the planet, the time of day is different due to the fact that most of the world refuses to recognize Pacific Standard Time as the official planet-wide time of day. While this temporal diversity makes communicating with most of the world somewhat inconvenient for people who live near the epicenter of the universe (Redondo Beach, Ca.), it does allow most of the worlds programmers to sleep when it is light and program when it is dark, thus keeping the wheels of progress properly lubricated.
Suppose that I want to extend my time of day server to support multiple time zones without requiring source code changes in my client programs. Had I exposed my object to the client via CoCreateInstance, there would be no way for me to discern what time zone the client wishes to query. One solution to this problem would be to add an explicit method to my instances that allows the client to set the desired time zone. However, since this time zone would need to be stored in the object as a data member, I would no longer be able to use a singleton. This means that my server would need to have thousands of extant objects in order to serve thousands of clients simultaneously. Besides the scalability limitations this type of solution poses, it also requires modifications to the client source code, which may not be possible in many situations.
The key to finding a palatable solution is to examine the problem domain. There are a finite number of time zones on Earth. There are even fewer in the United States (actually four, if you consider only the 48 contiguous states and ignore the fact that the state of Arizona and a handful of renegade Indianans choose to ignore daylight savings time). Assuming that my server needs to serve clients only in the United States, the server could export four singleton instances, one per time zone. This would keep the per-object overhead fairly low. All that is needed is a mechanism for the client to bind to the correct time zone.
Assuming that the original singleton solution was deployed as a class object and that clients bind using GetObject/MkParseDisplayName, this problem can be solved using Item monikers. Item monikers are simply text strings that name some subcomponent of a containing object. If I model my class object as such, I can use the item moniker to name the individual time zones as follows:
clsid:12341234-1234-1234-1234-123412341234:!Eastern
This display name maps to a composite moniker with a class moniker as the left component and the item moniker Eastern as the right component. When parsed, MkParseDisplayName will first parse the class moniker, then bind the class moniker (which starts the server) and ask the class object for the IParseDisplayName interface. My class objects IParseDisplayName::ParseDisplayName method then gets the remainder of the string ("!Eastern" in this example) to convert into the right component of the composite. Since my implementation will use Item monikers to name the time zone objects, the implementation of ParseDisplayName is as follows:
STDMETHODIMP
TODClass::ParseDisplayName(IBindCtx *pbc,
LPOLESTR pszDisplayName,
ULONG *pchEaten,
IMoniker **ppmkOut)
{
*pchEaten = wcslen(pszDisplayName);
return CreateItemMoniker(OLESTR("!"),
pszDisplayName,
ppmkOut);
}
At bind time, the
composite moniker will first try to bind the item moniker. The
item moniker binds itself by first binding the moniker to its
left (which is the class moniker in this case), requesting the
IOleItemContainer interface from my class object. In my class
objects IOleItemContainer::
GetObject method, I am presented with the string Eastern and must
map this name onto an instance. Assuming that my class object
maintains an array of four singleton objects, one per time zone,
this method could be implemented as follows:
STDMETHODIMP
TODClass::GetObject(LPOLESTR pszItem,
DWORD dwSpeedNeeded, IBindCtx *pbc,
REFIID riid, void **ppv)
{
if (!_wcsicmp(pszItem, OLESTR("pacific")))
return m_rgTimes[0].QueryInterface(riid, ppv);
else if (!_wcsicmp(pszItem, OLESTR("mountain")))
return m_rgTimes[1].QueryInterface(riid, ppv);
else if (!_wcsicmp(pszItem, OLESTR("central")))
return m_rgTimes[2].QueryInterface(riid, ppv);
else if (_wcsicmp(pszItem, OLESTR("eastern")))
return m_rgTimes[3].QueryInterface(riid, ppv);
*ppv = 0;
return MK_E_NOOBJECT;
}
Figures 7 and 8 show the complete implementation of this server. Figures 9 and 10 show a Visual Basic-based client application that displays the time in each available time zone.

Figure 7 Singleton TimeOfDay Server
Figure 8 Per-Time Zone TimeOfDay Singleton
TOD.idl
////////////////////////////////////////////
//
// TOD.idl - 1997, Don Box
//
// Interface definitions for TimeOfDay
//
[
uuid(8C54EFA0-B85F-11d0-8C3E-0080C73925BA),
object,
oleautomation
]
interface ITimeOfDay : IUnknown
{
import "oaidl.idl";
HRESULT GetCurrentTimeOfDay([out, retval] DATE *pval);
}
[
uuid(8C54EFA1-B85F-11d0-8C3E-0080C73925BA),
helpstring("Time Of Day Interfaces"),
lcid(0),
version(1.0)
]
library TimeOfDayLib
{
[
uuid(8C54EFA2-B85F-11d0-8C3E-0080C73925BA)
]
coclass TimeOfDay
{
interface ITimeOfDay;
}
}
TZ.cpp
////////////////////////////////////////////
//
// TZ.cpp - 1997, Don Box
//
// A multi-singleton implementation of ITimeOfDay
// that supports multiple time zones using composite
// monikers.
//
// Usage:
// Dim tod as ITimeOfDay
// Set tod = GetObject("clsid:8C54EFA2-B85F-11d0-8C3E-0080C73925BA:!Eastern")
// MsgBox tod.GetCurrentTimeOfDay()
//
#define _WIN32_WINNT 0x402
#include <windows.h>
#include "TOD.h"
#include "TOD_i.c"
#include "TimeOfDay.h"
class TODClass :
public IOleItemContainer,
public IExternalConnection,
public ITimeOfDay
{
// this object supports four time zones
TimeOfDay m_rgTimes[4];
public:
TODClass(void)
{
// initialize the offsets to adjust for timezone shifts
m_rgTimes[0].m_offset = 0;
m_rgTimes[1].m_offset = 1/24.0;
m_rgTimes[2].m_offset = 2/24.0;
m_rgTimes[3].m_offset = 3/24.0;
}
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void ** ppv)
{
if (riid == IID_IUnknown || riid == IID_ITimeOfDay)
*ppv = static_cast<ITimeOfDay*>(this);
else if (riid == IID_IExternalConnection)
*ppv = static_cast<IExternalConnection*>(this);
else if (riid == IID_IParseDisplayName)
*ppv = static_cast<IParseDisplayName*>(this);
else if (riid == IID_IOleContainer)
*ppv = static_cast<IOleContainer*>(this);
else if (riid == IID_IOleItemContainer)
*ppv = static_cast<IOleItemContainer*>(this);
else
return (*ppv = 0), E_NOINTERFACE;
((IUnknown*)*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) AddRef(void)
{ return 2;}
STDMETHODIMP_(ULONG) Release(void)
{ return 1; }
// ITimeOfDay methods
STDMETHODIMP GetCurrentTimeOfDay(DATE *pval)
{
// class object also implements ITimeOfDay (maps to
// pacific time)
return m_rgTimes[0].GetCurrentTimeOfDay(pval);
}
// IExternalConnection methods
STDMETHODIMP_(DWORD) AddConnection(DWORD extconn, DWORD) {
extern void LockModule();// some module locking routine
if (extconn&EXTCONN_STRONG)
LockModule();
return 2;
}
STDMETHODIMP_(DWORD) ReleaseConnection(DWORD extconn, DWORD, BOOL) {
extern void UnlockModule();// some module unlocking routine
if (extconn&EXTCONN_STRONG)
UnlockModule();
return 1;
}
// IParseDisplayName methods
STDMETHODIMP
ParseDisplayName( IBindCtx *pbc,
LPOLESTR pszDisplayName,
ULONG *pchEaten,
IMoniker **ppmkOut)
{
// parse string as an item moniker, stripping off the leading "!"
*pchEaten = wcslen(pszDisplayName);
return CreateItemMoniker(OLESTR("!"), pszDisplayName + 1, ppmkOut);
}
// IOleContainer methods
STDMETHODIMP
EnumObjects(DWORD grfFlags, IEnumUnknown **ppenum)
{
*ppenum = 0;
return E_NOTIMPL;
}
STDMETHODIMP
LockContainer(BOOL fLock)
{
return E_NOTIMPL;
}
// IOleContainer methods
STDMETHODIMP
GetObject(LPOLESTR pszItem, DWORD dwSpeedNeeded,
IBindCtx *pbc, REFIID riid, void **ppv)
{
if (_wcsicmp(pszItem, OLESTR("pacific")) == 0)
return m_rgTimes[0].QueryInterface(riid, ppv);
else if (_wcsicmp(pszItem, OLESTR("mountain")) == 0)
return m_rgTimes[1].QueryInterface(riid, ppv);
else if (_wcsicmp(pszItem, OLESTR("central")) == 0)
return m_rgTimes[2].QueryInterface(riid, ppv);
else if (_wcsicmp(pszItem, OLESTR("eastern")) == 0)
return m_rgTimes[3].QueryInterface(riid, ppv);
*ppv = 0;
return MK_E_NOOBJECT;
}
STDMETHODIMP
GetObjectStorage(LPOLESTR pszItem, IBindCtx *pbc, REFIID riid, void **ppv)
{
*ppv = 0;
return MK_E_NOSTORAGE;
}
STDMETHODIMP
IsRunning(LPOLESTR pszItem)
{
return E_NOTIMPL;
}
};
HANDLE g_heventDone = CreateEvent(0, TRUE, FALSE, 0);
void LockModule()
{
CoAddRefServerProcess();
}
void UnlockModule()
{
if (CoReleaseServerProcess() == 0)
SetEvent(g_heventDone);
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR szCmdParam, int)
{
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hr))
return hr;
if (strstr(szCmdParam, "/RegServer") == 0)
{
DWORD dwReg;
TODClass todClassObject;
hr = CoRegisterClassObject(CLSID_TimeOfDay,
(IExternalConnection*)&todClassObject,
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
&dwReg);
if (SUCCEEDED(hr))
{
WaitForSingleObject(g_heventDone, INFINITE);
CoRevokeClassObject(dwReg);
}
}
else
{
// self-registration code
char szFileName[MAX_PATH];
GetModuleFileNameA(0, szFileName, MAX_PATH);
OLECHAR wszFileName[MAX_PATH];
mbstowcs(wszFileName, szFileName, MAX_PATH);
ITypeLib *ptl = 0;
hr = LoadTypeLib(wszFileName, &ptl);
if (SUCCEEDED(hr))
{
hr = RegisterTypeLib(ptl, wszFileName, 0);
ptl->Release();
}
RegSetValueA(HKEY_CLASSES_ROOT, "CLSID\\{8C54EFA2-B85F-11d0-8C3E-0080C73925BA}", REG_SZ, "TimeOfDay Object", 23);
RegSetValueA(HKEY_CLASSES_ROOT, "CLSID\\{8C54EFA2-B85F-11d0-8C3E-0080C73925BA}\\LocalServer32", REG_SZ, szFileName, lstrlenA(szFileName));
}
CoUninitialize();
return hr;
}
Figure 9 Visual Basic Time Zone Client

Figure 10 Multi-Time Zone TimeOfDay Using Monikers
Form1.FRM
VERSION 5.00
Begin VB.Form Form1
Caption = "It's Moniker Time!"
ClientHeight = 2475
ClientLeft = 240
ClientTop = 1545
ClientWidth = 4275
LinkTopic = "Form1"
ScaleHeight = 2475
ScaleWidth = 4275
Begin VB.Timer Timer1
Interval = 1000
Left = 3480
Top = 360
End
Begin VB.Label Label5
Caption = "Label1"
BeginProperty Font
Name = "Arial"
Size = 12
Charset = 0
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 375
Left = 120
TabIndex = 4
Top = 2040
Width = 4000
End
Begin VB.Label Label4
Caption = "Label1"
BeginProperty Font
Name = "Arial"
Size = 12
Charset = 0
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 375
Left = 120
TabIndex = 3
Top = 1560
Width = 4000
End
Begin VB.Label Label3
Caption = "Label1"
BeginProperty Font
Name = "Arial"
Size = 12
Charset = 0
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 375
Left = 120
TabIndex = 2
Top = 1080
Width = 4000
End
Begin VB.Label Label2
Caption = "Label1"
BeginProperty Font
Name = "Arial"
Size = 12
Charset = 0
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 375
Left = 120
TabIndex = 1
Top = 600
Width = 4000
End
Begin VB.Label Label1
Caption = "Label1"
BeginProperty Font
Name = "Arial"
Size = 12
Charset = 0
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 375
Left = 120
TabIndex = 0
Top = 120
Width = 4000
End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Dim defaultTime As ITimeOfDay
Dim pacificTime As ITimeOfDay
Dim mountainTime As ITimeOfDay
Dim centralTime As ITimeOfDay
Dim easternTime As ITimeOfDay
Private Sub Form_Load()
Set defaultTime = GetObject("clsid:8C54EFA2-B85F-11d0-8C3E-0080C73925BA")
Set pacificTime = GetObject("clsid:8C54EFA2-B85F-11d0-8C3E-0080C73925BA:!pacific")
Set mountainTime = GetObject("clsid:8C54EFA2-B85F-11d0-8C3E-0080C73925BA:!mountain")
Set centralTime = GetObject("clsid:8C54EFA2-B85F-11d0-8C3E-0080C73925BA:!central")
Set easternTime = GetObject("clsid:8C54EFA2-B85F-11d0-8C3E-0080C73925BA:!eastern")
End Sub
Private Sub Timer1_Timer()
Label1.Caption = "Default Time: " & defaultTime.GetCurrentTimeOfDay & Chr(13)
Label2.Caption = "Pacific Time: " & pacificTime.GetCurrentTimeOfDay & Chr(13)
Label3.Caption = "Mountain Time: " & mountainTime.GetCurrentTimeOfDay & Chr(13)
Label4.Caption = "Central Time: " & centralTime.GetCurrentTimeOfDay & Chr(13)
Label5.Caption = "Eastern Time: " & easternTime.GetCurrentTimeOfDay & Chr(13)
End Sub
No discussion of singletons would be complete without addressing in-process servers. Mixing singletons and in-process servers is generally a bad idea for several reasons. First, it is impossible to have one instance per machine, as each client will load the server DLL independently and will have its own unique instance of the singleton, which ultimately results in a per-process singleton. Second, unless the CLSID for the singleton is marked TheadingModel=Free (which means its objects run in the MTA of the process) or has no threading model at all (which means its objects run in the first STA of the process), COM will allow direct access to the class object (and any singletons it exports) from more than one apartment. This generally breaks the identity model used by COMs remoting layer.
Multi-apartment access is especially bad news if your singletons have data members that are interface pointers. In ThreadingModel=Both or ThreadingModel=Apartment-based singletons, interface pointer data members belong to the apartment of the thread that initializes them. If a thread from another apartment calls a method that in turn invokes methods through these interface pointers, the results are undefined. Undefined behavior in programs went out of fashion with Windows® 3.1 and is now considered socially unacceptable programming style. While cross-thread marshaling could help in this situation somewhat, the complexity that this would add to the design of the object probably warrants just deploying singletons using ThreadingModel=Free or as out-of-process servers. It is likely that future versions of COM will provide better support for cross-apartment access to pointers. Check future installments of this column for more coverage of this topic as details become available.
Finally, if you are implementing a singleton object as an out-of-process server, be aware that if you dont explicitly configure your server to run as a distinguished login account, it will run using the login account of the activator. If more than one user tries to access your singleton on a given machine, the SCM will start multiple copies of your server process, resulting in a per-user singleton instead of a per-machine singleton. This severely limits the number of clients that can access your server, as spawning a process (and probably a window station) per object is extremely expensive. Figure 11 shows how to use DCOMCNFG to change the activating identity to that of a distinguished user account, ensuring that only one copy of the server process will be started.
Figure 11 Using DCOMCNFG
In this column I addressed using monikers as a generic, extensible object activation mechanism. The examples I used leveraged the concept of class objects to provide custom activation interfaces as well as to implement singletons. One moniker-related topic that I didnt address was the Running Object Table (ROT). The ROT is a facility provided by the SCM that allows instances to be associated with monikers. While the ROT is an integral part of the file moniker protocol and of some custom monikers, I was able to achieve the same effect by using the SCMs Class Table and the class moniker. If you are interested in more information on monikers, surf over to http://www.develop.com/dbox/com/mk and download the source code.
Have a question about programming with ActiveX or COM? Send it to Don Box at dbox@develop.com or http://www.develop.com/dbox.
169; 1997 Microsoft Corporation. All rights reserved. Terms of use