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.
More on Porting Applications from MTS To COM+
Ted Pattison T
his month Iï¿½ll build on the March 2000 Basic Instincts
installment and continue to look at how COM+ will change the way you write your code. Iï¿½ll discuss new interfaces such as ContextInfo, IContextState, and SecurityCallContext that COM+ makes accessible to developers programming in Visual BasicÂ®. Iï¿½ll talk about whatï¿½s new with transactional programming, and show you how to make the most of declarative construction strings. Finally, Iï¿½ll examine how you can make use of application proxies for building client-side setup programs.
The COM+ Programming Model
When you create configured components, you should reference the COM+ Services type library in your ActiveXÂ® DLL project. Figure 1
should give you a sense of how much bigger that type library is than the MicrosoftÂ® Transaction Services (MTS) type library. Unlike the MTS type library, the COM+ type library includes the shared property manager along with many new types to support extensions to the programming model. New interfaces and enumerations support queued components, COM+ events, integrated security, and transactional compensators. Figure 1 Referencing the COM+ Services Type Library
In the MTS programming model, the ObjectContext interface provides one-stop shopping. Itï¿½s more or less the only interface you use to talk to the underlying platform. COM+ provides several new interfaces. Figure 2 lists the core COM+ interfaces that are used most often by programmers working with Visual Basic.
Unlike MTS, the COM+ programming model splits interfaces into two different categories: object context and call context. Object context is created when the Service Control Manager (SCM) creates a new context, and does not generally change across method calls. Call context contains call-specific information and flows across contexts as control is passed from one method to the next.
Programming Against the Object Context
Each context gets a special private object known as the object context (see Figure 3
). The object context is a system-provided COM object that holds context-related information. The object context also exposes methods that allow you to interact with the COM+ runtime. Some of these methods allow your objects to query the COM+ runtime for information. Other methods allow you to tell the COM+ runtime to do things on your behalf. For example, if an object has finished its work and wants to commit the current transaction, you would write the following code to interact with the COM+ runtime:
Dim oc As COMSVCSLib.ObjectContext
Set oc = GetObjectContext
A call to GetObjectContext allows an object to retrieve a reference to its object context. In this example, the object is using its object context to tell the COM+ runtime that it has completed its work and is voting for the transaction to be committed. Note that you donï¿½t have to create an explicit ObjectContext reference. Hereï¿½s a shorthand version of the previous code that does the same thing:
A call to GetObjectContext is performed very quickly because the COM+ runtime stores a reference to the object context in a place thatï¿½s fast to access, known as thread local storage. In general, youï¿½re not going to optimize your code by attempting to minimize the number of calls to GetObjectContext. For example, it doesnï¿½t really speed things up when an object calls GetObjectContext and stores the returned reference in a module-level variable. This approach makes your component code more complex without any noticeable performance improvements. Figure 3 Programming the ObjectContext Interface
Note that an object should never access an object context from a foreign context. In Figure 3, for example, Object2 should never acquire a reference to the object context for Context A. This means you should never pass a reference to an object context in a method. The only context an object should ever use is the one it acquires with a call to GetObjectContext.
So far, Iï¿½ve discussed programming the ObjectContext interface in COM+ exactly as you would in MTS. Now, itï¿½s time to see some things that are new to COM+ and WindowsÂ® 2000.
Thereï¿½s a new interface named ContextInfo that is specific to object context. This interface allows you to retrieve contextual information (primarily the identifying GUIDs) for the current context, activity, and transaction. This interface also allows you to retrieve the ITransaction reference to the current transaction. You can acquire a reference to a system-provided object that implements the ContextInfo interface through the object context. Hereï¿½s an example of using this interface to retrieve the GUID that identifies the current context:
Dim ci As COMSVCSLib.ContextInfo
Dim MyContextID As String
Set ci = GetObjectContext.ContextInfo
MyContextID = ci.GetContextId
As you can see, the object that implements ContextInfo is a property of the object context. This means there are two separate objects. In COM speak, there are two separate identities.
In addition to exposing a subobject that implements ContextInfo, the object context implements other interfaces in addition to ObjectContext. Some of these interfaces are only accessible through C++, but IContextState is accessible to Visual Basic.
The IContextState interface, like ContextInfo, is specific to object context. This interface is used to provide control over your transactions. You might notice that this interface exposes some functionality thatï¿½s duplicated in ObjectContext. Hereï¿½s how to acquire and use an IContextState reference.
Dim cs As COMSVCSLib.IContextState
Set cs = GetObjectContext
As you can see, you acquire the reference by calling GetObjectContext and casting the return value to the type IContextState. Moreover, once youï¿½ve called GetObjectContext you can cast back and forth between ObjectContext and IContextState. This works because they represent two interfaces implemented by a single COM identity.
Dim oc 'As ObjectContext, cs As IContextState
Set oc = GetObjectContext
Set cs = oc
In this example, the second line acquires an ObjectContext connection to the object context. The next line results in a call to QueryInterface to obtain a second reference to the same object. When you understand why this style of casting works the way it does, you begin to appreciate all the time and energy youï¿½ve put into learning about interface-based programming.
Understanding Call Context
While object context resides in a single place, call context travels along with the flow of a chain of method calls. Letï¿½s say youï¿½ve created a form-based client application on a computer running Windows 98. Assume this client application creates an object in a COM+ server application on a server running Windows 2000. Assume this object creates a few more objects, as shown in Figure 4
. When the client application makes a method call that flows across all three objects, there is a logical call chain that extends across three different computers. However, as the flow of control passes across context, process, and computer boundaries, lots of contextual information is flowing along with it. Figure 4 Object Creation
Causality has been part of COM since before either MTS or COM+ came along. When the client on the computer running Windows 98 invokes a method, the COM runtime creates a new causality and generates a GUID to identify it. You already know that as the call is transmitted across the wire, the methodï¿½s parameter values are sent along with it. However, you should also see that the causality GUID and other security-related information are automatically propagated as well. The causality remains alive while the flow of control moves from Object1 to Object2 to Object3 and then all the way back to the client. It seems like thereï¿½s one big call stack that extends across the network.
The primary motivation for caring about call context relates to security. There is one interface that specifically deals with call context called SecurityCallContext. This interface allows an object to determine if COM+ security has been turned on and who the caller is. Hereï¿½s an example of using SecurityCallContext.
Dim scc As COMSVCSLib.SecurityCallContext
Dim IsMyAppSecure As Boolean
Set scc = GetSecurityCallContext
IsMyAppSecure = scc.IsSecurityEnabled()
You might notice that a few methods are available in both SecurityCallContext and ObjectContext. While itï¿½s possible to program security with either interface, you should note that SecurityCallContext includes functionality not available in ObjectContext. For that reason, I recommend being consistent and using the SecurityCallContext interface for all your programmatic security.
Whatï¿½s New with Transactions
Many of you have been programming with declarative transactions in MTS. While programming COM+ transactions is similar, there are a few important differences you should understand. Iï¿½ll assume you know the basics of running MTS-style transactions with the DTC. If you need some more background, take a look at my article, "Writing OLTP Apps with Visual Basic, Microsoft Transaction Server, and SQL Server 7.0
," in the October 1999 issue of MSJ
You can control the outcome of an MTS transaction with four methods in the ObjectContext interface. These methods still work in COM+. However, COM+ introduced the IContextState interface, which has overlapping functionality. Figure 5
describes the four methods in this interface.
The methods listed in Figure 5
allow you to change the happy and done bits in the exact same way as the methods in the ObjectContext interface. For example, you can replace a call to SetComplete with a call to SetMyTransactionVote and SetDeactivateOnReturn. As long as you know how to set the happy bit and done bit to True, it doesnï¿½t really matter how you do it.
So what are the differences between using ObjectContext and IContextState to control a transaction? First, IContextState gives you individual control over the done and happy bits. Second, IContextState allows you to read the current state of the done and happy bits, which isnï¿½t possible through ObjectContext. Third, the COM+ runtime raises errors when you call an IContextState method on an object that isnï¿½t configured properly. Calls to GetMyTransactionVote and SetMyTransactionVote fail whenever the object isnï¿½t running in a transaction. Calls to SetDeactivateOnReturn and GetDeactivateOnReturn fail whenever the object doesnï¿½t support just-in-time activation.
So how do you chose between ObjectContext and IContextState when writing a transactional component? The truth is that it doesnï¿½t really matter much. Programmers who have been using MTS might prefer calling SetComplete and SetAbort because theyï¿½re familiar with that approach. Other programmers might prefer IContextState because they want to read the state of the happy and done bits. C++ programmers who are creating poolable objects that arenï¿½t involved in transactions should use IContextState because they want to manipulate the done bit without touching the happy bit. As long as you know how the COM+ runtime behaves and you know how to manipulate the happy and done bits, you can use either interface.
The AutoComplete Attribute
One of the most important things to keep in mind when creating the component for a root object is to release every transaction as soon as possible. The most common way to do this is by setting the done bit to True with a call to SetComplete, SetAbort, or SetDeactivateOnReturn. These calls are required because the default value of the done bit is False.
Each method of a configured component has an AutoComplete attribute that can be used to change the default value of the done bit to True. You can configure this attribute from the method properties dialog in the Component Services administration tool by selecting the "Automatically deactivate this object when this method returns" option.
The idea behind the AutoComplete attribute is that it sets the default behavior of a root object to deactivate automatically even when a programmer doesnï¿½t include a call to SetComplete or SetAbort. This attribute has the additional characteristic of automatically setting the happy bit to False when the method raises an error back to its caller. This feature is especially valuable for prewritten components that donï¿½t include calls to SetComplete or SetAbort. You might want to use such a component as the root of a transaction without modifying any code. You can configure each method for AutoComplete to ensure that each method will start and release a transaction.
If youï¿½re writing a transactional component, using the AutoComplete attribute is optional. It provides convenience, but youï¿½ll have more control if you manipulate the happy and done bits yourself. Moreover, itï¿½s important to remember that the AutoComplete attribute only changes the default value of the done bit to True; it doesnï¿½t guarantee that the done bit will remain True. You have to be watchful because calls to EnableCommit, DisableCommit, and SetDeactivateOnReturn can set the done bit back to False and keep a transaction alive longer than you want.
Beware of the SetAbort Bug
A bug specific to Visual Basic-based components can occur when a secondary object calls SetAbort and raises an error back to the root object. This bug is located in the lightweight proxy that COM+ builds to connect the root object to a secondary object in the same single-threaded apartment (STA). The bug overwrites any custom error description youï¿½re trying to propagate from the secondary object back to the root, and replaces it with the dreaded description method ï¿½~ï¿½ of object ï¿½~ï¿½ failed.
The workaround to this bug is to avoid calling SetAbort or SetDeactivateOnReturn(True) in secondary objects that raise errors. To roll back a transaction, a secondary object should call DisableCommit or SetMyTransactionVote(txAbort) instead. As long as the secondary object doesnï¿½t set its done bit to True, it can raise an error with a reliable description back to the root and roll back the transaction.
This bug raises two concerns. First, thereï¿½s lots of code written for MTS where secondary objects call SetAbort. This bug doesnï¿½t exist in MTS and many developers have been in the habit of calling SetAbort and raising an error when encountering a problem, so you might have to touch up MTS code when porting it to COM+.
Second, a component must be written to be either a root object or a secondary object. The root object should always call either SetComplete or SetAbort. But this is a catch-22 because secondary objects should never call SetAbort. The bug takes away your ability to write a component that can be used interchangeably as either the root object or a secondary object.
At the time of this writing, the COM+ team has created a fix for this bug, which is scheduled to appear in the first service pack for COM+. Once this bug has been removed from COM+, youï¿½ll be able to port your MTS code more easily and write a component that can be used as either the root object or a secondary object. For more information, check out Microsoft Knowledge Base articles Q255735 and Q255733
. These articles were not complete at press time, but should be available by the time you read this.
Using an Object Constructor String
Each configured component has a configurable object constructor string. You can use the constructor string for many purposes, but here Iï¿½m going to use it to hold an OLE DB database connection string. The two important configurable attributes in declarative construction are ConstructionEnabled and ConstructorString. You can configure these two attributes in the Component Services administration tool from the Activation tab of the component properties dialog.
When the SCM creates an object from a configured component that has been assigned a declarative constructor string, the COM+ runtime calls into the object and provides an opportunity to load the string value from RegDB. Letï¿½s walk through the details of how the COM+ runtime interacts with the new object.
During object creation, the SCM looks to see if the componentï¿½s ConstructionEnabled attribute is turned on. If it is, the SCM performs a QueryInterface on the new object to obtain a reference to an interface named IObjectConstruct. As long as your component implements this interface, the COM+ runtime calls the Construct method and passes a reference to a constructor object.
Private MyConnectionString As String
Sub IObjectConstruct_Construct(ByVal pCtorObj)
Dim cs As IObjectConstructString
Set cs = pCtorObj
MyConnectionString = cs.ConstructString
As you can see, this custom implementation of the Construct method loads the object constructor string into a module-level variable named MyConnectionString. Note that the reference passed by the pCtorObj parameter is cast to the IObjectConstruct interface. This interface exposes a single property, ConstructString, which makes it possible to obtain the configured value from RegDB and store it in a module-level variable. After the object has been created, the constructor string is available to any other method implementation in the class.
The most valuable aspect of using a constructor string is that the actual string value doesnï¿½t get compiled into the DLL. The administrator can easily reconfigure the connection string with the Component Services administration tool. If the database connection information changes, thereï¿½s no need to modify any code. This example demonstrates how COM+ lets you do more declaratively, lessening the need for modifying code and recompiling.
Keep in mind, however, that the information in the constructor string is not considered secure. You might want to use a connection string that uses a UDL file on which you can set NTFS security if you are storing sensitive information.
Building an Application Proxy
Like MTS, COM+ provides assistance with client-side configuration. COM+ allows you to create a setup program that automatically writes the required configuration information and copies type libraries to client computers. In COM+, this client-side setup program is known as an application proxy.
You can build an application proxy by running the Export command on a COM+ server application with the Component Services administration tool. When you run the Export command to create an application proxy, COM+ generates an MSI file (specifically, a Windows Installer file). Note that the setup program runs differently under Windows 2000 than it does on other DCOM-enabled versions of Windows. On computers running Windows 2000, it writes configuration data for CLSIDs and ProgIDs to RegDB, while writing other configuration data for type libraries, IIDs, AppIDs, and the RemoteServerName to the Windows registry. RegDB doesnï¿½t exist on earlier versions of Windows, so the setup program adds all entries to the Windows registry.
After an application proxy has been installed on a computer running Windows 2000, youï¿½ll notice that thereï¿½s a new application under the COM+ applications folder in the Component Services administration tool. Every computer running Windows 2000 has an Applications collection that contains application proxies, in addition to server applications and library applications. The difference is that an application proxy represents a set of CLSIDs that live on another machine.
There are a few more important issues concerning application proxies. First, you can explicitly set the remote server name that gets built into the application proxy. If you donï¿½t explicitly set this value, COM+ simply uses the computer on which the application proxy is built. If you want the application proxy to point to a different computer, you should change the remote server name with the Component Services administration tool before building the application proxy. You can adjust the remote server name value with the Options tab in the property dialog for My Computer in the Application Proxy RSN edit box.
Second, your client computers might require an updated version of the Windows Installer to install an application proxy. Windows NTÂ®, Windows 98, and Windows 95 all require updated versions of MSI. If you experience errors while installing an application proxy, you can find the required MSI update in any Platform SDK released January 2000 or later. There is an MSI update version 1.1 available for Windows NT and Windows 9x
. Once youï¿½ve installed this updated version of MSI, the application proxy should install without any problems.
Third, you should make sure all the relevant type libraries have been added to the server application before the application proxy is built. Make sure you add components to the server application with the Install command as opposed to the Import command. The Install command adds the type libraries embedded inside your ActiveX DLLs, while the Import command does not. If youï¿½ve built any custom type libraries using IDL and the MIDL compiler, you should also add them to the server application before building the application proxy. This can be a bit tricky the first time you do it because the Component Services administration tool only allows you to install a TLB file in a server application when youï¿½re installing components from a DLL.
Finally, there is a bug that prevents you from building application proxies in certain situations. As you know, the client computers need a copy of each type library, but they donï¿½t need the DLLs that hold your configured components. When you select the Remote Server Files option in an ActiveX DLL project, the Visual Basic IDE builds a standalone TLB file in addition to the type library it builds into the DLL.
You can add a standalone TLB file into a server application when you add the components from the DLL to prevent the application proxy from copying the entire DLL down to each client computer. However, thereï¿½s a problem. If you add the standalone TLB file generated by Visual Basic to a COM+ server application in addition to the DLL, COM+ cannot build the application proxy. Instead, when you attempt to build the application proxy youï¿½ll receive a fairly nondescriptive error message that has to do with some incompatibility between COM+ and type libraries generated by Visual Basic. This bug can be frustrating given the fact that things work correctly under MTS, but not under COM+. At the time of this writing I have no information as to when this bug will be addressed.
What are the implications of this bug? If you rely on application proxies to set up client-side computers, you must copy all your Visual Basic-based DLLs to the client computers. If youï¿½re installing the application proxy on a Web server, this might not present a problem. However, if you have a LAN-based application where desktop computers are connecting directly to your COM+ application, it can present a security hole. This is especially true if your DLL contains sensitive passwords. A user could bypass your applicationï¿½s security scheme by installing the DLL into a local COM+ server application and executing your methods in a nonsecure way.
One workaround is to move all your passwords out of your compiled DLLs and into declarative constructor strings or some other more secure storage. A second workaround is to avoid using COM+ application proxies when configuring client computers. Instead, you can copy type libraries and write all the required configuration information to client computers using some other way. Visual Basic provides an alternative technique for setting up client computers using VBR files and the CLIREG32.EXE utility.
In the next installment of Basic Instincts, Iï¿½ll discuss threading and concurrency in a COM+ application. While Visual Basic objects still run exclusively in STAs, COM+ provides a new optimized thread pooling scheme. Iï¿½ll compare the MTS and COM+ threading differences that affect response times and overall application throughput.
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 summer 2000.
From the May 2000 issue of MSDN Magazine.