This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


November 1999

Microsoft Systems Journal Homepage

Wicked Code

Code for this article: Nov99Wicked.exe (94KB)

Jeff Prosise is the author of Programming Windows 95 with MFC (Microsoft Press, 1996). He also teaches Visual C++, MFC and COM programming seminars. For more information, visit http://www.solsem.com.

As COM matures and becomes less of a novelty and more of a mainstream programming technology, the body of COM programming literature grows exponentially. So does the volume of available sample code. But no matter how expansive these resources become, the subject of monikers—what they are, how they work, and when they should be used—remains a mystery to the average COM developer.
     One reason monikers seem so abstruse to rank-and-file programmers is that much of the published documentation discusses monikers in the context of OLE. Ironically, OLE is a relatively minor subset of the larger problem set that monikers was designed to solve. Predefined moniker types or system monikers, such as URL monikers and file monikers, enable COM developers to download resources from URLs and identify object instances based on the names of the files in which they store their persistent data. Custom monikers can be combined with MkParseDisplayName to extend the COM namespace. One use for custom monikers is to build a generic object-naming facility that allows clients to identify and connect to existing object instances.
     Which brings me to the subject of this month's column. Recently I spoke to a fellow programmer who's incorporating COM into his company's development plans in a big way, and he posed the following scenario. Suppose you write a COM class to monitor a hardware device and provide information about the device's status to interested clients. Furthermore, suppose that the target machine contains several such devices, and that each device will be monitored by a unique instance of said COM class. When a client starts up, how can it connect to the object associated with a particular device if all it knows is the device name?
     As usual, there are many ways to attack this problem. One solution is a custom moniker that permits object instances to be named—and connected to—using the name of the corresponding device. Since monikers are often defined as objects that are used to name other objects, you'd think that sample code would be easy to come by. It's not. So I wrote a custom moniker I call the instance moniker that is essentially a general-purpose object-naming mechanism. It can be used to identify object instances by device name or any other name, and to retrieve interface pointers to running objects. Besides being useful in its own right, the instance moniker is a wonderful example of how custom monikers are written and how they're used to solve a classic COM programming problem.

Instance Monikers at Work
     Before you dive into the source code for the instance moniker, it's instructive to see instance monikers at work. First, you'll need to download the following files, included in this month's code archive:

  • InstMon.zip, which contains the source code for the instance moniker
  • DevMonServer.zip, which houses a device monitor object that uses instance monikers to identify object instances
  • DevMonClient.zip, which contains an MFC client that uses instance monikers to identify and connect to running device monitor objects
     After you download the zip files and expand them, use Visual C++® 6.0 to build the resulting projects, which are named InstMon, DevMonServer, and DevMonClient. In addition to producing the needed binaries, the build process automatically registers instance monikers and device monitor objects on your system. It also builds and registers DevMonServerps.dll, the proxy/stub DLL that provides marshaling support for the IDeviceMonitor interface exported by device monitor objects.
     Once the builds are complete, run two instances of DevMonClient.exe side by side. Type a fictitious device name such as USB001 into the Device Name boxes, and then click the Start buttons. Given identical device names, both clients will connect to the same device monitor object. You can prove it by clicking the Get Device ID buttons. Both should display the same device ID, indicating that both are connected to the same device monitor object (see Figure 1). Start a third client and enter a different device name, however, and you'll be greeted with another device ID.
Figure 1 Two Clients Connected to the Same Object
     Figure 1 Two Clients Connected to the Same Object

     The secret to its ability to connect to a running device monitor object is that before it creates a new object, DevMonClient attempts to connect to an existing object by binding to it through an instance moniker created from the name typed into the Device Name box. Here's the pertinent source code from DevMonClientDlg.cpp:
hr = pMoniker->BindToObject (pBindCtx, NULL,   
     IID_IDeviceMonitor,
     (void**) &m_pDeviceMonitor);
     If BindToObject fails, DevMonClient creates a brand new object instance. It then connects the object to a device by calling IDeviceMonitor::StartMonitoring and passing in the device name found in the Device Name box:
hr = ::CoCreateInstance (CLSID_DeviceMonitor, NULL, 
     CLSCTX_SERVER,
     IID_IDeviceMonitor, (void**) &m_pDeviceMonitor);
 •
 •
 •
 hr = m_pDeviceMonitor->StartMonitoring (pszDeviceName);
     The device monitor object's StartMonitoring method doesn't really connect to a device; it only pretends to. But it does something else that's no less important: it creates an instance moniker that wraps the device name in pszDeviceName, and it registers that moniker in the running object table. Here's an excerpt from DeviceMonitor.cpp:
IRunningObjectTable* prot;
 hr = pBindCtx->GetRunningObjectTable (&prot);
 
 if (SUCCEEDED (hr)) {
     hr = prot->Register (0, (IUnknown*) this, pMoniker,
         &m_dwROTID);
     prot->Release ();
 }
     Now that the device monitor object is up and running and registered in the running object table, the next client that binds through a moniker encapsulating the object's device name will succeed in retrieving an interface pointer.
     That's a high-level explanation of how instance monikers work. The rest is just detail.

Creating Instance Monikers
     So instance monikers provide an easy and effective means for connecting a client to a running object instance given only an instance name. In the previous example, a device's name doubled as the instance name for the corresponding device monitor object.
     One crucial detail that I haven't addressed is how instance monikers are created in the first place. The answer is the COM MkParseDisplayName function, which resolves an input string of the form progid:name by instantiating the class object for the object whose ProgID is progid and passing the input string to the class object by calling IParseDisplayName::ParseDisplayName. ParseDisplayName's job is to create a moniker that encapsulates the name portion of the input string and return an IMoniker interface pointer. That interface pointer is returned to the client by MkParseDisplayName. Once a client has an IMoniker interface pointer, it can bind to the object that the moniker names with IMoniker::BindToObject or place the moniker in the running object table.
     My instance moniker implementation registers itself using the ProgID called Instance. Consequently, the following statements create an instance moniker that encapsulates the instance name USB001:

IBindCtx* pBindCtx;
 HRESULT hr = CreateBindCtx (0, &pBindCtx);
 
 if (SUCCEEDED (hr)) {
     ULONG ulEaten;
     IMoniker* pMoniker;
     hr = MkParseDisplayName (pBindCtx, 
        OLESTR ("Instance:USB001"),
         &ulEaten, &pMoniker);
 
     if (SUCCEEDED (hr)) {
         // It worked!
     }
     pBindCtx->Release ();
 }
This is precisely the technique used by DevMonClient and DevMonServer to create instance monikers. Both formulate the string passed to MkParseDisplayName on the fly. To create an input string of the form Instance:devicename, device monitor clients use the device name found in the Device Name box. Device monitor objects use the device name referenced by IDeviceMonitor::StartMonitoring's pszDeviceName parameter.
     Note that an inherent race condition occurs if two clients call BindToObject at about the same time. If the timing is right (or wrong, depending on your point of view), it's entirely possible that both clients will succeed in creating new object instances, even if the monikers on which they called BindToObject are identical. If that's a concern, you'll need to institute some sort of synchronization mechanism in your clients to ensure that they don't call BindToObject at the same time. That mechanism could be as simple as an interprocess mutex that each client claims before calling BindToObject.

CInstanceMoniker and CMonikerFactory
     The CInstanceMoniker class, which you can find in InstanceMoniker.h in this month's code distribution, is the ATL COM class from which instance monikers are created. A moniker is a COM object that implements the IMoniker interface. If the moniker is placed in the COM running object table, it should implement the IROTData interface, too. CInstanceMoniker implements both. IMoniker is a poorly factored interface that contains 23 methods, if you include the three IUnknown methods that are common to every COM interface. CInstanceMoniker implements seven of those methods—GetClassID, BindToObject, Reduce, IsEqual, IsRunning, GetDisplayName, and IsSystemMoniker—in addition to the lone IROTData method, GetComparisonData. The other methods return E_NOTIMPL.
     The heart of the instance moniker is CInstanceMoniker's implementation of BindToObject. BindToObject searches the running object table for a like moniker and returns the interface pointer associated with that moniker if the search is successful. The search is performed by enumerating all the monikers currently registered in the running object table and calling each moniker's IsEqual method to compare it to the moniker whose BindToObject method was called. If a match is found—that is, if the two monikers have the same instance names—BindToObject uses IRunningObjectTable::GetObject to retrieve the corresponding interface pointer from the running object table. That interface pointer refers to a device monitor object and is used to query the object for the interface specified in the call to BindToObject.
     One of the least intuitive aspects of implementing a custom moniker in ATL is implementing the moniker's class object. In order to work with MkParseDisplayName, a moniker's class object must implement IParseDisplayName in lieu of (or in addition to) a standard activation interface such as IClassFactory. ATL includes four predefined class object classes in the form of CComClassFactory et al., but none of them is suitable for monikers because they don't implement IParseDisplayName.
     Therefore, I wrote a custom class object class named CMonikerFactory to dispense instances of CInstanceMoniker. You can see for yourself in this month's code distribution how CMonikerFactory's implementation of IParseDisplayName::ParseDisplayName parses the instance name from the input string, creates a CInstanceMoniker object, and uses CInstanceMoniker::SetName to pass the instance name to the newly formed moniker.
     In CInstanceMoniker's class declaration, the statement

DECLARE_CLASSFACTORY_EX (CMonikerFactory<CInstanceMoniker>)
directs ATL to use CMonikerFactory as the class object class for CInstanceMoniker objects. Because CMonikerFactory is a template class, you can use it with other moniker classes if you'd like by changing the class name passed to it as a template parameter and modifying its implementation of ParseDisplayName.

The CDeviceMonitor Class
     Device monitor objects are represented by instances of an ATL class called CDeviceMonitor (see Figure 2). It is CDeviceMonitor's implementation of IDeviceMonitor::StartMonitoring that registers device monitor objects in the running object table. StartMonitoring forms an input string by concatenating the device name in its parameter list to the string "Instance:". It then passes the resulting string to MkParseDisplayName and gets an IMoniker interface pointer in return. Finally, it places the moniker in the running object table and associates it with the object's own IUnknown interface pointer. This sets the stage for clients to bind to the device monitor object by calling BindToObject later on.

Device Monitor Clients
     DevMonClientDlg.cpp (also found in this month's code distribution) contains the source code for the MFC class CDevMonClientDlg, which represents the window that appears on the screen when you run DevMonClient.exe. CDevMonClientDlg also contains the logic that connects to a running object instance and, failing that, creates and initializes a new object instance. The crucial code is CDevMonClientDlg::OnStart, which is called when the application's Start button is clicked. OnStart forms an input string from "Instance:" and the device name that the user typed into the Device Name box, and then converts the string into an instance moniker with MkParseDisplayName. But rather than place the moniker in the running object table, OnStart calls BindToObject on the moniker to bind to a running device monitor object should a compatible instance already exist.
     Instance monikers are terrific devices for identifying running object instances. They also provide the perfect illustration of how monikers are used to implement a generic naming mechanism. But instance monikers suffer from one potentially crippling limitation: they can't bind to objects running on remote machines. The primary reason for this limitation is that the running object table can't be accessed on remote machines without some other object present to serve as an intermediary. That's why real-world distributed systems are more likely to use custom class objects that allow objects to be identified (and connected to) by name than they are to use custom monikers, or to rely on designs that don't require clients to share object instances. For an example of a custom class object that supports instance naming, see the February 1999 Wicked Code column in MSJ.

Drop Me a Line
     Are there tough Win32®, MFC, COM, or MTS programming questions you'd like answered? If so, drop me a note at jeffpro@msn.com. Please include "Wicked Code" in the message title. I regret that time doesn't permit me to respond to individual questions, but rest assured that each and every suggestion will be considered for inclusion in a future column.

Have a tricky issue dealing with Windows? Send your questions via email to Jeff Prosise: JeffPro@msn.com


From the November 1999 issue of Microsoft Systems Journal.