hat is it about software engineers that make them wish they were hardware engineers? Since the invention of the function, programmers have spent most of their time (and most of their employers' money) attempting to design systems that are merely snap-together models of parts built by others, arranged in unique shapes and draped in pleasing colors. Functions, templates, classes, components, and so on, ad infinitum, are all attempts by software engineers to build themselves "Software Integrated Circuits" just like the hardware designers' electronic analogs.
I blame Lego. That satisfying click that you get when two bricks (aka components) snap together is addictive and has driven many a programmer to invent one new mechanism of encapsulation and reuse after another. The latest such advance is called aspect-oriented programming (AOP). At its core, AOP is a way of laying components, one on top of the other, to achieve levels of reuse not available in other kinds of component-based development. This layering happens on the call stack between client and object, and the effect is to create a specific environment for your object. This environment is chiefly what an AOP programmer is after, as you'll see as you read through this article.
There are two parts to the code sample provided with this article: the COM part and the Microsoft® .NET part. The COM part creates an infrastructure for adding aspects to COM objects, provides a UI to configure a class's aspects, and provides a sample aspect implementation built on the plumbing we provide. The .NET part shows how to use the infrastructure built into .NET to do the same thing as the COM version did, but with less code and more options. It also provides a sample aspect to fit into that infrastructure. We will describe all of the code later in the article.
What is Aspect-oriented Programming?
Normally, objects are "glued" together using lines of code. Create this object. Create that object. Set a property on that object whose value is this object. Sprinkle in some user data. Stir. Execute when the runtime reaches 450 degrees. The problem with hooking together components in this manner is that you're going to be spending a lot of time writing the same bits of code when it comes to implementing the individual methods. It goes something along these lines: log this method to a file for debugging, run a security check, start a transaction, open a database connection, remember to catch C++ exceptions or Win32® structured exceptions so that they can be translated into COM exceptions, and validate parameters. And let's not forget you have to remember to tear down at the end of the method whatever it was you set up at the beginning.
The reason that this kind of duplication often occurs is that developers are trained to design systems based on the nouns in software press releases. If you're building a banking system, you've got an Account class and a Customer class, both of which gather the details necessary to be uniquely themselves in one place, but both of which have the need, on a per-method basis, to do logging, security checking, transaction management, and so on. The difference is that the logging and such are aspects of the system that are orthogonal to the specific application. Everyone needs them. Everyone writes the code. Everyone hates it.
Well, not everyone... Oh, everyone needs to use these services, and everyone hates to write duplicate code, but not everyone needs to write the code. For example, COM+ and .NET programmers have something called attributed programming, also known as declarative programming. This allows a programmer to decorate a type or a method with an attribute that declares the need for a service to be provided by a runtime. Some of the services provided by COM+, for example, include role-based security, Just-in-Time activation, distributed transaction management, and marshaling. When calling the method, the runtime stacks up a set of objects (called interceptors if you're a COM+ programmer, or message sinks if you're using .NET) that get between the client and the server to provide the service on each method, requiring no code to be written by the component developer. This is the simplest form of aspect-oriented programming, a paradigm invented in the 1990s at Xerox PARC by Gregor Kiczales (see http://www.parc.xerox.com/csl/groups/ sda/publications/papers/Kiczales-ECOOP97/for-web.pdf).
In the realm of AOP, COM+ interceptors are aspects that are associated with components via metadata. The runtime uses the metadata to compose the stack of aspects, typically at object creation time. When the client calls a method, the particular aspects each get a turn at processing the call and performing their service, until finally the object's method is called. On the return trip, each aspect is given a chance to unwind. In this way, you can factor those same lines of code that you find yourself writing in each method of each of your components into aspects, and let the runtime stack them up. This set of aspects work together to provide a context for the component's method to execute. The context provides the method implementation in an environment where its actions take on a special meaning.
![Figure 1 Object Nestled Safely in a Context](https://i-blog.csdnimg.cn/blog_migrate/20e73c2999011da8ee28b71b9776b150.png) Figure 1 Object Nestled Safely in a Context
For example, Figure 1 shows an object nestled safely in a context that is providing error propagation, transaction management, and synchronization. Exactly as a Win32 console application program can assume a context where a console exists and calls to printf will be shown on it, an AOP object can assume that a transaction has been established and calls to the database will be part of that transaction. If there were any problems setting up these services (for example there were no resources available to establish a transaction) the object would never be called, saving it the need to worry about that, too.
General-purpose AOP
While COM+ provides most of the services needed of AOP, it lacks one very important detail needed to use it as a general-purpose AOP environment: the ability to define custom aspects. For example, if role-based security doesn't do it for you, you can't implement role-playing-based security (as much as you may want vampires to guard your objects). If programmers had that ability, many COM idioms could be implemented with an AOP framework. Figure 2 provides a short list of examples.
Designing an Aspect Framework
Of course, once such a framework was conceived, we had to build it. We wanted this framework to have the following features:
- A runtime for stringing aspects together between a client and an object.
- User-defined aspects implemented as COM components.
- Metadata descriptions of which aspects were associated with each COM component, just like the COM+ Catalog.
- A method that clients can use to activate components with the aspects in place.
The idea behind our AOP framework is simple. The key to it is interception and delegation. The art of interception lies in making the caller believe that the interface pointer that it holds is pointing to the object it requested, whereas in reality it is a pointer to an interceptor obtained via one of the activation techniques described later in the article. It's the interceptor's job to implement the same interfaces as the target component, but to delegate all calls through the stack of aspects associated with the component. When a method is called, the interceptor will give each aspect the opportunity to pre- and post-process the call, as well as the ability to propagate or cancel the current call.
The AOP framework performs two distinct steps, component activation and method invocation. In component activation, the AOP framework builds the stack of aspect implementation objects and returns a reference to an interceptor instead of a reference to the actual object. In method invocation, when the caller makes method calls on the interceptor, the interceptor delegates the calls to all the registered aspects for preprocessing of [in] and [in,out] arguments on the call stack, and delivers the actual call to the object. Then it delivers the call to the aspects for post-processing by passing the return value of the call that the component returned, the [in,out] and [out] arguments on the call stack.
Aspects as COM Objects
In our AOP framework, an aspect is a COM class that implements the IAspect interface shown in Figure 3. The framework calls the IAspect::PreProcess method of all the specified aspects before delivering the method call to the actual underlying component instance (referred to as delegatee from here on). It passes the identity of the delegatee, IID of the interface, name of the method, vtbl slot at which the method occurred, and an enumerator over the [in] and [in,out] arguments to the aspect. If an aspect returns a failure HRESULT from PreProcess, the framework does not deliver the call to the delegatee, effectively cancelling the call.
Upon a successful return from the preprocessing by the aspects, the framework then delivers the actual call to the delegatee. Regardless of the return HRESULT from the delegatee, the framework then calls the IAspect::PostProcess method, passing the HRESULT returned by the delegatee and all the parameters as the PostProcess method, except that this time the enumerator is built over the [out], [in,out] and [out,retval] arguments.
Figure 4 shows how to write a call-tracing aspect that traces all the caller-supplied arguments passed to the delegatee's method.
Now that we have a framework to call aspects and an aspect to play with, we need to have a mechanism for stringing them together. We'll do that as the object is activated.
Object Activation
In spite of the fact that we're stacking an arbitrary number of aspects between the client and the object, the client should be able to create the object and call methods on it in the same fashion as it does without interception. Unfortunately, COM doesn't support arbitrary extensibility code injected into its chief activation API, CoCreateInstance, without some pretty fancy hackery (which is what Microsoft Transaction Services had to do until it was integrated into the COM plumbing and renamed COM+). However, COM does provide an activation API that's fully extensible: GetObject in Visual Basic® (or CoGetObject if you're a C++ programmer). We built our AOP activation code based on this API using a custom moniker.
A COM moniker is a piece of code that translates an arbitrary string (called a display name) into a COM object, whether that means creating a new one, digging one out of a file, or downloading it from the moon. Our AOP moniker takes the metadata describing the aspects associated with the class in question, creates an instance of the class, builds the stack of aspects, hooks them all up via an AOP interceptor, and then hands the interceptor back to the client. Here's an example:
Private Sub Form_Load()
Set myfoo = GetObject("AOActivator:c:/AopFoo.xml")
myfoo.DoSomethingFooish
End Sub
Notice that, except for obtaining the instance of Foo, the client doesn't need to do anything special to use the component. It still implements the same interfaces and, even more importantly, it still has the same semantics despite the fact that the AopFoo.xml file could associate any number of aspects with this particular instance of Foo.
Implementing a custom COM moniker is somewhat of a black art, mostly involving intimate knowledge of OLE trivia from bygone days. Luckily, the vast majority of the implementation is boilerplate and the COM community has long ago set down the basic implementation of monikers into an ATL class called CComMoniker. (The COM moniker framework is available at http://www.sellsbrothers.com/tools.) Using the framework, all we really had to worry about was implementing ParseDisplayName, which is a boring method that parses the custom display name syntax, and BindToObject, the part of the moniker that allows us to activate the COM object indicated by the display name provided by the client (see Figure 5).
Notice that the code in Figure 5 doesn't show the difficult part—creating and initializing the interceptor. What makes this difficult is not the interceptor itself, but what the interceptor has to do. Remember, for our generic AOP framework to function generically, it must be able to respond to QueryInterface with the exact same set of interfaces as any component being wrapped. And the interface returned must be able to take the call stack provided by the client for each method, pass it to all of the aspects, and pass it along to the component itself, leaving the arguments intact—no matter how many there are or what types they are. This is a difficult job involving lots of __declspec(naked) and ASM thunks.
Luckily, because the COM community is a mature one, we can again stand on the shoulders of giants and make use of the Universal Delegator (UD), a COM component built by Keith Brown to perform this very task. Keith described his UD in the two part series in MSJ called "Building a Lightweight COM Interception Framework, Part I: The Universal Delegator," and Part II: "The Guts of the UD". We used Keith's UD to implement our AOP framework, which reduces the "magic" part of the BindToObject implementation to the code in Figure 6.
To wrap our target component for use by the client, we performed the following four steps:
- We created the actual component using the CLSID of the component passed to the moniker earlier in the metadata XML file.
- We created a DelegatorHook object for intercepting the QueryInterface calls to the object. The hook is responsible for routing method calls through each aspect.
- Next, we created the UD object and retrieved the IDelegatorFactory interface.
- Using IDelegatorFactory, we called CreateDelegator, passing the interface of the actual object, the delegator hook, the IID of the interface the original caller requested (riidResult), and the pointer to the interface pointer (ppvResult). The delegator returns a pointer to the interceptor that calls our delegator hook on each call.
![Figure 7 COM AOP Architecture](https://i-blog.csdnimg.cn/blog_migrate/aecb55bc96828118f6dbc046a3c765bc.png) Figure 7 COM AOP Architecture
The result is shown in Figure 7. From this point on, the client can use the interceptor as the actual interface pointer from the target component. As calls are made, they are routed through the aspects on the way to the target component.
Aspect Builder
To activate the component with all of the aspects strung together properly, our AOP moniker relies on an XML file to describe the component and the associated aspects. The simple format merely contains the CLSID of the component and the CLSIDs of the aspect components. Figure 8 shows an example that wraps the Microsoft FlexGrid Control with two aspects. In order to ease the task of creating instances of AOP metadata, we created Aspect Builder (as shown in Figure 9).
![Figure 9 Aspect Builder](https://i-blog.csdnimg.cn/blog_migrate/1aac215d8763fb176f8b9878c04321a3.png) Figure 9 Aspect Builder
Aspect Builder enumerates all the aspects registered on the machine and displays each one of them as cloud-shaped items in the list view on the left. The client area of the Aspect Builder contains a visual representation of the component. You can double-click on it (or use the corresponding menu item) and specify the component's ProgID. Once you've chosen a component, you can drag and drop the aspects into the client area, adding aspects to your component's AOP metadata.
To produce the XML format necessary to feed to the AOP moniker, choose Compile from the Tools menu and the metadata will be shown in the bottom pane. You can verify that the metadata is indeed correct by doing the actual scripting in the Verify Aspects pane. You can save these compiled XML instances to the disk and reload them with Aspect Builder as well.
Aspects in .NET
While the Aspect Builder makes things very pretty, storing aspect metadata separately from the component makes programming AOP in COM less convenient than it could be. Unfortunately, COM's metadata leaves quite a bit to be desired when it comes to extensibility, which is why we felt the need to separate the metadata from the class in the first place. However, .NET, the heir apparent to COM, has no such issue. .NET's metadata is fully extensible and therefore has all the necessary underpinnings for associating aspects directly with the class itself via attributes. For example, given a custom .NET attribute, we can readily associate a call-tracing attribute with a .NET method:
public class Bar {
[CallTracingAttribute("In Bar ctor")]
public Bar() {}
[CallTracingAttribute("In Bar.Calculate method")]
public int Calculate(int x, int y){ return x + y; }
}
Notice the square brackets containing the CallTracingAttribute and the string to be output when the method is accessed. This is the attribute syntax that associates the custom metadata with the two methods of Bar.
Like our AOP framework in COM, attributes in .NET are classes by components in .NET. A custom attribute in .NET is implemented with a class that derives from Attribute, as shown here:
using System;
using System.Reflection;
[AttributeUsage( AttributeTargets.ClassMembers,
AllowMultiple = false )]
public class CallTracingAttribute : Attribute {
public CallTracingAttribute(string s) {
Console.WriteLine(s);
}
}
Our attribute class itself has attributes, which modify its behavior. In this case, we're requiring that the attribute be associated only with methods, and not assemblies, classes, or fields, and that each method may have only one trace attribute associated with it.
Once you've associated the attribute with the method, you're halfway home. To provide for AOP, you also need access to the call stack before and after each method to establish the environment necessary for the component to execute. This requires an interceptor and a context in which the component can live. In COM, we did this by requiring the client to activate using our AOP moniker. Luckily, .NET has built-in hooks that don't require the client to do anything special at all.
Context-bound Objects
The key to interception in .NET—as in COM—is to provide for a context for the COM component. In COM+ and in our custom AOP framework, we provided that context by stacking aspects between the client and the object to establish the properties of the context for the component before the method was executed. In .NET, a context is provided for any class that derives from System.ContextBoundObject:
public class LikeToLiveAlone : ContextBoundObject {...}
When an instance of the LikeToLiveAlone class is activated, the .NET runtime will automatically create a separate context for it to live in, establishing an interceptor from which to hang our own aspects. The .NET interceptor is a combination of two objects, the transparent proxy and the real proxy. The transparent proxy acts just like the target object, as our COM AOP interceptor did, and serializes the call stack into an object called a message, which it hands to the real proxy. The real proxy takes the message and sends it to the first message sink to process. The first message sink pre-processes the message, sends it along to the next message sink in the stack of message sinks between client and object, and then post-processes the message. The next message sink does the same, and so on until the stack builder sink is reached, which deserializes the message back into a call stack, calls the object, serializes the outbound parameters and the return value, and returns to the previous message sink. This call chain is shown in Figure 10.
![Figure 10 Interception](https://i-blog.csdnimg.cn/blog_migrate/02d069e7d2f6e871cc34da4e4dcdaf79.png) Figure 10 Interception
To participate in this chain of message sinks we first need to update our attribute to participate with context-bound objects by deriving our attribute from ContextAttribute (instead of just Attribute) and by providing something called a context property:
[AttributeUsage(AttributeTargets.Class)]
public class CallTracingAttribute : ContextAttribute {
public CallTracingAttribute() :
base("CallTrace") {}
public override void
GetPropertiesForNewContext
(IConstructionCallMessage ccm) {
ccm.ContextProperties.Add(new
CallTracingProperty());
}
•••
}
When the object is activated, the GetPropertiesForNewContext method is called for each context attribute. This allows us to add our own context property to the list of properties associated with the new context that's being created for our object. A context property allows us to associate a message sink with an object in the chain of message sinks. The property class acts as a factory for our aspect message sinks by implementing IContextObject and IContextObjectSink:
public class CallTracingProperty : IContextProperty,
IContributeObjectSink {
public IMessageSink GetObjectSink(MarshalByRefObject o,
IMessageSink next) {
return new CallTracingAspect(next);
}
•••
}
The process of proxy-creating the attribute, which creates the context property, which then creates the message sink, is shown in Figure 11.
![Figure 11 .NET MessageSink Creation](https://i-blog.csdnimg.cn/blog_migrate/45116ee9cf50aa4c37a87678b0a0becb.png) Figure 11 .NET MessageSink Creation
.NET Aspects
Once everything is attached properly, each call enters our aspect's implementation of IMessageSink. SyncProcessMessage, which allows us to pre- and post-process the message, is shown in Figure 12.
Finally, the context-bound class that wants to associate itself with the call-——tracing aspect declares its preference using the CallTracingAttribute:
[AOP.Experiments.CallTracingAttribute()]
public class TraceMe : ContextBoundObject {
public int ReturnFive(String s) {
return 5;
}
}
Notice that we're associating our context attribute with a class and not with each method. The .NET context architecture will automatically notify us on each method, so our call-—tracing attribute has all of the information it needs, saving us the trouble of manually associating our attribute with each method, as we did with our plain attribute before. When the client class instantiates the class and calls a method, the aspect comes to life:
public class client {
public static void Main() {
TraceMe traceMe = new TraceMe();
traceMe.ReturnFive("stringArg");
}
}
When they're run, our client and aspect-oriented object output the following:
PreProcessing: TraceMe.ReturnFive(s= stringArg)
PostProcessing: TraceMe.ReturnFive( returned [5])
Aspect and Context
So far, our simple aspect hasn't really lived up to the intended ideal of AOP. While it's true that aspects can be utilized for pre- and post-processing of method calls alone, what's really interesting is how an aspect affects the execution of the method itself. For example, the COM+ transactional aspect causes all resource providers that are used during the object's method to participate in the same transaction, allowing the method to abort all activities by merely aborting the transaction provided by the COM+ transactional aspect. To allow for this, COM+ aspects augment the COM call context, which provides a rallying point for all components interested in accessing the current transaction. Likewise, .NET provides an extensible call context that we can make use of to allow a method. For example, we can allow an object wrapped in our call-tracing aspect to have the ability to add things to the stream of trace messages by putting ourselves into the .NET context, as you can see here:
internal class CallTracingAspect : IMessageSink {
public static string ContextName {
get { return "CallTrace" ; }
}
private void Preprocess(IMessage msg) {
•••
// set us up in the call context
call.LogicalCallContext.SetData(ContextName, this);
}
•••
}
Once we've added the aspect to the call context, the method can pull us out again and participate in the tracing:
[CallTracingAttribute()]
public class TraceMe : ContextBoundObject {
public int ReturnFive(String s) {
Object obj =
CallContext.GetData(CallTracingAspect.ContextName) ;
CallTracingAspect aspect = (CallTracingAspect)obj ;
aspect.Trace("Inside MethodCall");
return 5;
}
By providing a way to augment the call context, .NET allows aspects to set up a real environment for the objects. In our example, the object is allowed to add tracing statements to the stream without any need to know where the stream is going, how the stream is established, or when it will be torn down, much like the COM+ transactional aspect, as shown here:
PreProcessing: TraceMe.ReturnFive(s= stringArg)
During: TraceMe.ReturnFive: Inside MethodCall
PostProcessing: TraceMe.ReturnFive( returned [5])
Wrap-up
Aspect-oriented programming allows developers to encapsulate usage of common services across components in the same way that you encapsulate the components themselves. By using metadata and interceptors, you can stack arbitrary services between the client and the object, semi-seamlessly in COM and seamlessly in .NET. The aspects we've shown in this article have access to call stacks on the way into and out of method calls and they provide an augmented context in which our objects live. While still in its infancy when compared to structured programming or object-oriented programming, native support for AOP in .NET provides a valuable tool for pursuing Lego-like software fantasies.
|