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 1998

Microsoft Systems Journal Homepage

Download Nov98HoC.exe (5KB)

Don Box is a cofounder of DevelopMentor where he manages the COM curriculum and spreads the love of COM across the globe. Don recently finished coauthoring Effective COM, a follow-up to his first book, Essential COM, both from Addison-Wesley. Don can be reached at http://www.develop.com/dbox.

Microsoft® Transaction Server (MTS) can bring out the Houdini in COM programmers. Many of us have spent years honing our skills to get the most out of the COM programming model. Then along came MTS, which takes away many of the tools and techniques we've come to depend on. The escape artist in us comes out as we attempt to circumvent, deceive, or otherwise trick the MTS runtime into doing what we want.
      Perhaps one of the most basic things that MTS takes away is the ability to issue activation calls against different host machines. COM developers who work in C++ or Java learn early on that the COSERVERINFO structure can be your friend. Every COM activation call takes one of these as an optional parameter and, if provided, COM will use the host name you provide to service the activation request. Here's a simple example that activates an object at the Microsoft Web server:
 HRESULT CreateExcel(IUnknown **ppUnk) {
     *ppUnk = 0;
     MULTI_QI mqi = { &IID_IUnknown, 0, 0 };
     COSERVERINFO csi = {0,L"www.microsoft.com",0,0}; 
     HRESULT hr = CoCreateInstanceEx(
                               CLSID_ExcelApplication,
                               0, CLSCTX_REMOTE_SERVER,
                               &csi, 1, &mqi);
     if (SUCCEEDED(hr))
         *ppUnk = mqi.pItf;
     return hr;
 }
Granted, www.microsoft.com probably won't let you activate a Microsoft Excel object, but you get the idea.
      Programmers working in Visual Basic® have had to resort to somewhat less elegant techniques to achieve the same effect. I've seen several variations of Charlie Kindel's Declare statement for CoCreateInstanceEx floating around the Internet. And whenever a Visual Basic guy and a C++ programmer meet, the C++ programmer invariably writes an inproc server that calls CoCreateInstanceEx for the other programmer (to date, there are approximately 2,518 of these DLLs in existence). Fortunately, Visual Basic version 6.0 updates the CreateObject intrinsic function to support an optional second parameter, which is of course a host name. This allows programmers working in Visual Basic to do the following:
 Function CreateExcel() As IUnknown
 Set CreateExcel = CreateObject("Excel.Application",
                                "www.microsoft.com")
 End Function
Of course, for this to work, the ProgID must make sense on the client machine, but compared to some of the other problems that Visual Basic has wrought on the COM programming community, this is a minor issue.
      So, given the simplicity of activating on a particular host machine, why can't an MTS programmer use the techniques just described? The answer is simple: MTS programmers need to factor activities into their work at all times. In case you're new to the MTS technology, here's a simple explanation of MTS activities.
      An MTS activity is a set of objects that act in concert on behalf of a single client. An activity can contain objects from multiple packages potentially running on multiple host machines. Each MTS object exists in exactly one activity, although an activity can contain multiple objects. Each MTS transaction stream exists in exactly one activity, although an activity can contain multiple transaction streams if the [TRANSACTION_REQUIRES_NEW] attribute is used. Each MTS-based object created using IClassFactory::CreateInstance (this includes calls to CoCreateInstance[Ex]) belongs to a new activity. MTS-based objects created using IObjectContext::CreateInstance belong to the activity of their creator. While not formally specified, the client thread that begins the activity by calling CoCreateInstance should be considered part of the activity as well, although a single thread can begin multiple activities simply by calling CoCreateInstance multiple times.
      In general, each client should have exactly one activity. This means that if your MTS object needs to create another MTS object to help perform its work, it had better not call CoCreateInstanceEx. If it were to use CoCreateInstanceEx, the subobject would reside in a different activity. This means no transaction propagation, no end-to-end security support, and strange concurrency and threading behavior. To avoid these problems, MTS objects must create all sub-objects using IObjectContext::CreateInstance as follows:
 IObjectContext *poc = 0;
 HRESULT hr = GetObjectContext(&poc);
 if (SUCCEEDED(hr)) {
     ISteve *ps = 0;
     hr = poc->CreateInstance(CLSID_Steve,
                        IID_ISteve, (void**)&ps);
     if (SUCCEEDED(hr)) {
         ps->DoSomeWork();
         ps->DoALittleMore();
         ps->Release();
     }
     poc->Release();
 }
 return hr;
which in Visual Basic would look like:
 Sub IBob_DoWork() 
     Dim oc as ObjectContext
     Set oc = GetObjectContext()
     Dim s as ISteve
     Set s = oc.CreateInstance("Steve")
     s.DoSomeWork
     s.DoALittleMore
 End Sub
This technique allows the two objects to perform work inside of the same transaction, and ensures that the security and concurrency pieces of MTS work properly and efficiently.
      Note that IObjectContext doesn't take a COSERVERINFO. This means that all instances of Steve are going to come from exactly one machine. The machine that is selected will depend on how the local machine is configured.
      MTS handles remote activation in a fairly simplistic manner. The MTS catalog manager allows remote configuration of all MTS-based classes on the network. Every machine running MTS has a RemoteComponents collection that contains CLSID to host machine mappings. The RemoteComponents collection can be configured programmatically using the MTS catalog interfaces or the MTS Explorer. Figure 1 shows some simple Visual Basic code for adding a remote component reference to the local machine, and Figure 2 shows the MTS Explorer doing the exact same thing. The ability to configure a network of COM machines using MTS Explorer or the MTS catalog interfaces is really useful and something that has been lacking in classic COM up until now.
      The major deficiency of RemoteComponents is that all local activation requests for a given CLSID must be routed to the same machine. There is no way that an MTS object can select hosts based on performance criteria, availability, or proximity to other resources in use. Granted, future versions of COM+ plan to offer some support for load balancing, but this still will not solve the "I want the object over there!" problem that inevitably arises.
      One solution to this problem is to resort to a bit of trickery (here's the Houdini part). Remember that the goal is to be able to specify a host when creating an object within the same activity. This means that CoCreateInstanceEx is definitely out of the question. But if you were to simply create a middleman object on a specific machine, that middleman object could create a local object of any desired type. Such a middleman object might implement an interface that looks something like:
 interface IActivatorEx : IUnknown {
     HRESULT CreateInstance(
         [in] REFCLSID rclsid, 
         [in] REFIID riid, 
         [out, iid_is(riid), retval] void **ppv);
 }
If each machine on the network had a local implementation of this interface with a unique CLSID per host machine, you could simply use this middleman object to do the remote activation call on behalf of the caller (see Figure 3).
Figure 4  Using a Middleman
Figure 4  Using a Middleman

      Figure 3 assumes that HOSTID_HOSTX is an alias to a CLSID that has been associated with a particular machine in the RemoteComponents collection of the catalog (as shown in Figure 4). To support multiple hosts, multiple CLSIDs can be registered, all of which would point to the identical code. In essence, the network would have dozens of implementations of IActivatorEx that differ only by the machine on which they activate.
      Since either the top-level object or the subobject may be transactional, the middleman must allow the transaction stream to flow from the top-level object to the subobject. If both objects are nontransactional, then it would be foolish (as well as semantically wrong) to have the middleman introduce a transaction stream into the object hierarchy. To get the correct behavior in all situations, the middleman should be marked [TRANSACTION_ SUPPORTED], so that it allows the transaction stream to flow through to the subobject. In the case where the top-level object is nontransactional, the middleman will also be nontransactional, which keeps it from having the problems associated with transactional versus nontransactional lifecycle management (the "statelessness" problem).
      One advantage of this middleman technique is that the physical host name is never hardcoded into source code. Rather, the activation request for the middleman goes through the same remote components mechanism that any other request would use. This allows system administrators to associate the CLSID (HOSTID_HOSTX) with an arbitrary machine. This level of indirection is not unlike MTS Roles, where the developer works in terms of logical names that get associated with physical identifiers at deployment time.
      A given deployment environment could have an arbitrarily large number of CLSIDs that represent aliases to a large number of host machines. To simplify the use of this middleman approach, Figure 5 contains an MTS component that wraps different middlemen objects behind a simple interface. You can download this code along with the corresponding MTS package files from http://www.develop.com/dbox/ (or find it in the source code download at the top of this article). Also included is a simple Visual Basic tool that automates CLSID to hostname associations.

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

From the November 1998 issue of Microsoft Systems Journal.