MSDN Magazine > Issues and Downloads > 2001 > October >  COM+ Integration: How .NET Enterprise Services ...
COM+ Integration: How .NET Enterprise Services Can Help You Build Distributed Applications
MSDN Magazine

COM+ Integration: How .NET Enterprise Services Can Help You Build Distributed Applications

Tim Ewald
This article assumes you're familiar with COM and C++
Level of Difficulty     1   2   3 
Download the code for this article: ComPlus.exe (41KB)
Browse the code for this article at Code Center: CFGClass
SUMMARY The .NET Common Language Runtime (CLR) is Microsoft's next-generation component technology. The CLR is a replacement for COM, but not for COM+. COM+, now called .NET Enterprise Services, is the Microsoft object runtime environment for scalable system development.
      This article explains how to implement and deploy COM+ configured classes using the CLR, how to access object context and call context, and the rules for managing context-relative object references. Also discussed are ways to manage precious resources such as data connections and pooled objects, and the relationship between COM+ and the new .NET remoting architecture.
I n July 2000, Microsoft officially announced Microsoft® .NET as its new platform for Web-centric development. The heart of .NET is the common language runtime (CLR), the next-generation component technology that is the heir to (and backward-compatible with) COM. The CLR improves on COM in numerous ways, most importantly by providing metadata that completely describes the types a component implements, including which other components they rely on. All of this type information is accessible via reflection and extensible via custom attributes. This makes it easy to build all sorts of sophisticated plumbing that COM developers have wanted for years, like automatic support for object serialization.
      The CLR is a replacement for COM, but not for COM+. COM+ is a runtime environment for objects that provides a set of services intended to make building scalable distributed systems easier. If you found COM+ useful when you were working with COM, chances are you'll find it useful when you start working with the CLR, too. This article is about COM+ programming with the CLR. I'll start with a brief review of COM+, now part of .NET Enterprise Services.

A Quick Review of COM+

      All of the COM+ services are implemented in terms of contexts. A context is a space within a process that provides runtime services to the object or objects that reside there. When an object (or thread) in one context calls to an object in another context, the method is intercepted by a proxy, as shown in Figure 1. The proxy gives the COM+ runtime the opportunity to preprocess and postprocess the invocation and execute the necessary service code to satisfy the target object's needs, such as call serialization, declarative transaction management, and the like.

Figure 1 Contexts and Interception
Figure 1 Contexts and Interception

      COM+ offers runtime services to classes that need them. If a class needs a service, COM+ makes sure that instances of that class always reside in contexts that provide that service. For example, if a class uses a declarative transaction, COM+ ensures that each instance lives in a context that provides a distributed transaction for the object to use. A class indicates its desire to use a COM+ runtime service by marking itself with a declarative attribute. Each service defines one or more declarative attributes that are used to control its behavior.
      Classes that use COM+ runtime services and are marked with one or more declarative attributes, are called configured classes. Classes that do not use COM+ services and are not marked with any declarative attributes, are called nonconfigured classes. COM+ stores a configured class's declarative attribute values in a metadata repository called the catalog. The catalog is divided into applications, which control how instances of configured classes are mapped to processes. There are two sorts of applications, library applications and server applications. Each configured class belongs to a single application. If a configured class is in a library application, new objects of that type are always created in their client's process. If a configured class is in a server application, new objects of that type are always created in an instance of the dedicated surrogate process, dllhost.exe.
      When a configured class is instantiated, the object creation plumbing has to ensure that the new object ends up in a context that provides the runtime services it needs. When a new object is created, the object creation plumbing examines its service requirements, as expressed by its class's declarative attribute values stored in the COM+ catalog. If the object's requirements can be met by its creator's context, the new object lives there, and calls to it will not be intercepted. If the new object's requirements cannot be met by its creator's context, a new context is created that provides the runtime services required by the object. The new object lives in the new context, and calls to it will be intercepted in order to ensure that the object always has access to the services it needs. When a nonconfigured class is instantiated, the object creation plumbing always puts the new object in its creator's context.

Figure 2 Object Context and Call Context Objects
Figure 2 Object Context and Call Context Objects

      An object within a particular context may want to interact with the runtime services its context provides. COM+ facilitates this by modeling each context as a COM object called an object context object (object context for short). It also models each logical thread of execution (causality) as a COM object called a call context object (call context for short). Any code executing within a context can acquire references to these objects by calling CoGetObjectContext and CoGetCallContext, respectively (or the Visual Basic 6.0 equivalents, GetObjectContext and GetSecurityCallContext). Figure 2 illustrates this architecture.

Implementing COM+ Configured Classes with the CLR

      Like COM, the CLR relies on COM+ to provide runtime services that may be useful to developers building scalable applications. Using the CLR to implement COM+ configured classes is easier and, in some cases, more efficient than implementing them with COM. It is very important to understand that the integration of these two technologies is not achieved solely through interoperability with COM. That is, while you could use the CLR to implement classic COM components that use COM+, the degree of integration between the CLR and COM+ actually goes much deeper, producing a programming model that is better integrated with other CLR technologies like remoting and code access security. The CLR-managed API to COM+ is defined by the types in the System.EnterpriseServices namespace. CLR classes that rely on COM+ use these types to define their declarative attributes and to interact with object and call context.
      The most important type in the System.EnterpriseService namespace is ServicedComponent. All CLR classes that use COM+ runtime services must extend ServicedComponent, as shown here:
using System.EnterpriseServices;

namespace ESExample
{
  public class MyCfgClass : ServicedComponent
  {
    public MyCfgClass() {}
    •••
  }
}
      The presence of ServicedComponent in a class's inheritance hierarchy tells the common language runtime's object creation plumbing that the class is configured and that its instances may need to live in new COM+ contexts. Classes that derive from ServicedComponent must be public and concrete, and must provide a public default constructor.

Declarative Attributes

      The decision about whether a new instance of a ServicedComponent-derived configured class needs a new context depends on the class's declarative attributes. Remember that a configured class's declarative attributes are stored in the COM+ catalog. With a COM-based configured class, the task of populating the catalog with the appropriate information was left to you. You either had to write a script that accessed the catalog programmatically or you had to install the configured class manually using the Component Services Explorer management console.
      With a CLR-based configured class, you can take advantage of the CLR's intrinsic support for extensible metadata and embed the relevant declarative attributes directly in a class's definition. This information can be extracted from a configured class's definition at installation time and can be used to populate the COM+ catalog. The System.EnterpriseServices namespace defines a set of attribute classes that can be used to describe a configured class's COM+ runtime service requirements. The most important attribute classes are listed in Figure 3 (there are additional attributes for controlling other COM+ services like queued components).
      This code shows how a class attribute is used.
using System.EnterpriseServices;

namespace ESExample
{
  [ Transaction(TransactionOption.Required) ]
  public class MyTxCfgClass : ServicedComponent
  {
    public MyTxCfgClass() {}
    •••
  }
}
In this code, the presence of the Transaction attribute indicates that the MyTxCfgClass requires the use of a COM+-managed distributed transaction.
      When information about a configured class is added to the COM+ catalog, the class is always added to a COM+ application. With traditional COM-based configured classes, there is no way to establish this association in the class code. With CLR-based configured classes, the extensibility of the CLR's metadata can help, too. The System.EnterpriseServices namespace defines a set of attribute classes that can be used to describe which application the configured classes in an assembly belong to. Figure 4 lists the most important assembly-level attributes (there are additional attributes for controlling other COM+ services, such as queued components).
      The following code shows how application attributes are used.
using System.EnterpriseServices;

[assembly: ApplicationName("MyApp")]
[assembly: ApplicationActivation(ActivationOption.Library)]

namespace ESExample
{
  [ Transaction(TransactionOption.Required) ]
  public class MyTxCfgClass : ServicedComponent
  {
    public MyTxCfgClass() {}
    •••
  }
}
      In this case, the MyTxCfgClass will be compiled into an assembly which is marked as implementing classes that belong in a library application called MyApp. If the constructor argument to the ApplicationActivation attribute were changed to ActivationOption.Server, the assembly would be marked as implementing classes that belong in a server application. Note that these application attributes do not provide complete control over all the application settings COM+ offers. For instance, there is no way to use an application attribute to specify what security principal a server application should run as. This makes sense because encoding a user name and password as metadata in a CLR assembly is not a very good idea.

Implementing IObjectConstruct and IObjectControl

      Some COM+ runtime services, including object construction, object pooling, and just-in-time (JIT) activation, need to invoke standard methods on the objects that use them. Specifically, the object construction service invokes the Construct method defined by the IObjectConstruct interface to deliver a class's constructor string to each new instance. The object pooling and JIT activation services invoke the Activate, Deactivate, and CanBePooled methods defined by the IObjectControl interface to notify an object about events in its lifecycle. While you might expect that in order to use these services you must explicitly implement the IObjectConstruct and IObjectControl interfaces on your ServicedComponent-derived configured class, that is not the case. The ServicedComponent class already provides default implementations of all four of these methods and a class can simply override the methods in which it is interested.
      For example, the following code shows how a pooled object might selectively implement the methods of IObjectControl.
namespace ESExample
{
  [ ObjectPooling(true, 5, 20) ]
  public class MyPooledCfgClass : ServicedComponent
  {
    public MyPooledCfgClass() {}

    // override CanBePooled so that instances can be
    // reused, use default implementations of Activate
    // and Deactivate provided by ServicedComponent
    public override bool CanBePooled() { return true; }
  }
}
In this example, the MyPooledCfgClass class is marked with the ObjectPooling attribute, with a minimum pool size of 5 and a maximum pool size of 20. The class overrides the definition of CanBePooled and provides an implementation that returns true so that instances of the class can be reused. The default implementation returns false, meaning that instances of a class should not be reused. The class does not override either Activate or Deactivate; it relies on the default implementations provided by ServicedComponent. The default implementation of Activate returns successfully, thereby allowing activation to proceed. The default implementation of Deactivate does nothing.
      It is important to note that while the ServicedComponent class provides default implementations for the methods of both IObjectConstruct and IObjectControl, it does not actually implement either interface. Instead, the interfaces are implemented by an internal class (ProxyTearoff), which forwards calls directly to your object. This may seem like an inconsequential implementation detail, but you'll soon see why it is important.

ContextUtil and SecurityCallContext

      Configured classes often need to interact with the runtime services they are using. COM+ implements its services via interception, and objects interact with the services they use indirectly via object context. The System.EnterpriseServices.ContextUtil class wraps CoGetObjectContext, the COM+ API for retrieving object context. ContextUtil exposes the functionality of the COM+ object context object using a set of static methods and properties listed in Figure 5.
      The code in Figure 6 shows how the ContextUtil class is used to manipulate the happy and done bits that control the outcome of a COM+ declarative transaction. Specifically, it sets the ContextUtil.DeactivateOnReturn property to true, turning the done bit on and indicating that the declarative transaction will complete at the end of the method call. Then it sets the ContextUtil.MyTransactionVote property to TransactionVote.Abort, turning off the happy bit and indicating that the declarative transaction should abort if the method does not complete successfully—in other words, if an exception is thrown. Then, after working with some databases, the method sets the ContextUtil.MyTransactionVote property to TransactionVote.Commit, turning the happy bit back on and indicating to the COM+ interception plumbing that the declarative transaction should commit.
      The COM+ security service exposes its behavior through call context, not through object context. The System.EnterpriseServices.SecurityCallContext class wraps CoGetCallContext, the COM+ API for retrieving call context. SecurityCallContext exposes the functionality of the call context object using the set of methods and properties listed in Figure 7.
      The static property, CurrentCall, returns a reference to a SecurityCallContext object for the current call. Figure 8 provides an example in which the GetCallerName method uses the SecurityCallContext class to retrieve and return the name of the current caller. (Note that the ApplicationAccessControl attribute is used to turn on support for role-based security in the MyApp library application.)

Compiling a Configured Class

      Once a configured class is implemented, it must be compiled. Compiling the code is easy, but there are two things to remember. First, the COM+ integration plumbing requires that the compiled assembly be strongly named. To create a strongly named assembly, you must generate a key by running the Strong Name Utility, sn.exe. Then you must reference the key, which is stored in a file, from within your component's code using an assembly-level attribute called AssemblyKeyFileAttribute from the System.Reflection namespace, as shown here:
using System.EnterpriseServices;
using System.Reflection;

[assembly: ApplicationName("MyApp")]
[assembly: ApplicationActivation(ActivationOption.Library)]

// AssemblyKeyFile attribute references keyfile generated
// by sn.exe - assembly will have strong name
[assembly: AssemblyKeyFile("keyfile")]

namespace ESExample
{
•••
}
      Second, when you compile your strongly named assembly, you must reference the assembly that exports the types in the System.EnterpriseServices namespace, System.EnterpriseServices.dll. Here are the commands for generating a key and compiling a configured class:
sn -k keyfile
csc /out:ESExample.dll /t:library
    /r:System.EnterpriseServices.dll MyCfgClass.cs

Deploying a Configured Class

      After a CLR-based configured class has been compiled, its assembly needs to be deployed. You can do this by running the Services Installation Utility, regsvcs.exe, from the command line, as shown here:
regsvcs ESExample.dll
This tool does three things. First, it registers the CLR assembly as a COM component (as if you had run the Assembly Registration Utility, regasm.exe). Second, it emits a COM type library (as if you had run the Assembly to Type Library Converter, tlbexp.exe) and uses it to deploy the configured classes your assembly implements in the COM+ catalog. Regsvcs.exe will create the target application described by your assembly's ApplicationName and ApplicationActivation attributes by default. (You can override this behavior using command-line switches.) Third, it uses the .NET reflection APIs to interrogate the metadata for the configured classes your assembly implements and uses that information to programmatically update the COM+ catalog so that each class will have the appropriate declarative attribute settings.
      If your configured class does not have a particular declarative attribute, regsvcs.exe will use a default value instead. In general, the default option is to turn off a runtime service. This is good because it minimizes overhead by ensuring that instances of your configured classes only use the services they explicitly said they needed. There are some circumstances, however, where regsvcs.exe will infer values for attributes based on the settings of other attributes. For instance, the MyTxCfgClass in Figure 6 is not marked with either the Synchronization or JustInTimeActivation attribute. But because the COM+ declarative transaction service relies on both of those services, they will be enabled anyway when the class is registered in the COM+ catalog. If the MyTxCfgClass were marked with these additional attributes, their values would have to be compatible with the value specified by the Transaction attribute, as shown here.
// all these attributes
// are compatible
[ Transaction(TransactionOption.Required) ]
[ Synchronization(SynchronizationOption.Required) ]
[ JustInTimeActivation(true) ]
public class MyTxCfgClass : ServicedComponent
{
  public MyTxCfgClass() {}
  •••
}
      So, are the declarative attributes that adorn your ServicedComponent-derived class simply a way to initialize the COM+ catalog with the right information, or is something more going on here? It turns out that in many cases, the underlying plumbing of the managed COM+ API reflects not against the COM+ catalog, but against the declarative attributes in your class's code to see which services your class uses. It uses this information to tailor its behavior.
      For instance, the ProxyTearoff class I mentioned earlier will only forward calls to the methods of IObjectControl to an instance of your class if your class is marked with the ObjectPooling or JustInTimeActivation attribute from the System.EnterpriseServices namespace. If it is not, your object won't get the notifications, no matter what the COM+ catalog says.
      In practical terms this means that the COM+ catalog should be viewed as a copy of your class's declarative attributes, not the real source. The only attributes you should ever be changing in the COM+ catalog are things which are deployment-specific, like object construct strings and security settings. This is a sign of the times. The COM+ catalog exists because COM did not have an extensible metadata model from binary components. The CLR does have one, so you should expect to see a future version of the COM+ runtime services that relies entirely on inline CLR metadata and abandons the catalog once and for all.
      It is worth noting that, by default, your class's public methods do not appear in the COM+ catalog. That's because they do not appear in the type library that regsvcs.exe generates and uses for installation. This is the default behavior whenever you export a CLR type for use from COM. You can change this behavior either by defining and implementing interfaces on your configured class or by marking your class with System.ClassInterfaceAttribute.
      In addition to deploying an assembly that implements CLR-based configured classes in COM+, you have to deploy it so that it can be loaded by CLR-based clients. You can deploy your assembly privately, making it available to a single client, by copying it into that client's directory (or whatever directory that client is configured to look in). Or you can deploy your assembly publicly, making it available to all clients on a machine, by installing it in the Global Assembly Cache (GAC). You can do this by running the command-line Global Assembly Cache Utility, gacutil.exe, with the -i switch.
gacutil -i ESExample.dll
      If your CLR-based configured classes are deployed in a COM+ server application, the COM+ plumbing needs to be able to load your assembly independent of the directory in which the actual client is running. In this case, your assembly must be deployed in the GAC; otherwise, attempts to instantiate your configured class will fail.
      One of the goals of the .NET Framework is to simplify the installation of systems by supporting XCOPY deployment. The term XCOPY refers to an MS-DOS command-line utility for copying files and directories from one location to another. The purpose of XCOPY deployment is to make it possible to install applications on remote servers without executing any code there. To support XCOPY deployment, the CLR/COM+ integration plumbing allows you to forego the execution of regsvcs.exe at installation time and takes care of registration the first time the CLR-based configured class is used.
      There are some shortcomings to the XCOPY deployment of CLR-based configured classes that you should be aware of. First, the COM+ catalog can only be updated by users included in the Administrators role defined by the COM+ System application. By default, only local administrators are included in that role. As a result, in order to successfully register your configured components, the code that first uses your CLR-based configured classes has to have administrative privileges. If it does not, registration will fail. (Of course, anyone who runs the regsvcs.exe tool has to have these privileges, too.)
      The second shortcoming is that, while you can deploy an assembly in the GAC simply by copying it to the appropriate directory, if you are deploying your assembly there so your configured class can be installed in a COM+ server application there is probably additional work to do. Specifically, you probably need to set the COM+ server application's security principal, which cannot be done automatically using metadata for the reason I just mentioned. If you are deploying your configured classes in a COM+ library server, this is a nonissue as long as your assembly is deployed where the clients that need it can find it. For instance, if you were using the CLR to implement a configured class that will only be used by an ASP .NET application, you could use XCOPY deployment to install the class in a COM+ library application and the assembly in the ASP .NET application's .\bin subdirectory, where it could be found by the ASP .NET code.

Implementing a Client

      Once a CLR-based configured class is compiled and deployed, you're ready to use it. From a client's perspective, there is nothing special about a configured class; the fact that it uses COM+ runtime services is irrelevant. This code shows a simple client that uses the MyTxCfgClass class defined earlier:
using ESExample;

public class Client
{
  public static void Main(string[] args)
  {
    MyTxCfgClass tcc = new MyTxCfgClass();
    ... // use object as desired
  }
}
      Of course, as with all CLR code, the client has to be given a reference to the configured class's assembly when it is compiled:
csc /r:ESExample.dll Client.cs

Subtle Complexities

      As you can see at this point, implementing COM+ configured classes with the .NET CLR is pretty straightforward. The types in the System.EnterpriseServices namespace provide a managed API to COM+ that simplifies the use of runtime services. The runtime services themselves haven't changed; they still work exactly the way experienced COM+ developers are used to. Having said that, it is important to note that integrating COM+ and the CLR is not without its share of subtle complexity. There are several issues that make writing COM+ code with CLR-based languages more complicated than I have implied so far. Most of these issues arise because the CLR is not COM, and it can do things that COM could not. None of the issues are insurmountable; you just need to understand how certain CLR features like static methods and garbage collection impact COM+ programming.
      Before I delve into these issues, there are two details you need to know about the relationship between CLR classes and COM+ contexts. First, calls to instances of classes that derive from ServicedComponent are intercepted at COM+ context boundaries. These objects are called context-bound. Calls to instances of classes that do not derive from ServicedComponent are not intercepted at COM+ context boundaries. These objects are called context-agile. CLR objects are always context-agile by default. They only become context-bound when you derive from ServicedComponent. (This still has nothing to do with .NET remoting context, which I'll talk about shortly.) Figure 9 illustrates this architecture.

Figure 9 Context Objects
Figure 9 Context Objects

      It is interesting to note that CLR objects behave exactly the opposite of how COM objects behave with respect to COM+ context. With COM, calls to an object are always intercepted by default; all objects are context-bound. A COM object can only be context-agile if it aggregates the freethreaded marshaler (FTM) and is not the first object created in a new COM+ context (that is, it is not the distinguished object), in which case, calls to it will not be intercepted. The benefit of this new approach is that it reduces interception overhead by ensuring that calls will be pre- and post-processed only when absolutely necessary. Specifically, if an instance of a configured class returns a reference to an instance of a nonconfigured class (an ADO. NET DataSet object, for example), calls to that object will not be intercepted. And the DataSet object doesn't have to do anything special, it just works this way.
      The second thing you need to know is that, in addition to reducing cross-context interception to the cases where it is really needed, the CLR/COM+ integration plumbing avoids converting between managed and native types when it can. Calling from managed CLR code out to native COM code is known to be relatively expensive. A significant part of that expense is converting types back and forth—mostly in converting CLR strings to and from COM BSTRs. While calls across COM+ context boundaries do need to invoke some native code, the plumbing is smart enough to avoid the data type conversions as long as both the caller and the callee are implemented using the CLR and are in the same process. Someday the COM+ runtime services themselves will be reimplemented as managed code, at which point transitioning to native code will not be an issue. Until then, however, this optimization helps make COM+ interception faster.

Static Methods and Properties

      Now that you know a little more about how CLR code relates to contexts, consider this question: if a CLR-based configured class has static methods or property accessors, what context should they execute in? The answer is the caller's. This may not seem intuitive, but it makes sense when you consider that static members are not object-specific and so do not need to be accessed from the specific context where a given object resides. For example, the configured class in Figure 10 has a static method, Two, and a static property, Four. The code for these methods will always execute in the caller's context. Calls to the instance method, One, or property, Three, will always execute in the object's context.
      At this point you may be wondering what happens if you store a reference to an object in a static property while executing in one context, and then try to use it from another context. In classic COM-based COM+ programming, storing a raw object reference in a global variable, which is all a static property is, would wreak all kinds of havoc because you cannot take an object reference out of its original context without marshaling it. With CLR-based COM+ code, this is no longer an issue. The CLR wraps each instance of a configured class with a very thin proxy so that even other objects in the same context do not have a direct reference to the class instance. If code executing in one context stores a reference to a CLR-based configured class in a static property, it is really storing a reference to this special proxy. If code executing in another context retrieves the reference from the static property and starts using it, the special proxy detects the change and marshals the reference it holds so that the right interception occurs. This is a very elegant solution which, in essence, means that you can store references to instances of CLR-based configured classes anywhere and use them from anywhere, and the right thing will happen.

Managing Precious Resources

      As long as I'm on the topic of managing object references, there is another issue you need to be aware of. One of the major features of the CLR is its reliance on garbage collection to reclaim memory. This is a big deal because it helps avoid memory leaks by automatically detecting and freeing chunks of memory that are no longer in use. Unfortunately, while garbage collection makes managing memory easier, it also makes managing other kinds of resources, like objects, more difficult. Since managing resources efficiently is key to building scalable systems, which is what COM+ is designed for, you need to understand how to clean up precious CLR-based resources properly.
      Consider a CLR class that uses a SqlConnection object to talk to a database, as shown here:
public void UseADatabase(void)
{
  System.Data.SqlClient.SqlConnection =
      new System.Data.SqlClient.SqlConnection(...);
    ... // use connection to execute statements
}
At the end of the UseADatabase method, the reference to the SqlConnection object is released. SqlConnection objects wrap underlying database connections and are pooled by the ADO .NET infrastructure for efficiency's sake (in Beta 1 this was done with COM+ object pooling; in Beta 2, it is done using an internal mechanism that works just like COM+ object pooling). The question is, when does the connection held by the SqlConnection object return to the pool? Not until the garbage collector kicks in, realizes that the SqlConnection object is no longer being used, and calls its finalizer. The implementation of the finalizer returns the underlying connection to the pool, and then the SqlConnection object's memory is reclaimed.
      The problem with this is that database connections are very precious resources and you can't afford to have them floating around in memory, just out of reach, until the garbage collector reclaims them for you. There needs to be some way to reclaim these kinds of resources immediately. One way to do this would be to force the garbage collector to execute, as shown here:
public void UseADatabase(void)
{
  System.Data.SqlClient.SqlConnection conn =
      new System.Data.SqlClient.SqlConnection(...);
  ... // use connection to execute statements
  conn = null;
  System.GC.Collect();    
}
However, this approach is very heavy-handed and has all sorts of other implications.
      A better approach is to call the SqlConnection object's implementation of IDisposable.Dispose. The IDisposable interface in the System namespace models the behavior of an object that has precious resources that should be cleaned up explicitly by its client instead of implicitly by the garbage collector. The SqlConnection class's implementation of Dispose returns the underlying database connection to the pool immediately (in fact, this is the same method the class's finalizer calls).
      The following code shows a new version of the UseADatabase method that explicitly cleans up its SqlConnection.
public void UseADatabase(void)
{
  System.Data.SqlClient.SqlConnection conn =
      new System.Data.SqlClient.SqlConnection(...);
  try
  {
    ... // use connection to execute statements
  }
  finally
  {
    conn.Dispose();
  }
}
Note that the call to Dispose appears inside a finally block to ensure that it always executes, even if an exception is thrown.
      So how does this relate to COM+ and configured classes? First, your configured classes are likely to use database connections and other precious resources, so if you want your system to scale, you have to know how to clean up those resources properly. Second, all CLR-based configured classes implement IDisposable.Dispose, courtesy of their base class, ServicedComponent. You need to know what happens when a client calls Dispose. The behavior of the default implementation depends on which runtime services your configured class uses. If it does not use object pooling activation, Dispose simply calls an object's finalizer immediately (it will not be called again when the object's memory is reclaimed by the garbage collector). If your configured class uses object pooling but not JIT activation, Dispose calls Deactivate to notify an object that it is leaving its current context. Then it calls CanBePooled to ask the object if it wants to be reused or destroyed. If CanBePooled returns true, the object is returned to its class's pool. If it returns false, the object's finalizer is called (it will not be called again when the object's memory is reclaimed by the garbage collector). It is particularly important that clients call Dispose for pooled objects, as shown here.
public void UseAPooledObject(void)
{
  MyPooledCfgClass pcc = new MyPooledCfgClass();
  try
  {
    ... // use pooled object to do something
  }
  finally
  {
    pcc.Dispose();
  }
}
      If they do not call Dispose, the objects (such as the database connections described), will not be returned to their pools until the garbage collector reclaims them. Finally, if for some reason you choose to implement your own version of Dispose on a configured class, you must call your base class's implementation if you want the behavior described.
[ Synchronization(SynchronizedOption.Required) ]
public class MyCfgClass : ServicedComponent
{
  public new void Dispose()
  {
    ... // do whatever
    base.Dispose(); // call base to clean up
  }
}
      At this point you may be wondering about JIT activation and deactivating an object at the end of every method call. Doesn't that resolve these resource management issues? The general answer is no. Deactivating an object at the end of every call forces an object to release any resource it holds in data members, but it also forces a client that wants to use the same object more than once to reacquire an object for each method call. You can achieve the benefit of this model without the overhead simply by not storing any resources in data members. If you do choose to use JIT activation and deactivate your object at the end of every call, be aware that a client's call to Dispose will actually force a reactivation of your object simply to deactivate and finalize it again. In general, the shift to the CLR doesn't change the rules about using JIT activation. You should avoid it unless it is required by some other runtime services (such as the declarative transaction service) because it is never more efficient, and is often less efficient than simply managing precious resources carefully yourself.

The Future

      The ServicedComponent class derives from System.ContextBoundObject, which derives from System.MarshalByRefObject, the base class that makes objects accessible via the .NET remoting infrastructure. There are two benefits to building CLR/COM+ integration plumbing directly on top of the new remoting layer. First, you can use the remoting layer's built-in support for SOAP to access remote objects that use COM+ runtime services today. You cannot, however, use SOAP to propagate a declarative transaction from one process to another (not that you'd want to, but it's the principle of the thing). Second, it opens the way for a new version of the COM+ runtime services implemented entirely in terms of the CLR remoting layer's brand-new context infrastructure, which COM+ doesn't use today. This will happen sometime in the future. When it does, services will seamlessly integrate with SOAP or any other transport, and the context architecture will be fully extensible. Until then, the CLR makes many of the details of COM+ programming simpler, which is almost always a step in the right direction.
For related articles see:
Windows XP: Make Your Components More Robust with COM+ 1.5 Innovations
House of COM: Migrating Native Code to the .NET CLR
the \samples\technologies\componentservices subdirectory of the .NET Framework SDK
Transactional COM+: Building Scalable Applications by Tim Ewald (Addison-Wesley, 2001)

Tim Ewald is a Principal Scientist at DevelopMentor and author of the recently published Transactional COM+: Building Scalable Applications (Addison-Wesley, 2001).

From the October 2001 issue of MSDN Magazine.

Page view tracker