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.
Porting Applications from MTS to COM+
ver the next few months many of you will be porting your MicrosoftÂ® Transaction Server (MTS) applications over to COM+ and WindowsÂ® 2000. This month's column examines some of the most significant changes that will affect the way you'll write middle-tier components. I'll assume you know how to create Visual BasicÂ®-based components for applications that employ COM and MTS. Thankfully, there aren't many aspects of component development that will change when you're moving to COM+. Still, some aspects have changed slightly, while other ones have changed quite dramatically.
I'll start by quickly reviewing the architecture of MTS. This will allow me to make comparisons between the old platform and the new one. Then I'll examine a few of the new architectural details of COM+. In particular, you'll see why calls to CreateInstance and SafeRef are no longer necessary. I'll finish by discussing how CLSIDs are configured in COM, MTS, and COM+. This will give you an understanding of why the MTS command refresh component is no longer necessary.
Moving from COM to MTS
The release of MTS was a significant milestone in the evolution of Microsoft's multitier development strategy. MTS is a piece of software that was created for Windows NTÂ® Server. As its name implies, MTS allows middle tier applications running on Windows NT Server to run and control distributed transactions.
However, MTS has been much more than a transaction monitor. It provided a brand new runtime environment for COM objects running in the middle tier. MTS added lots of critical infrastructure support that wasn't included with earlier versions of COM. In particular, it added new support for distributed transactions, integrated security, thread pooling, and improved configuration and administration.
The name MTS has caused confusion because it wasn't just created for companies that want to run distributed transactions. MTS is a vehicle that Microsoft used to ship the next generation of its distributed application framework. Middle-tier objects targeted for Windows NT Server should usually be run in the MTS environment, whether or not they're involved in transactions. The name MTS confuses people when they learn that it can be used to deploy objects that are nontransactional.
To eliminate this confusion in Windows 2000, Microsoft has renamed the middle-tier runtime environment from MTS to COM+. The transaction support that was created for MTS is also included with COM+. If you already know how to program transactions using MTS, there's good news. There's not too much more you have to learn to write transactional components for COM+; things work almost exactly the same way.
Interception and Attribute-based Programming
MTS introduced a very important concept to a platform's programming model: attribute-based programming. Services provided by the platform are exposed through declarative attributes. Attribute settings are determined by programmers at design time and can be reconfigured by administrators after an application has been put into production. Attribute-based programming differs from the way most operating systems have exposed system services in the past.
Traditionally, operating systems have exposed services through a set of functions in a call-level API. In this type of model, an application uses system services by making explicit calls into the API. This means you must compile explicit system-level calls into your application. If you want to change the way you're using a system service after an application is already in production, you must modify your code and recompile the application. With a programming model based on declarative attributes (such as MTS), things are different and much more flexible.
Let's look at an example to give you a clearer picture of how declarative attributes work. A component in an MTS application has an attribute that tells the MTS runtime environment whether it supports transactions. If a programmer marks a component to require a transaction, objects instantiated from the component will be created inside the context of a logical MTS transaction.
When a client calls one of these transactional objects, the MTS runtime automatically makes a call to start a DTC-controlled transaction. It also makes the appropriate call to either commit or roll back the transaction. The point is that programmers never make explicit calls to start, commit, or abort a transaction. All the necessary calls are made behind the scenes by the MTS runtime.
Why is this new attribute-based programming model supposed to be better than the older procedural model, which requires explicit API calls? First, the new model requires less code. You indicate your preferences at design time by setting attributes, and the underlying runtime environment makes sure that your needs are met. A second less obvious reason is that administrators can easily reconfigure the way an application uses a system service after it's been put into production. There's no need to modify any code or recompile your application.
The MTS attribute-based programming model relies on a mechanism known as interception. When a client creates an object from a component that's been configured in an MTS application, the underlying runtime inserts an interception layer. This layer represents a hook that allows the system to perform preprocessing and postprocessing on a per-method basis. The system steals away control immediately before and immediately after the object executes a method call.
The COM Versus MTS Problem
One of the biggest dilemmas for programmers building multitier applications targeted for Windows NT Server is deciding whether or not to use MTS. Many companies building distributed applications have chosen MTS because it offers valuable built-in services that aren't part of COM. Other companies have chosen the basic COM model because they don't understand the added value of the MTS runtime environment.
COM ships with Windows NT Server, while MTS does not. In order to use MTS, you must install an extra piece of software, the Windows NT Option Pack. This means that MTS isn't really part of COM. COM has its own programming model and runtime layer; MTS has a different programming model and separate runtime layer.
You should understand that MTS is simply a layer that has been built on top of COM. COM was never modified to accommodate MTS. So what does this mean? The two runtime layers aren't as tightly integrated as they could be. This results in a platform that contains a certain degree of inefficiency, ambiguity, and confusion. Things are tricky for MTS programmers because they need to know how both programming models work. Moreover, many valid COM programming techniques don't work correctly when used in an MTS application.
Moving on to COM+
As the platform architects began planning changes and enhancements for Windows 2000, one thing was obvious: COM and MTS had to be unified into a single runtime layer and also into a single programming model. This task wasn't a trivial undertaking, but it was well worth the effort.
With the release of Windows 2000, all the best ideas of COM and MTS have been integrated into a new runtime named COM+. You should note that, unlike MTS, this new runtime layer is not optional. COM+ is part of the default installation of Windows 2000. However, the good news is that the COM versus MTS dilemma doesn't exist on Windows 2000. Moreover, writing components for COM+ is easier than MTS because many of the annoying idiosyncrasies associated with MTS have gone away.
COM+ is based on COM. It's based on binary components and interface-based programming. Method calls are remoted across process and computer boundaries through the use of a transparent RPC layer. And just like COM, COM+ components can be upgraded and extended in production without adversely affecting the client applications that use them.
COM+ is also based on MTS. It provides support for distributed transactions and role-based security. In addition, it provides a built-in scheme for thread pooling that's as transparent as the one built into MTS. Like MTS, the COM+ programming model uses interception to expose platform services to developers through declarative attributes. However, COM+ takes attribute-based programming much further. In addition to transactional services and integrated security, COM+ exposes additional services such as custom object construction, synchronization, and object pooling, among others.
In this column and over the next few months, I'm going to discuss the new COM+ attributes that are most pertinent to programmers using Visual Basic. If you want to know about each and every new COM+ attribute, you should read Don Box's article "Windows 2000 Brings Significant Refinements to the COM(+) Programming Model" (MSJ
, May 1999). It's very comprehensive and well worth reviewing online at http://www.microsoft.com/msj/0599/complusprog/complusprog.htm
Configured and Nonconfigured Components
One fairly obvious change is that the MTS term "package" has been replaced with the COM+ term "application." If you want your components to take advantage of COM+ services, you must install them inside a COM+ application. Just as with MTS packages, there are library applications and server applications. Objects created from a component configured in a library application are loaded into the process of the creator. Objects created from a component configured in a server application are run inside their own surrogate process. You should note that in COM+ the surrogate container application has changed from MTX.EXE to DLLHOST.EXE.
When a component is installed into a COM+ application, it's given a profile in a system catalog called the COM+ registration database (RegDB). This catalog holds configured attribute settings for components as well as COM+ applications. A component that has been installed into a COM+ application is known as a configured component. It earns this name primarily because it has associated COM+ attribute settings.
You will also encounter nonconfigured components, which are older components that don't have associated COM+ attributes. Nonconfigured components are not installed into COM+ applications. Instead, they are registered in a manner consistent with earlier versions of COM. For example, an in-process DLL containing nonconfigured components should be registered by using REGSVR32.EXE. You should note that nonconfigured components cannot take advantage of COM+ services. However, they can run in environments other than MTS and COM+.
If you're authoring component code for a COM+ application, you'll generally produce configured components. This approach allows you to take advantage of various platform services. When you're writing code, you're also likely to encounter nonconfigured components. For instance, the ActiveXÂ® Data Objects (ADO) library is made up of nonconfigured components. ADO objects can run inside a COM+ application. However, unlike configured components, they also run in applications based on earlier versions of COM.
The MTS Interception Layer
The interception layer in MTS is set up through an invisible system-supplied object called a context wrapper, as shown in Figure 1
. The MTS runtime layer (MTXEX.DLL) is responsible for creating and inserting a context wrapper between every MTS object and its creator. The term "MTS object" refers to an object that has been created from a component that's been configured in an MTS package. Figure 1 MTS Interception Layer
The term "configured component" wasn't formalized until the emergence of COM+. However, you should think of a component that's been installed in an MTS package as a configured component. It's really the same thing. MTS components are like COM+ components in that they have associated declarative attributes.
The MTS programming model forces programmers to depart from a few standard COM programming techniques. These irregularities happen because the COM runtime is ignorant of how MTS stores contextual information and sets up the interception layer. In essence, MTS needs to fake out various COM services when activating objects and creating proxies and stubs.
MTS programming often requires calls to CreateInstance. An MTS component should not call CreateObject when instantiating another object from another MTS component. A call to CreateObject is passed from the Visual Basic runtime to the COM Service Control Manager (SCM). However, the SCM doesn't know how to flow the contextual information maintained by MTS from the creator to the new object.
In MTS, calling CreateObject when you should call CreateInstance is problematic. For example, if one MTS component creates an object from another MTS component, the SCM doesn't know how to flow the information about the creator's activity. The SCM forwards the activation request to the MTS runtime, but it doesn't pass along any contextual information about the creator. The MTS runtime responds by creating a new activity for the object. Therefore, the creator and the object don't run in the same activity, which can cause quite a few problems.
The object and its creator cannot run in the same transaction because transactions must be scoped inside activities. The new object will also more than likely be placed on a different thread than the creator. This means that every call requires an expensive thread switch.
In addition to knowing when to call CreateInstance, MTS programmers also must know when it's necessary to call SafeRef. An MTS object should never hand out a direct reference to itself. If it did, method calls would bypass the context wrapper and defeat the interception layer. A call to SafeRef allows a programmer to pass a reference to an MTS object's context wrapper, which in turn prevents short-circuiting the interception code provided by MTS.
The Role of Contexts
The COM+ runtime layer does not rely on context wrappers. Instead, the interception layer is set up based on the notion of contexts. A context can be loosely defined as a set of objects running inside a process. (In actuality, processes can be subdivided into apartments, and apartments can be subdivided into contexts.) The COM+ runtime performs interception whenever a call crosses over a context boundary. As you will see, this makes the COM+ programming model less complicated. It also eliminates the need for calling CreateInstance and SafeRef.
Every object created inside a COM+ application is created within the scope of a specific context, as shown in Figure 2
. COM+ doesn't intercept calls when a caller and an object live in the same context. However, COM+ does intercept calls that go across contexts. The term "context switch" refers to system-supplied code that's run automatically whenever a call crosses over a context boundary. Figure 2 Crossing COM+ Context Boundaries
In COM+, the context wrappers used in MTS have been replaced with context-aware proxies. In MTS, context wrappers are only created when a client activates a new object. Every incoming method call must be routed through the lone context wrapper associated with an object. In COM+, context-aware proxies are created at activation and also after that time, if needed. An object may have many associated context-aware proxies. As you'll see, this new scheme is more flexible and more straightforward.
Every object activated inside a COM+ application is created inside a valid context. There are a few different ways that COM+ handles activation requests. The COM+ runtime always creates the new object in one of three places: inside the context of its creator, inside a newly created context, or inside the default context of a different apartment.
It turns out that the first two cases are much more common than the third, so let's look at the two more common cases first. At creation time, the COM+ runtime must decide whether a new object needs its own set of contextual information to describe its COM+ attributes. COM+ stores this information inside a place that's known as the object context. COM+ relies on information inside the object context when performing a context switch. If the new object needs its own object context, a new context is created. If not, a new object is created in the context of its creator.
In most cases, objects created from nonconfigured components are created in the context of their creator, while objects created from configured components get their own new context. Figure 3 shows what happens when a COM+ object creates an ADO object such as a connection or a recordset. Like any other object created from a nonconfigured component, an ADO object doesn't need its own context because it doesn't have any declarative attributes.
Figure 3 An Object Created from a Nonconfigured Component
In almost every situation, objects created from configured components need their own context. This is due to the fact that the COM+ runtime needs to know a few things to run the interception code correctly and provide reliable system services. The object context holds valuable pieces of information, such as an object's association to a specific activity and a specific transaction. For a more in-depth discussion about the information maintained inside the object context, see Don Box's House of COM column in the September 1999 issue of MSJ at http://www.microsoft.com/msj/0999/com/com0999.htm.
The last case, where the COM+ runtime creates the new object inside the default context of a different apartment, is by far the most infrequent. It's uncommon because it only occurs when the creator and the object being created have incompatible threading models. This means they cannot run in the same apartment. An example of this rare occurrence is when a creator running in a single threaded apartment (STA) instantiates an object from a nonconfigured component that's marked to run in the multithreaded apartment (MTA).
In this example the new object cannot run in the context of its creator because it's the wrong apartment type. (A context must live inside one specific apartment.) However, COM+ doesn't need to create a new context for the object since it doesn't carry any COM+ attributes. In these rare situations, the COM+ runtime creates the new object in the default context of the MTA. If you're only using apartment-threaded components such as the ones created with Visual Basic, you don't have to worry about this third scenario.
Contexts and Object References
The COM+ rules concerning object references are fairly straightforward. A client is directly bound to an object when they both live in the same context. A client must communicate with an object through a system-provided proxy whenever it lives in a different context. So the next question becomes, when and how do these proxies get created? The answer is refreshing: proxies are transparently created by the COM+ runtime on an as-needed basis.
There are two things you will commonly do as a COM+ programmer that result in the transparent creation of a context-aware proxy. First, you'll create proxies when you activate new objects from configured components with the CreateObject function. Second, you'll create proxies when you pass object references in standard method calls using parameters and return values.
When you create an object from a configured component, the COM+ runtime will more than likely create a new context. However, the COM+ runtime doesn't return a direct reference back to the creator. COM+ determines that the object and creator live in different contexts, so it creates a context-aware proxy and returns that to the creator instead. This means that all calls from the creator to the object go through the proxy. The proxy performs the required context switch at runtime each time control passes back and forth between these two contexts.
One of the most significant programming changes as you move from MTS to COM+ is that you don't need to call CreateInstance anymore. In COM+ the SCM is context-aware. This means that unlike Windows NT 4.0, the SCM built into Windows 2000 knows how to flow contextual information concerning things like the creator's activity. Calls to CreateObject are always handled correctly. Note that calls to CreateInstance are still supported, but only for backward compatibility.
You should also be aware that creating COM+ objects with the Visual Basic New operator can cause problems in COM+ as well as MTS. Never use the New operator when the client and the class used to create the object live in the same DLL. For more details on the problems associated with using the New operator, see my Advanced Basics column in the August 1999 issue of Microsoft Internet Developer
As you have seen, COM+ automatically creates a proxy when an object is created in a different context from its creator. COM+ also commonly creates a proxy when an object reference is passed across context boundaries in a standard method call. Since the COM+ runtime intercepts calls as they travel across context boundaries, it can also determine when the recipient of an object reference needs a new proxy. Figure 4 Object References Across Contexts
Let's look at an example to see how things works. In Figure 4 assume that Object1 creates Object2 by calling the CreateObject function and passing the ProgID for Class2. Object1 is in context A, while Object2 is in context B. Now assume that Object1 calls the following method on Object2:
Function CreateObject3() As Class3
Dim obj As Class3
Set obj = CreateObject("MyDll.Class3")
Set CreateObject3 = obj
When Object2 creates Object3, it's passed a reference to a proxy that knows how to perform a context switch between context B and context C. You should note that Object1 cannot use this proxy because it lives in context A. Object1 needs a different proxy that can perform the proper context switch between context A and context C. However, things work out fine. When the call to Create3Object returns from context B back to context A, the COM+ runtime automatically builds a new proxy for Object3, which can be used inside context A.
This example demonstrates a very important point: all object references in COM+ are context-relative. If you follow the rules, things are pretty easy. Whenever you need to share an object reference between two or more objects, you should design methods to pass references between them. However, you still have to watch out. You can get into some trouble if you try to share object references using other techniques.
For instance, it's a bad idea to place an object reference inside a public variable in a BAS module. Let's revisit my previous example. What do you think would happen if Object2 created Object3 and stored the returned reference in a BAS module variable? You should see that the reference can only be used legally by code running inside context B.
If Object1 accessed the BAS module variable and acquired the reference, things would not work correctly. Object1 needs a proxy that knows how to perform the context switch between A and C. However, the reference held by the BAS module variable points to a proxy that was built to perform the context switch between B and C. This style of programming breaks the interception scheme set up by COM+. Likewise, it's also a bad idea to store object references using the Shared Property Manager.
CLSIDs and the Registry
Another cause for confusion associated with MTS development has to do with how CLSIDs should be configured in the registry. COM requires CLSIDs to be configured one way, while MTS requires something completely different.
Let's start by looking at the InProcServer32 key. According to traditional COM, a CLSID from an in-process server must have this key to point the SCM to the DLL containing the code for the CLSID. When you build a Visual Basic DLL, the compiler automatically builds in self-registration code. Once the DLL has been built, it can be registered with the REGSVR32.EXE utility. The resulting registry entry looks like this:
Note that the SCM needs this entry when it activates an object. Once the SCM finds the path to the DLL, it can load it into the creator's process and activate an object.
However, the SCM in Windows NT 4.0 doesn't know how to properly create an MTS object that's context-aware. The MTS team had to come up with a way to steal away activation requests from the SCM. When you install this component into an MTS library package, the MTS catalog manager modifies the registry to fool the SCM. Here's the resulting entry:
As you can see, the SCM is tricked into thinking your component code lives in MTXEX.DLL. The SCM loads in the MTS runtime layer, which in turn loads your DLL. The MTS runtime must play this trick because the SCM was never modified to accommodate the MTS programming model.
Things are even more confusing for a CLSID configured in an MTS server package. This is because objects created from a server package component must run inside their own private instance of the MTS container application MTX.EXE. The following entry shows how the MTS catalog manager configures the CLSID to force the SCM to activate the object in a package-specific instance of MTX.EXE:
With Windows NT 4.0, things are confusing because there's several ways to register a CLSID, the original COM way and two new MTS ways. To make matters even worse, the self-registration code built into a Visual Basic DLL only knows the COM way to register a CLSID. If you call REGSVR32.EXE on a Visual Basic DLL after it's been installed in an MTS package, the CLSID entries get messed up.
Many programmers have gotten into trouble when building and testing DLLs on an MTS machine. This is because Visual Basic always calls REGSVR32.EXE at the successful completion of every build. There's nothing you can do to suppress this behavior in the Visual Basic IDE. Things don't work correctly when you try to test an MTS component that's being reconfigured as a standard COM component.
To solve the problem, MTS offers a utility command named refresh components. When you refresh the components in an MTS package, the MTS catalog manager simply looks through the registry and makes sure each CLSID is configured the proper MTS way instead of the old COM way. There's also a Visual Basic add-on which automatically refreshes MTS components whenever the associated ActiveX DLL server is rebuilt.
In COM+, things have come full circle. CLSIDs are registered the original COM way instead of the newer, MTS way. There's always an InProcServer32 key which holds the path to your DLL. This is true for both configured as well as nonconfigured components. Configured components only differ from nonconfigured components in that they have additional information about their attributes and activation requirements in RegDB. The SCM either creates an object in the process of the creator or as a separate instance of DLLHOST.EXE, depending on information in the catalog. In particular, the SCM determines whether the component has been configured in a library application or a server app.
You'll notice that the refresh components command isn't included in the COM+ Services administration tool, as it was in the MTS Explorer. That's because it's no longer needed. The command is still supported programmatically for backward compatibility with development tools (such as Visual Basic) that may continue to call it. However, now there's one less thing you have to worry about. Rebuilding or reregistering a DLL doesn't mess up the CLSID settings in the registry.
This month's column looked at some of the biggest changes that will affect the way you develop Visual Basic-based components for the middle tier. You've seen that COM+ builds on the strengths of both COM and MTS. Many aspects of COM+ programming should be familiar to you already. You've also seen that COM+ eliminates much of the confusion and ambiguity that's inherent in a platform such as MTS, which has two different programming models and two separate runtime layers.
Over the next few months, I'll continue to look at how COM+ changes the way you'll write your code. I'll discuss the new interfaces that COM+ makes accessible to programmers, such as ContextInfo, IContextState, and SecurityCallContext. I'll show you how to make the most of declarative construction strings. I'll also take an in-depth look at how the COM+ team optimized the built-in thread pooling scheme for apartment-threaded objects. Compared to MTS, the resulting runtime environment of COM+ improves overall system throughput and scalability.
Ted Pattison is an instructor and researcher at DevelopMentor (http://www.develop.com), where he co-manages the Visual Basic curriculum. The second edition of Ted's book, Programming Distributed Applications with COM and Visual Basic 6.0 (Microsoft Press, 1998), is due out in May 2000.
From the March 2000 issue of MSDN Magazine.