Cirrus Introduction

From The Oxygene Language Wiki

Jump to:navigation, search

This is a Language topic
Feel free to add your notes to this topic below.



With the May 2009 Release (3.0.19) of Delphi Prism, we've introduced Cirrus, a layer for the Delphi Prism compiler that introduces Aspect Oriented Programming.

Delphi Prism's AOP makes it possible to change the behavior of code, add or remove fields, properties, events or methods and even extra classes, by applying special kinds of attributes - Aspects - to classes or members.

Aspects are written in Prism itself, compiled into a separate library, and are reusable by different projects. They are fairly simple to write.

To write an aspect

You have to create a new class library, reference the RemObjects.Oxygene.Cirrus library, and create a new attribute class. This class should descend from System.Attribute and have a regular AttributeUsage() attribute to denote where it can be applied. The only difference from a regular attribute is that aspects implement one of the special interfaces defined by Cirrus, such as IMethodImplementationDecorator, as in the sample below.

Aspect attributes are loaded and instantiated by the compiler at compile time, and are given the chance to take very powerful influence on the code the compiler is generating.

In the example below, we are creating an aspect to decorate methods of the class it is applied to. This is done through the IMethodImplementationDecorator interface, which requires one single method, HandleImplementation to be implemented by the aspect. The compiler will call this method after a method body (implementation) was generated and allows the aspect to take influence on the generated code and to change or augment it:

namespace ClassLibrary1;

interface
uses
  RemObjects.Oxygene.Cirrus;

type
  [AttributeUsage(AttributeTargets.Class or AttributeTargets.Struct)]
  LogToMethodAttribute = public class(System.Attribute, IMethodImplementationDecorator)
  public
    [Aspect:AutoInjectIntoTarget]
    class method LogMessage(aEnter: Boolean; aName: String; Args: Array of object); 
    
    method HandleImplementation(Services: IServices; aMethod: IMethodDefinition);
  end;
  
implementation

class method LogToMethodAttribute.LogMessage(aEnter: Boolean; aName: String; 
  Args: Array of object); 
begin
  if aEnter then 
    Console.WriteLine('Entering ' + aName)
  else
    Console.WriteLine('Exiting ' + aName);
end;

method LogToMethodAttribute.HandleImplementation(Services: IServices; aMethod: IMethodDefinition);
begin
  if String.Equals(aMethod.Name, 'LogMessage', 
    StringComparison.OrdinalIgnoreCase) then exit;
  if String.Equals(aMethod.Name, '.ctor', 
    StringComparison.OrdinalIgnoreCase) then exit;

  // Cool use of anonymous methods here....
  aMethod.SetBody(Services,
    method begin
      LogMessage(true, Aspects.MethodName, Aspects.GetParameters);
      try
        Aspects.OriginalBody;
      finally
        LogMessage(false, Aspects.MethodName, Aspects.GetParameters);
      end;
    end);
end;

end.

In the fragment above, our aspect compares the method name to ".ctor" and "LogMessage" (we do not want to augment those), and if they do not match, it adds LogMessage calls around the original method, protected by a try/finally.

The Aspects class is a special Compiler Magic Class provided by Cirrus that allows the aspect to take control of the code it is being applied to. Among other things, you see that it can query for the method name and the parameters, but also the body of the method in question, as written in the original source for the class.

By calling SetBody() on the method, the aspect can replace the body of the generated code (in this case, by taking the original body and surrounding our calls to LogMessage). Note how the new method body is being provided as plain, readable Prism code, in form of an extension to the anonymous method syntax.

It is also worth noting that the LogMessage method of our aspect has an aspect of its own applied. The [aspect:AutoInjectIntoClass] is an aspect defined by Cirrus itself, and it's intended for use within aspects only. It causes the member (in this case the LogMessage method) to be added to the class the aspect is applied to.

This is necessary since our aspect makes use of LogMessage() in the new and augmented method body - but no such method is likely to exist in the target object. Without AutoInjectIntoClass, all the logic for LogMessage would need to be crammed into the SetBody call - making it potentially harder to read, but also potentially duplicating a lot of code and logic.

The following application makes use of our Log aspect:

namespace CirrusTest;

interface

uses
  ClassLibrary1,  
  System.Linq;


type
  [Aspect:LogToMethod]
  ConsoleApp = class
  public
    class method Main;
    class method Test(S: string);
  end;

implementation

class method ConsoleApp.Main;
begin
  // add your own code here
  Console.WriteLine('Hello World.');
  Test('Input for Test');
end;
class method ConsoleApp.Test(S: string);
begin
  Console.WriteLine('TEST: '+s);
end;

end.

We simply created a new console app that references the aspect library we created above, as well as the Cirrus library.

Note: Because aspects are applied at compile time, the final executable will not depend on the aspect library or on Cirrus anymore.

Running and debugging this program will output a log message at the beginning and end of each method, just as specified in our designed aspect.

Entering Main
Hello World.
Entering Test
TEST: Input for Test
Exiting Test
Exiting Main

When this code is run, the LogMessage method is been injected into our class, and the reference section lists only mscorlib - RemObjects.Oxygene.Cirrus.dll and our aspect .dll do not need to be deployed for the application to run.


See Also


Oxygene-48.png

Area: Oxygene Language
Compiler version: Oxygene 5

Language GlossaryKeywordsTypesFAQHow To

Navigation
Areas
More
Toolbox