Share via


Error Handling

Throwing Custom Exception Types from a Managed COM+ Server Application

Bob DeRemer

Code download available at:ExceptionsinCOM.exe(179 KB)

This article assumes you're familiar with COM+ and C#

SUMMARY

Exception handling semantics in .NET are based on type, so you can create custom exceptions that have their own properties and methods. In .NET, exceptions are first-class citizens, and since they're the built-in error handling mechanism, all .NET-compliant languages must support exceptions. In addition, COM+ services are available to .NET code as Enterprise Services, so you can leverage exceptions in your Enterprise Services design.

In this article the author describes custom exceptions, throwing exceptions across COM interop boundaries, and working with Enterprise Services.

Contents

Creating a Custom Exception Type
Exception Serialization and Remoting
Interop 101
Exception Propagation Through Interop
Enterprise Services Primer
Serviced Component Architecture
Optimization
Design Decisions
Solution 1
Solution 2
Conclusion

Imagine you're developing a system that is comprised of one or more Microsoft® .NET Framework components. As part of this design, you're also incorporating a custom exception type hierarchy to provide the user with strongly typed, robust exception information. Knowing that your components will be accessed remotely, you make sure to develop the custom exception types so they can be properly serialized. So far, so good. It isn't until you're in the middle of unit-testing your system that you notice a problem. The catch block in your client test program did not catch the anticipated custom exception type. Instead, you receive one of the all-too-familiar unhandled exception dialog boxes.

What do you do now? Let's first look at the point where you throw the exception. On inspection, it looks fine:

throw new CustomException ("Throwing CustomException type");

Next, look at the client code. You'll find that it looks good as well:

try { ExceptionGenerator x_gen = new ExceptionGenerator (); x_gen.GenerateCustomException (); } catch (CustomException x) { MessageBox.Show (x.Message); }

You are probably wondering the same thing I did when this first happened to me—where did my custom exception go?

The focus of this article is the generation of custom exceptions from serviced components that are configured to execute in a COM+ server application (as opposed to a library application). I will show you how to get your custom exceptions to propagate back to the client. I will also explain the fundamental architectural issues and how design decisions play a critical role in how you implement an exception-handling strategy in a serviced component architecture.

Creating a Custom Exception Type

Let's look at what is required to create a custom exception. I will not go into all the details of exceptions since there are many resources that cover the topic at great length, but I will review the basics of creating a custom exception type. For this exercise, I am going to create a custom exception type called CustomAppException (see Figure 1). Microsoft has two recommendations when creating a custom exception type: the name of the type should end in "Exception" and the type should derive from ApplicationException. By following these rules, you make it clear to the consumer of your class this is an exception type and that it is a custom app exception as opposed to a built-in system exception type.

Figure 1 CustomAppException

using System; using System.Runtime.Serialization; using System.Threading; using System.Diagnostics; namespace CustomExceptions { /// <summary> /// Custom exception class. /// </summary> [Serializable] public class CustomAppException : ApplicationException { #region Custom Exception Properties private string credentials; public string Credentials { get {return (credentials);} set {credentials = value;} } private int process_id; public int ProcessID { get {return (process_id);} set {process_id = value;} } private int thread_id; public int ThreadID { get {return (thread_id);} set {thread_id = value;} } #endregion #region Minimal Implementation public CustomAppException () { GetExecutionContext (); } public CustomAppException (string message) : base (message) { GetExecutionContext (); } public CustomAppException (string message, Exception inner) : base (message, inner) { GetExecutionContext (); } #endregion #region ISerializable Implementation protected CustomAppException (SerializationInfo info, StreamingContext context) : base (info, context) { // get the custom property out of the serialization stream and // set the object's property thread_id = info.GetInt32 ("ThreadID"); process_id = info.GetInt32 ("ProcessID"); credentials = info.GetString ("UserID"); } public override void GetObjectData (SerializationInfo info, StreamingContext context) { // add the custom property into the serialization stream info.AddValue ("ThreadID", thread_id); info.AddValue ("ProcessID", process_id); info.AddValue ("UserID", credentials); // call the base exception class to ensure proper serialization base.GetObjectData (info, context); } #endregion #region Helper methods private void GetExecutionContext () { credentials = Environment.UserDomainName + @"\" + Environment.UserName; thread_id = AppDomain.GetCurrentThreadId (); process_id = Process.GetCurrentProcess().Id; } #endregion } }

Exception Serialization and Remoting

The exception type is not yet complete. I still have to ensure that the exception data is serialized across AppDomain boundaries when it is thrown. Therefore, I must now add the [Serializable] custom attribute to the class to inform the serialization process that this type can be serialized. If you do not mark the exception with this attribute, a SerializationException will be thrown. The error message will state that the type is not marked as serializable. Second, if I have any custom properties, I must implement the ISerializable interface. By doing so, I am able to customize the serialization/deserialization process. Fortunately, the top-level Exception class already implements this interface, so I only need to override the existing base implementation. I also must implement a special constructor (which is used to deserialize the exception) and override the GetObjectData method, which is used to serialize any property data into the serialization stream (see Figure 2).

Figure 2 Adding Custom Attributes

protected CustomAppException (SerializationInfo info, StreamingContext context) : base (info, context) { // get the custom property out of the serialization stream and // set the object's properties thread_id = info.GetInt32 ("ThreadID"); process_id = info.GetInt32 ("ProcessID"); credentials = info.GetString ("UserID"); } public override void GetObjectData (SerializationInfo info, StreamingContext context) { // add the custom property into the serialization stream info.AddValue ("ThreadID", thread_id); info.AddValue ("ProcessID", process_id); info.AddValue ("UserID", credentials); // call the base exception class to ensure proper serialization base.GetObjectData (info, context); }

As you can see, my custom type has three custom properties that are being serialized: ThreadID, ProcessID, and UserID. The complete solution contains this custom exception type, plus the remoting server and client applications to throw a text exception across simple remoting boundaries. The code can be found at the link at the top of this article. I encourage you to experiment with this project by removing the Serializable attribute or commenting out the ISerializable overrides. You will see some interesting things when you examine the returned stack traces associated with the various exception types that will be thrown.

You may be wondering why all of this discussion on serialization is necessary, but understanding the path an object takes when it is serialized is critically important to understanding why you didn't get the expected exception type in the opening scenario. This will also help when you have to troubleshoot a problem in this area. Before diving into Enterprise Services and the serviced component architecture, I will review another .NET Framework technology called COM interoperability, or interop for short.

Interop 101

COM interoperability is a key technology provided by the .NET Framework that enables COM components to use managed code, and vice versa. One of the primary goals of interop is to make the transition between managed and unmanaged code as seamless as possible. This is a fairly complex task because the .NET programming model is significantly different than the COM model.

As you are probably aware, at the center of COM error handling is the well-known HRESULT. HRESULTs are 32-bit values composed of four sections of bits. The most significant bit is called the severity bit and is used to indicate success (severity = 0) or failure (severity = 1). A COM client is responsible for checking each COM call for a success or failure HRESULT value. COM components may also support more detailed error information by implementing the IErrorInfo interface. This is not a requirement of the programming model, however, so clients cannot always be sure that this type of error information will be available.

In contrast, the .NET Framework error-handling model is based on exceptions. Exceptions are an object-oriented, strongly typed mechanism for informing the calling code that an error or other unanticipated event has occurred. Exceptions provide rich error information and, unlike HRESULTS and IErrorInfo, exceptions are extensible. This enables you to derive your own exception type from an existing exception type—adding additional properties and methods as needed. Other benefits of custom exceptions include the ability to distinguish them by the type name and improved code readability and maintenance.

The challenge comes when error information has to pass from managed code into unmanaged code, and vice versa. Interop should make the transition as smooth as possible and bridge the gap between these two very different models for handling errors.

Exception Propagation Through Interop

While interop does a great job of masking the programming model differences, it cannot hide everything. In various scenarios, interop is unable to bridge that gap seamlessly. This can be the case when exceptions make the transition from managed code, through unmanaged code, and back into managed code.

As you shall see when I review the internals of Enterprise Services, the COM+ surrogate process (DLLHOST.EXE) and the services it provides are still written in unmanaged code. As a result, implicit transitions across the interop boundary may occur at times without your knowledge. Because of this stealth activity, I will first take a look at what will happen if my CustomAppException is thrown across the managed/unmanaged code boundary.

Let's go back to the remoting solution I used previously. If you examine the client code that calls the GenerateException, you'll see that it simply instantiates the ExceptionGenerator class, then calls the method shown here:

try { ExceptionGenerator generator = new ExceptionGenerator (); generator.GenerateException ("testing remote exception generation..."); }

Assume now that the call to GenerateException doesn't go directly to the hosted object instance on the server, but is intercepted and redirected through some unmanaged code. When an exception is thrown from ExceptionGenerator, the return path would look like that shown in Figure 3. The result is an ApplicationException, not a CustomAppException. This occurs because the transition from managed to unmanaged code preserves the HRESULT and error message, but does not preserve the type of the exception. When the exception transitions from unmanaged to managed, the common language runtime (CLR) has to determine what to do with it. The CLR no longer has a type to work with, only an HRESULT.

Figure 3 Exceptions Across Interop Boundaries

Figure 3** Exceptions Across Interop Boundaries **

All is not lost, however. The CLR has a large list of HRESULTs that it knows how to transform into specific exception types. Because I derived CustomAppException from ApplicationException, I inherited the base HRESULT value. This is why my type became an ApplicationException after passing through unmanaged code. Had I not derived my exception type from ApplicationException or another exception type for which the CLR has a built-in HRESULT, the returned type would have been a COMException (when the CLR gets an unrecognizable HRESULT across the interop boundary, it creates a COMException).

Now that I have shown what happens when custom exceptions cross the boundaries between managed and unmanaged code, let's look at Enterprise Services and serviced components.

Enterprise Services Primer

As you may know, COM+ is the successor to Microsoft Transaction Services (MTS) and provides enterprise-level services such as distributed transaction management, automatic transactions, object pooling, and just-in-time (JIT) activation to name a few. One of the goals of COM+ is to provide an architecture that enables the development of component-based, distributed, enterprise-scale systems that are scalable and can handle an increasing amount of throughput. These same services, when accessed from managed code, are referred to as Enterprise Services.

The latest version of COM+ (version 1.5 on Windows Server™ 2003 and Windows® XP) is still written in unmanaged code. As a result, if managed code is going to utilize the services provided by COM+, interop is going to play an integral part. As you'll see, however, interop is only part of the solution. The details are found in the integration that occurs between the System.EnterpriseServices namespace and the COM+ infrastructure. At the heart of this integration is the ServicedComponent class.

Serviced Component Architecture

The serviced component architecture relies on a number of .NET Framework-based technologies—a major one being .NET Remoting. Remoting provides a layered, extensible infrastructure that enables various services to be plugged into the call sequence. In addition, the ProxyAttribute and RealProxy classes play an important role in bringing a serviced component to life.

Before I dive into the plumbing, let's go back to the ExceptionGenerator example. I am going to convert the ExceptionGenerator into a serviced component. The modified version of ExceptionGenerator (now called EnterpriseExceptionGenerator) is shown in Figure 4. Some important things to note in that code figure include the using statement that references System.EnterpriseServices, the ServicedComponent base class, and the .NET interface declaration, IExceptionGenerator.

Figure 4 EnterpriseExceptionGenerator

using System; using System.EnterpriseServices; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // supply the COM+ info [assembly: ApplicationName("Enterprise Exception Test")] [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationAccessControl(false)] // key file required for GAC deployment [assembly: AssemblyKeyFile(@"..\..\EnterpriseExceptionsKey.snk")] namespace EnterpriseExceptions { public interface IExceptionGenerator { void GenerateException (string message); } // ServicedComponent-based exception generator [EventTrackingEnabled (true)] public class EnterpriseExceptionGenerator : ServicedComponent, IExceptionGenerator { public EnterpriseExceptionGenerator () { } #region IExceptionGenerator Implementation public void GenerateException (string message) { CustomAppException x = new CustomAppException ("Generating Enterprise CustomAppException"); throw (x); } #endregion } }

Now let's take a look at the example in Figure 5. There are several issues to note here. First, without importing the EnterpriseServices namespace, the compiler could not resolve my base class (ServicedComponent), nor could it resolve the various custom attributes that I am using to declare COM+ application settings, as well as any services the component may require. Second, if the class does not derive from ServicedComponent, it cannot be an Enterprise Services application and cannot use the services provided by COM+. Finally, note that I have chosen to implement a .NET interface on my serviced component because it will be used by both COM and .NET clients. As you will see later in this article, the implementation of an interface is a design decision which you must consider, based on the current and future usage requirements for your component.

Figure 5 Importing System.EnterpriseServices

// portions omitted for clarity using System.EnterpriseServices; // supply the COM+ info [assembly: ApplicationName("Enterprise Exception Test")] [assembly: ApplicationActivation(ActivationOption.Server)] // key file required for GAC deployment [assembly: AssemblyKeyFile(@"..\..\EnterpriseExceptionsKey.snk")] namespace EnterpriseExceptions { public interface IExceptionGenerator { void GenerateException (string message); } // ServicedComponent-based exception generator [EventTrackingEnabled (true)] public class EnterpriseExceptionGenerator : ServicedComponent, IExceptionGenerator { // remainder omitted for clarity } }

At the heart of Enterprise Services is the ServicedComponent class, which derives from ContextBoundObject, which derives from MarshalByRefObject. MarshalByRefObject is the base class for any object that will be accessed remotely. When a client attempts to access the serviced component remotely, it does so using a proxy object. If you look at the diagram shown in Figure 6, you can see that there are a number of layers on the client and server side of the Remoting architecture. The client's proxy object is called a transparent proxy. This object behaves like the real object, but in reality it forwards calls to the next layer, the Remoting proxy. The Remoting proxy is a custom RealProxy.

Figure 6 Remoting Architecture

Figure 6** Remoting Architecture **

Figure 7 shows the primary components in the serviced component architecture, which has many similarities to the Remoting architecture, since Remoting is fundamental to the implementation of Enterprise Services. First, the object activation must be customized, so a custom ProxyAttribute is used to intercept the call to new. By intercepting the object activation, COM+ can set up the associated unmanaged context object based on the information in the COM+ catalog.

Figure 7 Serviced Component Architecture

Figure 7** Serviced Component Architecture **

Second, the intercepted activation sequence also extends the RealProxy class to create two custom RealProxy objects called the serviced component proxy and remote serviced component proxy, respectively. The serviced component proxy is responsible for pre- and post-method-call interception. This is how a service such as automatic transactions is implemented. The remote serviced component proxy is primarily responsible for holding the context ID associated with the respective ServicedComponent object. I encourage you to examine the client and server stack traces for the RemotingExceptions and EnterpriseExceptions solutions, respectively.

When debugging the EnterpriseExceptions application, don't forget to set DLLHOST.EXE as the Start application and the appropriate command-line arguments so that you can set breakpoints in the ServicedComponent-derived class. In addition, if you see a line in your stack trace that says "[non-user code]", simply right-click to display an Options popup menu, then turn on the option to display non-user code.

One of the most common deployment scenarios for a COM+ server application is to generate a COM+ proxy installer, which implies that the interprocess communication is done using .NET Remoting on the client and server with a DCOM communications channel in the middle. The architecture for a COM+ library application is different because the object is being hosted in the client's process, so there is no DCOM IPC access and no need to construct the remote serviced component proxy. As a result, the method invocation path is different.

As you can see from the details (especially when you examine the stack traces), the architecture is rather complex, and there are numerous places where managed code interacts with unmanaged code—some documented, others undocumented. In addition, there are details about various objects and interfaces in the COM+ architecture that are not publicly available since they are currently intended for use only by the COM+ internal implementation. So where does the CustomAppException get changed into a base exception type (ApplicationException, COMException, for example), as opposed to your desired type? The answer lies in some optimization performed in the serviced component architecture.

Optimization

Enterprise Services (COM+) are designed to help you build scalable, high-performance enterprise systems. As a result, certain optimizations are made when accessing serviced components running in a COM+ server application. These optimizations utilize COM where possible in order to avoid the additional overhead incurred by the remoting serialization logic. This optimization, however, is the reason the custom exception is being transformed into a more generic exception type (such as ApplicationException or COMException). There is a workaround, but it requires you to make a conscious decision about how the public interface of the serviced component will be exposed. In the next section, I am going to show you the design decisions you must understand, and what effect they have on this optimization.

Design Decisions

As with any software project, when designing a serviced component you must consider who your clients are. One specific aspect to consider is the type of client you plan to support. You must know whether your component will need to support only .NET clients, or if it must support both .NET and COM clients. If you're fortunate enough to be working on a project that does not have to consider legacy products or existing customers using COM-based software, then you may be able to forego support for COM clients. If not, then you must design your serviced component such that it is easily accessible from both COM and the .NET Framework. This interaction is at the heart of interop, and complete coverage of it is far beyond the scope of this article.

Suffice it to say that you must design your component to be COM friendly. Being COM friendly means choosing proper data types for method parameters, being aware of versioning issues and the impact they have on the COM programming model, and deciding whether to expose your component using a .NET class interface or a .NET interface, to name a few criteria. The subject of .NET class interface versus .NET interface, coupled with method signatures, is at the root of the optimization being performed here.

When a serviced component is registered with COM+, the relevant metadata in the assembly is retrieved using the functionality in the System.EnterpriseServices.RegistrationHelper namespace. This includes generating a type library and injecting this information into the COM+ catalog. By default, .NET managed code does not expose functionality via interfaces. Instead, the functionality is exposed directly from the class definition. As a result, COM clients cannot early-bind to a .NET class by default.

If you plan on supporting COM clients, you have two options. The first option involves utilizing the default behavior for the .NET Framework, which is to expose a .NET class interface. A .NET class interface is an IDispatch-derived interface generated by the CLR for COM clients to access a class's public members. The generation of this metadata is controlled by the ClassInterfaceAttribute class, which takes a ClassInterfaceType enumeration value as a parameter. Figure 8 lists the ClassInterfaceType values along with a description of each option. Using AutoDispatch is the default option, and it allows late-binding only, so COM clients are shielded from the underlying class layout. Unfortunately, it also incurs the performance penalties associated with late-binding. This is the recommended option when you want to use the class interface generated by the common language runtime.

Figure 8 ClassInterfaceType Enumeration

Value Description
AutoDispatch Clients can only late-bind to the class interface (this is the default)
AutoDual Allows clients to bind to the internal class layout, which will result in a class that is difficult to modify without breaking COM clients
None No class interface is generated

I recommend option two, which requires that you expose your functionality using a .NET interface. The primary reason for doing so is to insulate COM clients from underlying class layout changes, thus avoiding the likelihood that a client will break because of a .NET class modification. This approach will result in the interface being included in the generated type library, and likewise, the COM+ catalog. This also enables COM clients to early-bind to this interface, which results in lower overhead compared to the IDispatch-derived .NET class interface. An additional benefit to publishing a .NET interface is that both COM and .NET consumers can use your component because both programming models understand what an interface is. The downside is that a serviced component that exposes a .NET interface may be accessed differently under the hood. When making a remote call through the interface, COM+ will attempt to optimize the call by using DCOM serialization where appropriate as opposed to Remoting serialization. As a result, an exception thrown under this scenario will be subject to the interop conversions discussed earlier.

While you can use the auto-generated .NET class interface approach, I recommend explicitly declaring an interface. Even though it's more work up front, the long-term benefits can be huge. Let me now show you two solutions which implement a .NET interface and still preserve the exception type.

Solution 1

This solution requires that the interface methods be designed in such a way that COM+ will not attempt to optimize the call through DCOM. This requires what I will call a complex method signature—namely, one that contains a type, such as Object, that cannot be serialized by standard interop marshaling. This will force the method invocation to use Remoting serialization. The following code snippet shows the interface declaration for this solution and contains both simple and complex method signatures, such as the StringBuilder parameter:

public interface IExceptionGenerator { // simple method signature void GenerateException (string message); // complex method signature void GenerateException_ComplexSignature (string message, StringBuilder ComplexParam); }

When I execute the client, I will generate an ApplicationException and a CustomAppException, each with their own unique stack trace, shown in Figure 9 and Figure 10, respectively. Depending on the method signature, I will get a different exception type back and it will have taken a different path. Figure 11 shows the client application after receiving the custom exception properties that originated while running in the DLLHOST.EXE process. For complete details, refer to the EnterpriseExceptions_1 directory in the sample code download for this article.

Figure 10 Custom Exception

Figure 9 Application Exception

Figure 11 Custom Exception Data

Figure 11** Custom Exception Data **

Solution 2

The second option is a two-pronged approach. It exposes the functionality using a formal interface declaration implemented on a thin managed wrapper class. The wrapper then delegates all calls into the primary .NET class, which is also the class made accessible to managed clients (see Figure 12). This is the most work because it requires the implementation of a wrapper class, but it is the most versatile because it exposes a formal interface for COM clients and exposes the flexible, strongly typed .NET class for managed clients. In addition, I added a ToXml method to the CustomAppException class to enable the wrapper class to catch a CustomAppException and store it in an ApplicationException's Message property. The CLR will then serialize it across to the COM client in the IErrorInfo description. For complete details, refer to the EnterpriseExceptions_2 directory in the sample code.

Figure 12 Using a Wrapper Class

using System; using System.EnterpriseServices; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; // supply the COM+ info [assembly: ApplicationName("Enterprise Exception Solution #2")] [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationAccessControl(false)] // key file required for GAC deployment [assembly: AssemblyKeyFile(@"..\..\EnterpriseExceptionsKey.snk")] // // EnterpriseExceptions Solution #1 // implement a .NET Interface where method has a complex signature //--------------------------------------------------------------------- namespace EnterpriseExceptions { // formal interface declaration public interface IExceptionGenerator { void GenerateException (string message); } // // class for COM clients that delegates to the managed class //----------------------------------------------------------------- [ClassInterface (ClassInterfaceType.None)] public class COMEnterpriseExceptionGenerator : IExceptionGenerator { private EnterpriseExceptionGenerator exception--generator = null; public COMEnterpriseExceptionGenerator () { exception--generator = new EnterpriseExceptionGenerator (); } #region IExceptionGenerator Members public void GenerateException (string message) { try { exception--generator.GenerateException ("generating exception from COMEnterpriseExceptionGenerator"); } catch (CustomAppException x) { // catch custom exception, serialize contents to XML // string, then rethrow throw (new ApplicationException (x.ToXml ())); } } #endregion } // // ServicedComponent-based exception generator for managed clients //--------------------------------------------------------------------- [EventTrackingEnabled (true)] [ClassInterfaceAttribute (ClassInterfaceType.None)] public class EnterpriseExceptionGenerator : ServicedComponent { public EnterpriseExceptionGenerator () { } public void GenerateException (string message) { StringBuilder msg = new StringBuilder (); msg.AppendFormat ("[message: {0}]", message); CustomAppException x = new CustomAppException (msg.ToString ()); throw (x); } } }

Conclusion

This article was designed to help you to better understand exceptions in the context of the serviced component architecture, as well as propagation of exceptions across interop boundaries. I have presented two solutions to this problem. If you know of others, I would love to hear from you.

As you can see, writing a custom exception is fairly straightforward. There are nuances involved when propagating through the different technologies, and these can require quite a bit of attention. Also, the decision of whether or not to declare a formal interface can pose a monumental challenge. I believe a good design will utilize both class inheritance and interface implementation where they are appropriate.

For related articles see:
Writing Serviced Components
Extending RealProxy

For background information see:
Exception Management Architecture Guide
.NET and COM: The Complete Interoperability Guide by Adam Nathan (Sams Publishing, 2002)
Understanding Enterprise Services (COM+) in .NET

Bob DeRemer is a Senior Software Development Engineer with Lighthammer Software Development Corporation where he architects and develops enterprise application software for the manufacturing industry. When he's not with his family or flying, you'll find his nose buried in the latest publications on .NET technologies. Reach him at bob.deremer@lighthammer.com.