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

Migrating from MTS to COM+
Ken Spencer
I

n my column in the December 1999 issue of Microsoft Internet Developer I wrote about the Fitch & Mather (FMStocks) application that Microsoft demonstrated at TechEd in 1999. This application was designed to show how to create a scalable Web application using Visual Studio® and Microsoft® Internet Information Services (IIS). The demonstration illustrated a number of techniques that made the application work fast.
      Microsoft and Vertigo Software updated this application for Windows® 2000. The new application, FMStocks 2000, is based on the same code that was used in the previous version, but it has several feature enhancements. This month I will focus on the changes from a code perspective that take advantage of the new scalable features that Windows 2000 provides.

Background

      FMStocks 2000 uses COM+, not Microsoft Transaction Services (MTS). COM+ is built into Windows 2000, while MTS was an add-on to Windows NT® 4.0. This seems like a minor distinction, but it is very important to developers. Because it uses COM+, developers must make one small change to their applications. With MTS, anytime one object that is in a transaction instantiates another object that must run in the same transaction, the first object's code must use GetObjectContext.CreateInstance to instantiate the new object. This call is required because MTS is not a core part of the operating system. When you instantiate an object using CreateObject, MTS does not know about the object, so the object will run in a new transaction context (if its transaction settings require a transaction). Therefore, you must call CreateInstance to let MTS know you want to create the new object so it can create it inside the same transaction.
      COM+ changes this because it is part of the operating system and knows what is happening with components and their objects when they are created. In Visual Basic®, all you need to do when you want to instantiate an object from another object and have the new object become part of that transaction is use either CreateObject or the New keyword. For instance, you could instantiate the Customer object using either of these two methods:
Set oCustomer = CreateObject("Companies.Customer") 
Set oCustomer = New Companies.Customer

      The same approach applies to any object you instantiate, including ActiveX® Data Objects (ADO). However, don't use the New keyword when you dimension the object variable. For instance, if you use the following statement, you will add extra overhead to your application:


Dim oCustomer as New Companies.Customer

This causes Visual Basic to add code like the following internally every time you use oCustomer:


If not isObject(oCustomer) then 
    Set oCustomer = New Companies.Customer
oCustomer.Add "John"

Every time you use the oCustomer object, Visual Basic must place code before it to make sure it is instantiated. This makes your application code slightly smaller, but adds lots of overhead to your application if you use the object frequently (for instance, in a loop).

Migrating to COM+

      Now, what happens when you migrate an existing application to COM+ and it uses CreateInstance? You should change those CreateInstance statements to CreateObject or use the New keyword. Doing so will improve the performance of your application because it will work directly with COM+ and avoid the overhead of working with CreateInstance while it's really calling CreateObject behind the scenes.
      Once you have changed CreateInstance to CreateObject, you can take advantage of other COM+ features. You should also create a reference to the COM+ Services Type Library to replace your reference to the MTS type library (see Figure 1). The COM+ Services Type Library supports the same features as the MTS type library, but has new methods that add more functionality. You will still find good old GetObjectContext, SetAbort, and SetComplete there, but you will find many more features as well.

Figure 1 Referencing the COM+ Services Type Library
Figure 1Referencing the COM+ Services Type Library

      If you look through FMStocks 2000 you will see that SetComplete and SetAbort are used just like they were with MTS. The similarity of the interfaces makes migrating applications easy.

Managing Components

      To put components into COM+ and manage them, you must use Component Services Explorer, which can be found on the Administrative Tools menu. The first thing you must do with a component that's going to run in COM+ is load it into a COM+ application. A COM+ application looks and works just like an MTS package. In fact, you will find that many of the COM+ settings are just like those in MTS. That's good news because it makes it easier to get up to speed with COM+.
      You can create a new application the same way you created an MTS package. Right-click the COM+ Applications folder and select New | Application from the submenus. Click Next and you will see the Install or Create a New Application page. Click Create to create a new empty package. On the next page, enter the name for the package (see Figure 2).

Figure 2 New Application in COM+
Figure 2New Application in COM+

      You can change the server or library setting on this page. If I were creating a real database service layer (as in DBService), I would set this to Library. The application would then run in the calling process space when called by code in an object that was loaded in another package. This way you can use one package over and over again from code that runs in other packages. This makes it easy to share code between applications, which is what components and objects are all about. This setting is on the Create page in COM+, so you don't have to go looking for it after you create the package.
      You can now easily work with your new COM+ application and load components into it. Once you have created the new app, just use Windows Explorer to drag the component from its location on the disk and drop it into the application's Components folder, just like you did with MTS. Now the component will run in COM+.
      Next, open the properties for the objects in the component and tweak their settings. Just like an MTS class, the General property page has a description that you can enter or change at any time.
      The Transactions property page for COM+ is similar to the same page in MTS. In MTS, there were four transaction types to choose from. COM+ has similar settings (see Figure 3), plus a new one called Disabled.
      You will notice that the type names are slightly different, but otherwise the first four types match those from MTS. Disabled, the new transaction type, turns off transaction support for a class that just implements logic functions that are not tied to a database and are not transactional. For instance, neither helper classes nor classes that work with the registry need transactional support. You can set the transactional property to Disabled for those classes, and now they won't incur the COM+ transaction overhead. In fact, the documentation states that a class set to Disabled will see no more overhead from COM+ than a standard COM class would. That's pretty sweetâ€"now you get the benefits of COM+ for just-in-time activation without having to incur transactional overhead when you don't need it.
      Now, what about Visual Basic and its MTS settings? If you set one of the four MTS settings in Visual Basic, COM+ will pick up the setting from the class when you add that class to an application. You can't select the Disabled setting in Visual Basic. If you set the MTSTransactionMode property to 0â€"NotAnMTSObject, this will cause the transaction mode property to be set to Not Supported. It will not set it to Disabled. So for now, you must set Disabled outside of the class itself.
      The last option on the Transactions page allows you to control the transaction timeout value for a particular class. This is nice because it gives you very granular control. When you have a class that takes a long time to execute its transactions, you can tweak the transaction timeout for only that class without affecting the settings on the rest of the server. This is handy in Web development since ASP scripts can take a widely variable amount of time to run, especially with fluctuating user demand. You can tweak this timeout for a particular COM+ class without changing the other classes. This should also be useful where you have components that access different databases, where some of the databases are either on a slow server, are slow due to size or database type, or are accessed over a slow link. You can set the timeout higher for the class that uses those databases and leave it at the standard setting for all others.

Interface Implementation

      There's a new feature in COM+ called the IObjectConstruct interface. To use this feature, you use the Implements feature in Visual Basic, allowing you to implement the interface of another class. This is useful when you want to implement a standard interface across several objects. To use IObjectConstruct, you first need to set a reference to the COM+ Services Library in your project. Then to use this interface in your code, use the Implements statement with IObjectConstruct as shown here:


Implements IObjectConstruct

This should be the first line in your class. Once you have used Implements to bring in the interface, you must implement the interface of the Construct procedure from the IObjectConstruct class. You are responsible for implementing the interface within your own code.
      In FMStocks 2000, IObjectConstruct is implemented in the DBHelper class. When you implement an interface, your code must provide the implementation (logic) for the entire interface of the Implements class. In this case, you must create a procedure to implement the Construct procedure like so:


Private Sub IObjectConstruct_Construct(ByVal pCtorObj As Object)
    'do some stuff here
End Sub

      When the class containing this code loads, the code fires and you can grab the data from the pCtroObj object that is passed in as a parameter. Where does the data in pCtroObj come from? The FMStocks 2000 documentation doesn't go into much detail here, so I pulled together some information from both the COM+ documentation and my own testing. To use the IObjectConstruct Construct method to extract data from its parameter object, you must set the data somewhere. To do this, you must use Component Services Manager and the Activation property page. Open the properties for the class containing the Construct code and set the string that is passed to pCtroObj administratively by following these steps:

  1. In the Details pane of the Component Services administrative tool, right-click the class that you want to configure, then click Properties.
  2. Click the Activation tab.
  3. Click the "Enable object construction" checkbox.
  4. In the "Constructor string" edit box (see Figure 4), enter the construction string (for example, DSN=AccountingServer). Note that an empty string may be used.
Figure 4 MTS
Figure 4MTS

      You can put anything you want in the string, and the data does not have to be a DSN. For example, take a look at the Construct code from FMStocks 2000 that's shown in Figure 5. The incoming object (pCtorObj) has a ConstructString that contains the data you entered in the Activation property page for the class. The code from FMStocks 2000 parses this string and looks for either ConnectionString or Server in the string. If the code finds either one, then it sets the corresponding module level variables. It's pretty simple code, but it makes for an interesting way to handle connection strings and other variable data that does not lend itself to a database or file.

More Settings

      The other items on the Activation tab require a bit of discussion. The Enable Just In Time Activation box is automatically enabled when transactions are required. Checking this box causes COM+ to automatically create and destroy objects as needed to more efficiently manage server resources. This is similar to the way MTS handled activation, but it was automatic in MTS. In COM+, you can easily turn it on and off. This setting is automatically enabled when you use a class that must participate in transactions.
      The next checkbox, "Component supports events and statistics," does just what it says. If you set this box, COM+ will be required to collect information about that class when it is running. Any time you collect information, there is additional overhead. For instance, if you turn on all the auditing features in Windows 2000 or Windows NT, you can quickly bring a server to a crawl. Statistics reporting may not create a great deal of overhead for most applications, but you should be aware of the potential to increase resource usage, especially if you want to wring that last ounce of performance from an application.
      The last checkboxâ€"Must be activated in caller's contextâ€"forces an object to be executed in the same context as the object calling it.

Conclusion

      COM+ is a powerful object and transaction management system. The performance numbers from FMStocks 2000 and other tests show that Windows 2000 and COM+ offer unmatched scalability. Even if you don't need your applications to fly faster and higher than all the rest, you can use COM+ to solve many of the maintenance problems you typically face, and Windows 2000 will keep you out of DLL Hell.
      More and more Web developers are building applications that use COM, and tying those applications together with ASP. With the proper coding techniques and the combination of Windows 2000 and COM+, you can build high-performance applications to serve your Web customers that are stable and easy to maintain.

Ken Spencer works for the 32X Tech Corporation (https://www.32X.com), which specializes in conducting high-quality seminars on Microsoft technology. You can reach Ken at kenspencer@32x.com.

From the July 2000 issue of MSDN Magazine.