Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > July 1997
July 1997


Code for this article: activexcode0797.exe (6KB)
Don Box is a co-founder of DevelopMentor where he manages the COM curriculum. Don is currently breathing deep sighs of relief as his new book, Essential COM (Addison-Wesley), is finally complete. Don can be reached at http://www.develop.com/dbox/default.asp.

Q I 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?

A CoCreateInstance 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:


 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; }
 };
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 object's constructor:

 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;
 }
This implies that Visual Basic clients would write the code shown below,

 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
which translates to the following C++ code:

 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);
 }
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:

 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
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:

 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;
   }
    :  :  :
 };
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:

 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
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:

 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;
 }
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:

 HRESULT CoGetClassObject(REFCLSID rclsid,
                          DWORD dwClsCtx,
                          COSERVERINFO *pcsi,
                          REFIID riid,
                          void **ppv);
Note that the server-side registration function, CoRegisterClassObject, does not require the class object to export any interface other than IUnknown.

 HRESULT CoRegisterClassObject(REFCLSID rclsid,
                               IUnknown *pUnk,
                               DWORD dwClsCtx,
                               DWORD dwRegCls,
                               DWORD *pdwReg);
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:

 interface IColorClass : IUnknown {
   HRESULT CreateColor([in] short r, [in] short g,
                       [in] short b, 
                       [out, retval] IColor **ppc);
 }
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:

 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;
   }
 };
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:

 CoCreateInstance(CLSID_Color,...,&c1);
 c1->Init(...);
 CoCreateInstance(CLSID_Color,...,&c2);
 c2->Init(...);
 CoCreateInstance(CLSID_Color,...,&c3);
 c3->Init(...);
 CoCreateInstance(CLSID_Color,...,&c4);
 c4->Init(...);
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:

 CoGetClassObject(CLSID_Color,..., &cco);
 cco->CreateColor(..., &c1);
 cco->CreateColor(..., &c2);
 cco->CreateColor(..., &c3);
 cco->CreateColor(..., &c4);
 cco->Release();
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 moniker's 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 Basic's GetObject. The actual GetObject function from Visual Basic takes an optional second parameter that is not relevant to the discussion at hand.
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

 foo:ObjectName
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 moniker's class object to create a new moniker. The moniker's 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:

 clsid:12345678-1234-1234-1234-123412341234
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_ CLASSES_ROOT\CLSID\CLSID corresponds to the system-provided class moniker. The class moniker's 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
 }
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.

 interface IClassActivator : IUnknown {
     HRESULT GetClassObject(
         [in] REFCLSID rclsid,
         [in] DWORD dwClassContext,
         [in] LCID locale,
         [in] REFIID riid,
         [out, iid_is(riid)] void **ppv);
 }
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 Figure 3 ). This extensibility allows arbitrary machine selection algorithms to be composed to the left of the class moniker.
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
  1. Client calls MkParseDisplayName to create a composite moniker
  2. Client calls BindToObject on composite
  3. Composite calls BindToObject on class moniker passing load balance moniker as left component
  4. Class Moniker calls BindToObject on moniker to left
  5. Class Moniker calls GetClassObject on bound object and returns class object to client
  6. Client calls Color Class Object's CreateColor method to create an instance
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 object's 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 ATL's CComObject family of classes if you don't provide a default constructor. As Figure 5 illustrates, this is only an inconvenience and not an insurmountable problem.

Q I want to implement a singleton object in COM. How should I do it?

A Singletons 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, it's 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 don't require locks.
Perhaps the most direct technique for implementing singletons is to simply use the class object as the singleton. As I mentioned earlier, class objects can expose arbitrary COM interfaces and can be accessed from any language that supports either CoGetClassObject or MkParseDisplayName. Figure 6 shows a simple C++ class that implements the ITimeOfDay and IExternalConnection interfaces. Given this class, writing a singleton server is fairly straightforward:

 void main(void) {
   CoInitializeEx(0, COINIT_MULTITHREADED);
   TimeOfDay tod;
   DWORD dwReg;
   CoRegisterClassObject(CLSID_TimeOfDay,
              (ITimeOfDay*)&tod,
              CLSCTX_LOCAL_SERVER,
              REGCLS_MULTIPLEUSE,
              &dwReg);
   WaitForSingleObject(g_heventDone, INFINITE);
   CoRevokeClassObject(dwReg);
   CoUninitialize();
 }
Clients can use this server simply by using its CLSID to bind to the class object:

 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.
Another popular technique for implementing singletons is to use the class object's 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 world's 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 object's 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 object's 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;
 }
Figure 7 and Figure 8 show the complete implementation of this server. Figure 9 and Figure 10 show a Visual Basic-based client application that displays the time in each available time zone.
Figure 7 Singleton TimeOfDay Server
Figure 7 Singleton TimeOfDay Server

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 COM's remoting layer.
Figure 9 Visual Basic Time Zone Client
Figure 9 Visual Basic Time Zone Client

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 don't 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
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 didn't 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 SCM's Class Table and the class moniker. If you are interested in more information on monikers, surf over to http://www.develop.com/dbox/default.asp.

Have a question about programming with ActiveX or COM? Send your questions via email to Don Box at dbox@develop.com or http://www.develop.com/dbox/default.asp

From the July 1997 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe .

© 1997 Microsoft Corporation. All rights reserved.
Terms of Use
.

© 2017 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft