Share via


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.

MSDN Magazine

Wicked Code
Eight Lessons from the COM School of Hard Knocks
Jeff Prosise
I

n my line of work, I get to see a lot of COM code written by lots of different developers. I'm forever amazed by the creative ways COM is put to work, and the clever code written to make COM do things that Microsoft probably never anticipated. Still, I can't overcome my dismay at seeing certain mistakes made over and over again. A disproportionate share of those mistakes have to do with threading and security, which are two of the least well-documented areas of COM. They're also the two that are most likely to reach out and grab you if you don't plot your path through them carefully.
      This month's Wicked Code column is a little different than most. It doesn't present a cool piece of code that you can use in your own apps. Rather, it's about the right and wrong ways to implement COM-based applications. It's about lessons learned from the school of hard knocks, and about how you can avoid falling into traps that have snared more than a few COM developers.
      In the pages that follow, you'll read eight accounts of programmers who have learned these lessons the hard way. Every story is true, but names have been withheld to protect the innocent. It is my intention that these true COM tales will prevent you from repeating some of the mistakes that other COM programmers have made. It's also possible that they'll identify potential trouble spots in code you've already written. Whatever the case, I think you'll find that they make interesting bedtime reading.

Always Call CoInitialize(Ex)

      A few months ago, I received an e-mail message from a friend at a prominent hardware company. His company had written a fairly sophisticated COM-based application that employed a number of in-proc and local (out-of-process) COM components. At startup, the application created COM objects to serve various client threads running in multithreaded apartments (MTAs). The objects were also consigned to MTAs, which meant that interface pointers could be freely exchanged among client threads. In testing, my friend discovered that everything worked fine until the application was ready to shut down. Then, for no apparent reason, calls to Release (calls that had to be made in order to properly free the interface pointers that the clients were holding) were locking up. His question: "What on earth is going wrong?"
      The answer, as it turned out, was quite simple. The application's developers had done everything right with one very important exception: they weren't calling CoInitialize or CoInitializeEx in all of their client threads. One of the fundamental rules of modern-day COM is that every thread that uses COM should first initialize COM by calling either CoInitialize or CoInitializeEx. There are no exemptions to this rule. Among other things, CoInitialize(Ex) places a thread inside an apartment and initializes important per-thread state information that is required for COM to operate properly. Failure to call CoInitialize(Ex) typically manifests itself early in the application's lifetime in the form of failed COM API functions, most commonly activation requests. But sometimes the problems are more insidious and don't manifest themselves until it's too late, as in the case of calls to Release that disappear and never return. Once the dev team added CoInitialize(Ex) calls to all the threads that touched COM, their problems went away.
      Ironically, Microsoft is one of the reasons that COM programmers sometimes don't call CoInitialize(Ex). The Microsoft® Knowledge Base contains documents stating that calls to CoInitialize(Ex) aren't strictly necessary for MTA-based threads (see article Q150777 for an example). Yes, there are cases in which you can get away with skipping CoInitialize(Ex). No, you shouldn't do it unless you know what you're doing and can be absolutely sure that you'll suffer no debilitating effects. It's never harmful to call CoInitialize(Ex), so my advice to COM programmers is to always call it from any thread that does anything whatsoever with COM.

Don't Pass Raw Interface Pointers Between Threads

      One of the first COM projects I ever consulted on involved a distributed application comprising about 100,000 lines of code, written by a large software company on the West Coast. The application created dozens of COM objects on various machines and called into those objects from background threads launched by client processes. The development team was stymied by calls that would disappear into never-never land or simply fail for no obvious reason. One of the most egregious symptoms that they demonstrated for me was that following a call that failed to return, starting other COM-enabled applications (including Microsoft Paint, of all things) on the same machine would frequently induce those applications to lock up too.
      An examination of their code revealed that they had broken one of the fundamental laws of COM concurrency, which says that if one thread wants to share an interface pointer with another thread, it should first marshal the interface pointer. Marshaling an interface pointer enables COM to create a new proxy if necessary (and a new channel object, pairing the proxy with a stub) to permit callouts from another apartment. Passing a raw interface pointer (a 32-bit address in memory) to another thread without marshaling it bypasses COM's concurrency mechanism, and can produce all sorts of undesirable behavior if the sending and receiving threads reside in different apartments. (In Windows® 2000, because two objects can share an apartment but reside in different contexts, it can even get you in trouble if the threads are in the same apartment.) A typical symptom involves calls that fail and return RPC_E_WRONG_THREAD_ERROR.
      Windows NT® 4.0 and higher make it easy to marshal interface pointers between threads with a pair of API functions named CoMarshalInterThreadInterfaceInStream and CoGetInterfaceAndReleaseStream. Let's say one thread in your application (thread A) has created a COM object and received an IFoo interface pointer in return, and that another thread in the same process (thread B) wants to place calls to that object. In preparation for passing the interface pointer to thread B, thread A should marshal the interface pointer like this:

  CoMarshalInterThreadInterfaceInStream (IID_IFoo, pFoo, &pStream);
  

 

Once CoMarshalInterThreadInterfaceInStream has returned, thread B can safely unmarshal the interface pointer:

  IFoo* pFoo;
  
CoGetInterfaceAndReleaseStream (pStream, IID_IFoo, (void**) &pFoo);

 

      In these examples, pFoo is an IFoo interface pointer and pStream is an IStream interface pointer. COM initializes the IStream interface pointer in the call to CoMarshalInterThreadInterfaceInStream, and then uses and releases that interface pointer inside CoGetInterfaceAndReleaseStream. In the real world, you'll typically use an event or other synchronization primitive to coordinate the actions of the two threadsâ€"for example, to let thread B know that the interface pointer is ready for unmarshaling.
      Note that it's never harmful to marshal an interface pointer in this way because COM is smart enough not to marshal (or remarshal) the pointer if marshaling isn't necessary. Do this anytime you pass interface pointers between threads and life with COM will be happier.
      If calling CoMarshalInterThreadInterfaceInStream and CoGetInterfaceAndReleaseStream seems like too much trouble, you can also transfer interface pointers between threads by placing them in the Global Interface Table (GIT) and having other threads go there to retrieve them. Interface pointers retrieved from the GIT are automatically marshaled when they're retrieved. For more information, see the documentation on IGlobalInterfaceTable. Note that the GIT is only present in Windows NT 4.0 Service Pack 4 and higher.

STA Threads Need Message Loops

      The application described in the previous section suffered another fatal flaw, too. See if you can figure out what it is.
      This particular application happened to be written in MFC. At startup, it used MFC's AfxBeginThread function to launch a series of worker threads. Each worker thread called either CoInitialize or AfxOleInitâ€"the MFC analog to CoInitializeâ€"to initialize COM. Some of the worker threads then called CoCreateInstance to create COM objects, and followed up by marshaling the returned interface pointers to the other worker threads. Calls placed to these objects from the threads that created them worked just fine, but calls placed from the other threads never returned. Do you know why?
      If you guessed that the problem has to do with message loops (or lack thereof), you're exactly right. Here's the deal. When a thread calls CoInitialize or AfxOleInit, it's placed in a single-threaded apartment (STA). When COM creates an STA, it creates a hidden window to go with it. Method calls destined for objects in an STA are converted into messages and placed in the message queue of the window associated with that STA. When the thread running in that STA retrieves a message representing a method call, the hidden window's window procedure converts the message back into a method call. COM uses STAs to perform call serialization. An object in an STA can't possibly receive more than one call at a time because each call is transferred to the one and only thread running in the object's apartment.
      So what happens if an STA-based thread doesn't process messages? What if it doesn't have a message loop? Interapartment method calls destined for objects in that STA will never return; they'll languish forever in the message queue. MFC worker threads don't have message loops, so MFC worker threads and STAs are a bad mix if the objects hosted in those STAs intend to receive method calls from clients in other apartments.
      What's the moral of this story? STA threads need message loops unless you're confident that they won't be hosting objects that are called from other threads. A message loop can be as simple as this:

  MSG msg;
  
while (GetMessage (&msg, 0, 0, 0))
DispatchMessage (&msg);

 

      The alternative is to move COM threads to the MTA (or in Windows 2000, the neutral-threaded apartment, or NTA), where there are no message queue dependencies.

Apartment Model Objects Must Protect Shared Data

      Another common malady that afflicts COM developers has to do with in-proc objects that are marked ThreadingModel=Apartment. This designation tells COM that instances of the object must only be created in STAs. It also frees COM to place those object instances in any of the host process's STAs.
      Suppose a client application has five STA threads and each thread uses CoCreateInstance to create an instance of the same object. If the threads are STA-based and the object is marked ThreadingModel=Apartment, then the five object instances will be created in their creators' STAs. Because each object instance runs on the thread that occupies its STA, all five object instances can run in parallel.
      So far, so good. Now think about what happens if those object instances share data. Because the objects are executing on concurrent threads, two or more of them might attempt to access the same data at the same time. Unless all of those accesses are read accesses, that's a recipe for disaster. Problems probably won't become evident quickly; they'll take the form of errors that are extremely timing-dependent and therefore difficult to diagnose and reproduce. That's why ThreadingModel=Apartment objects should include code that synchronizes access to shared data unless you know beyond a shadow of a doubt that the objects' clients won't perform overlapping calls to the methods that perform the accesses.
      The problem is that too many COM developers believe that ThreadingModel=Apartment absolves them from the need to write thread-safe code. It doesn'tâ€"at least not entirely. ThreadingModel=Apartment doesn't mean an object has to be completely thread-safe, but it does represent a promise to COM that accesses to data shared by two or more instances of the object (or to data shared by instances of this object and other objects, for that matter) are performed in a thread-safe manner. It's up to you, the object implementor, to provide that thread safety. Shared data comes in many shapes and sizes, but most often it comes in the form of global variables, static member variables in C++ classes, and static variables declared within a function. Even statements as innocent as these can be trouble in an STA:

  static int nCallCount = 0;
  
nCallCount++;

 

      Because all instances of this object will share one instance of nCallCount, the proper way to code these statements is as follows:

  static int nCallCount = 0;
  
InterlockIncrement (&nCallCount);

 

      To wit: use critical sections, interlocked functions, or whatever means you want, but don't forget to synchronize accesses to data shared by STA-based objects!

Beware of Launching User

      Here's another problem that has bedeviled many a COM developer. Last spring, a cry for help reached me from a company whose developers had used COM to build a distributed application that featured client processes running on network workstations connecting to a singleton object on a remote server. During testing, they encountered some rather strange behavior. In one test scenario, the clients' calls to CoCreateInstanceEx connected them to the singleton just fine. In another, the same calls to CoCreateInstanceEx yielded multiple object instances and multiple server processes, effectively crippling the application by preventing the clients from connecting to the same object instance. In both cases, the hardware and software were exactly the same.
      The problem turned out to be security-related. When the COM Service Control Manager (SCM), which handles the remoting of activation requests, launches a process on another machine, it assigns that process an identity. Unless otherwise specified, the identity it chooses is that of the launching user. In other words, the server process is assigned the same identity as the client process that launched it. In this situation, if Bob logs in on machine A and uses CoCreateInstanceEx to connect to a singleton object on machine B, and if Alice does the same from machine C, two different server processes will be launched (in two different WinStations, no less), effectively nullifying the clients' ability to connect to a shared object instance using singleton semantics.
      The reason the two test scenarios produced wildly varying results was that in one scenario (the one that worked), all the test personnel were logged in as the same person using a special account set up just for testing. In the other scenario, the testers logged in using their normal domain user accounts. When two or more client processes have the same identity, they can successfully connect to a server process configured to assume the identity of the launching user. But if the clients have different identities, the SCM uses multiple server processesâ€"one per unique client identityâ€"to separate the identities assigned to different object instances.

Figure 1 User Accounts in DCOMCNFG
Figure 1 User Accounts in DCOMCNFG

      Once the problem was identified, the solution was simple: configure the COM server to use a specific user account rather than assume the identity of the launching user. One way to accomplish this is to run DCOMCNFGâ€"the Microsoft DCOM configuration toolâ€"on the server machine and change "The launching user" to "This user" (see Figure 1). If you'd prefer to make the change programmatically (perhaps from a setup program), add a RunAs value to the COM server's entry in the HKEY_CLASSES_ROOT\AppID section of the registry on the host machine (see Figure 2). You'll also need to use LsaStorePrivateData to store the password for the RunAs account as an LSA secret, and LsaAddAccountRights to ensure that the account has "Logon as batch job" rights. (For an example of how to do both, see the DCOMPERM sample in the Platform SDK. Pay special attention to the functions named SetRunAsPassword and SetAccountRights.)

DCOM is Firewall-unfriendly

      A popular question on the features and capabilities of DCOM is: "Does it work over the Internet?" DCOM works just fine over the Internet as long as it's configured to use TCP or UDP, and your servers are configured to allow anonymous method calls by granting launch and access permissions to everyone. After all, the Internet is one big IP network. But paradoxically, if you take an existing DCOM appâ€"one that works like a champ on your company's internal network or intranetâ€"and try it over the Internet, it's liable to fail miserably. The likely reason? Firewalls.
      Right out of the box, DCOM mixes with firewalls like oil with water. One reason is that COM's SCM uses port 135 to communicate with SCMs on other machines. Firewalls restrict the ports and protocols that may be used, and are likely to reject incoming traffic through port 135. But the greater problem is that in order to avoid conflicts with applications that use sockets, pipes, and other IPC mechanisms, DCOM isn't hardwired to use a specific range of ports; instead, it picks the port numbers that it uses at runtime. By default, it can use any port in the range of 1,024 to 65,535.
      One way to allow DCOM apps to bore through firewalls is to open port 135 and ports 1,024-65,535 to the protocol that DCOM is configured to use. (The default is UDP for Windows NT 4.0 and TCP for Windows 2000.) But that's not much better than removing the firewall altogether. Chances are your company's IT folks would have something to say about that.
      A safer and more realistic alternative is to restrict the range of ports that DCOM uses and to open just a small set of ports to DCOM traffic. As a rule of thumb, you should allocate one port for each server process that exports connections to remote COM clients (not one port per interface pointer or one port per object, but one per server process). It's also a good idea to configure DCOM to use TCP rather than UDP, especially if your servers perform callbacks to their clients.
      The range of ports that DCOM uses for remote connections and the protocol that it uses can be configured via the registry. On Windows 2000 and Windows NT 4.0 Service Pack 4 or later, you can apply these configuration changes with DCOMCNFG. The following is a recipe for configuring DCOM to work through a firewall.

Figure 3 Selecting the Protocol
Figure 3 Selecting the Protocol

  1. On the server (the machine that hosts remote objects behind the firewall), configure DCOM to use TCP as its protocol of choice, as shown in Figure 3.
  2. On the server, restrict the range of ports that DCOM will use. Remember to allocate at least one port per server process. The example in Figure 4 constrains DCOM to ports 8,192 to 8,195.
  3. Open the ports you selected in step 2 to TCP traffic through the firewall. Also open port 135.

Figure 4 Selecting the Ports
Figure 4 Selecting the Ports

      Perform these steps and DCOM will work admirably with firewalls. If you'd prefer, SP4 and higher also let you specify endpoints for individual COM servers. For more information, read Michael Nelson's excellent paper on DCOM and firewalls, available on MSDN® Online (see https://msdn.microsoft.com/library/ backgrnd/html/msdn_dcomfirewall.htm).
      Also note that users of SP4 and later can use COM Internet Services (CIS) to render DCOM compatible with firewalls by installing Internet Information Services (IIS) on their servers and using CIS to route DCOM traffic through port 80. For more information on this subject, see https://msdn.microsoft.com/library/backgrnd/html/cis.htm.

Use Threads or Async Calls to Avoid Long DCOM Time-outs

      I'm constantly asked about the long time-outs that sometimes occur when DCOM is unable to fulfill a remote instantiation request or method call. Here's a typical scenario: a client calls CoCreateInstanceEx to instantiate an object on a remote machine, but the machine is temporarily offline. On Windows NT 4.0, rather than fail the activation request immediately, DCOM might take a minute or more to return a failed HRESULT. DCOM can also take a long time to fail a method call directed at a remote object that no longer exists or whose host machine has been taken offline. What, if anything, can a developer do to avoid these long time-outs?
      The short answer is not much. DCOM is highly dependent upon the underlying network protocols and the RPC subsystem. There's no magic setting anywhere that lets you limit the duration of DCOM time-outs. There are, however, two techniques that I often use to avoid the debilitating effects of long time-outs.
      In Windows 2000, you can use asynchronous method calls to free the calling threads while calls are pending in the COM channel. (For an introduction to asynchronous method calls, see "Windows 2000: Asynchronous Method Calls Eliminate the Wait for COM Clients and Servers Alike" in the April 2000 issue of MSDN Magazine. If an asynchronous call hasn't returned after a reasonable amount of time, you can cancel it by calling ICancelMethodCalls::Cancel on the call object used to initiate the call.
      Windows NT 4.0 doesn't support asynchronous method calls, and even in Windows 2000 there's no support for asynchronous activation requests. The solution? Make the call to the remote object (or the request to instantiate it) from a background thread. Have the primary thread block on an event object and specify a time-out value that reflects the length of time you're willing to wait. When the call returns, have the background thread set the event. Assuming the primary thread blocks using WaitForSingleObject, when WaitForSingleObject returns the return value tells you whether it returned because the method call or activation request returned, or because the time-out you specified in the call to WaitForSingleObject expired. You can't cancel a pending call in Windows NT 4.0, but at least the primary thread will be free to go about its business.
      The code in Figure 5 demonstrates how a Windows NT 4.0-based client might call an object from a background thread. In this example, thread A marshals an IFoo interface pointer and starts thread B. Thread B unmarshals the interface pointer and calls IFoo::Bar. No matter how long the call takes to return, thread A won't block for more than five seconds due to the 5,000 that it passes in WaitForSingleObject's second parameter. It's not pretty, but if it's important that thread A not get stuck no matter what happens on the other end of the wire, then it may be worth the trouble.

Sharing Objects Isn't Easy

      Judging from the mail that reaches my inbox and the questions I'm asked at conferences, one issue that troubles a lot of COM programmers is how to connect two or more clients to one object instance. A feature-length article (or a small book) could easily be written in response to this question, but suffice it to say that connecting to existing objects is neither as easy nor as automatic as it could be. COM offers a multitude of ways to create objects, including the popular CoCreateInstance(Ex) function. But COM lacks a general-purpose naming service permitting object instances to be identified using names or GUIDs. And it offers no built-in way to create an object and later identify it as the target of a call to retrieve an interface pointer.
      Does that mean that it's impossible to connect multiple clients to a single object instance? Of course not. Here are five ways to do it, with links to resources where you can find more information and even sample code to guide your way. Be aware that these techniques are not interchangeable in a general sense; usually, circumstances dictate which, if any, is appropriate for the task at hand:
Singleton objects
A singleton object is one that is instanced just one time. Ten clients may call CoCreateInstance to "create" a singleton object, but in reality they all receive interface pointers to the same object. An ATL COM class can be transformed into a singleton by adding a DECLARE_CLASSFACTORY_SINGLETON statement to its class declaration.
File monikers
If an object implements IPersistFile and registers itself in the Running Object Table (ROT) using a file moniker that encapsulates the file name passed to the object's IPersistFile::Load method, then clients can use file monikers to connect to extant instances of the object. In effect, file monikers permit object instances to be named using the names of the files in which the objects store their persistent data. They even work across machines.
CoMarshalInterface and CoUnmarshalInterface
COM clients that hold interface pointers can share them with other clients, provided they're willing to marshal them. COM offers optimizations for threads that want to marshal interface pointers to other threads in the same process (see Lesson 2), but if the client threads belong to different processes, CoMarshalInterface and CoUnmarshalInterface are the keys that open the door to interface sharing. For a discussion and sample code, refer to my Wicked Code column in the August 1999 issue of MSJ.
Custom class objects
All "externally creatable" COM objects are accompanied by separate objects, called class objects, whose purpose is to create instances of said COM objects. Most class objects implement an interface named IClassFactory, which includes a method named CreateInstance that creates object instances. (Under the hood, COM translates CoCreateInstance(Ex) calls into calls to IClassFactory::CreateInstance). The problem with IClassFactory is that it's no help at all in retrieving interface pointers to previously created object instances. A custom class object is a class object that implements a custom activation interface in lieu of IClassFactory. Since you define the interface, you can also define methods for retrieving references to objects already created by this class object. For more information and a sample written in ATL, see the February 1999 Wicked Code column.
Custom monikers
The November 1999 Wicked Code column introduced a custom moniker class that permits object instances to be named using C-style strings. Pass an instance name to MkParseDisplayName and you get a moniker that connects you to the object instance. One drawback is that these kinds of monikers don't work across machines in Windows NT 4.0.
      Before you use any of these methods to share object instances among clients, ask yourself a question: is sharing really necessary? If a method call retrieves data from an external data source (a database, a hardware device, perhaps even a global variable) in response to clients' requests, then why not allocate one object instance per client and allow each instance to access the data source? You might have to synchronize access to the data source, but you won't have to fool with custom class objects, custom monikers, and the like. That's how apps built with Microsoft Transaction Services (MTS) work, and it has proven to be a compelling programming model for a variety of reasons, not the least of which are ease of implementation and enhanced performance.

Drop Me a Line

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

Jeff Prosise is the author of Programming Windows with MFC (Microsoft Press, 1999). He is also a cofounder of Wintellect, a software consulting and education firm whose Web address is https://www.wintellect.com.

From the November 2000 issue of MSDN Magazine.