380 likes | 570 Views
AOP and Aspect C++. presentation by Igor Kotenkov based on presentation by Andreas Gal, Daniel Lohmann and Olaf Spinczyk. Contents. AOP with pure C++ AspectC++ AspectC++ Tool Support Summary. Templates. Templates can be used to construct generic code.
E N D
AOP and Aspect C++ presentation by Igor Kotenkov based on presentation by Andreas Gal, Daniel Lohmann and Olaf Spinczyk
Contents • AOP with pure C++ • AspectC++ • AspectC++ Tool Support • Summary
Templates • Templates can be used to construct generic code. • To actually use the code, it has to be instantiated. • As preprocessor directives: • templates are evaluated at compile time • they do not cause any direct time overhead (if applied properly) Templates are typically used to implement generic abstract data types.
AOP with templates • Templates allow us to encapsulate aspect code independently from the component code. • Aspect code is “woven into” the component code by instantiating those templates. class Queue { … void enqueue(Item *item) { // adding item to the queue } Item *dequeue() { // remove item from the queue } }; temeplate <class Q> Counting_Aspect : public Q { int counter; public: void enqueue(Item *item) { Q::enqueue(item); counter++; } Item *dequeue() { Item *res = Q::dequeue(item); if (counter > 0) counter--; return res; } // this method is added to the component code int count() const {return counter;} };
Weaving We can define a type alias (typedef) that combines both, component and aspect code: typedef Counting_Aspect<Queue> CountingQueue; int main() { CountingQueue q; q.add(…); … }
Combining Aspects Weaving with multiple aspects: typedef Exception_Aspect<Counting_Aspect<Queue> > ExceptionsCountingQueue; ortypedef Counting_Aspect<Exception_Aspect<Queue> > ExceptionsCountingQueue; for counting first and then – checking for exceptions. Implementation of the aspects should be independent of ordering, so it will be no problem with either ordering we will apply.
Limitations • Joinpoint types • no distinction between function call and execution; • no advice for attribute access; • no advice for private member functions. • Quantification • no flexible way to describe the target components; • applying the same aspect to classes with different interfaces is impossible or ends with excessive template metaprogramming. • Scalability • the wrapper code can easily outweigh the aspect code; • explicitly defining the aspect order for every affected class; • makes code hard to understand and debug. “AOP with pure C++ is like OOP with pure C”
Conclusions • C++ templates can be used for separation of concerns in C++ code without special tool support • However, the lack of expressiveness and scalability restricts these techniques to projects with: • only small number of aspects; • few or no aspect interactions; • aspects with a non-generic nature; • component code that is “aspect-aware”.
Aspect C++ • Basic features within Queue example • aspect, advice, execution, call • introduction • joinpoints, pointcut expressions • Advanced concepts • the Joinpoint API • abstract aspects and aspect inheritance • aspects ordering • aspect instantiation • Summary
Introducing new aspect. An aspect starts with word aspect and is syntactically much like a class. Like a class, an aspect can define data members, constructors and so on. ElementCounter aspect aspect ElementCounter { int counter; ElementCounter() { counter = 0; } advice execution(“% util::Queue::enqueue(…)”) : after() { ++counter; printf(“ Aspect ElementCounter: # of elements = %d\n”, counter); } advice execution(“% util::Queue::dequeue(…)”) : after() { if (counter > 0) --counter; printf(“ Aspect ElementCounter: # of elements = %d\n”, counter); } };
Giving after advice This pointcut expression denotes where the advice shoud be given. ElementCounter aspect aspect ElementCounter { int counter; ElementCounter() { counter = 0; } advice execution(“% util::Queue::enqueue(…)”) : after() { ++counter; printf(“ Aspect ElementCounter: # of elements = %d\n”, counter); } advice execution(“% util::Queue::dequeue(…)”) : after() { if (counter > 0) --counter; printf(“ Aspect ElementCounter: # of elements = %d\n”, counter); } };
Introduction • The aspect is not the ideal place to store the counter, because it is shared between all Queue instances • Ideally, counter becomes a member of Queue • So, we will: • move the counter into Queue by introduction; • expose context about the aspect invocation to access the current new instance.
Introduces a new data member counter into all classes denoted by pointcut “util::Queue” Introducing a public method to read the counter A context variable queue is bound to that. It has to be an util::Queue. It is used to access the current instance Introduction - continue aspect ElementCounter { private: advice “util::Queue” : int counter; public: advice “util::Queue” : int count {return counter;} const ElementCounter() { counter = 0; } advice execution(“% util::Queue::enqueue(…)”) && that(queue) : after(util::Queue& queue) { ++queue.counter; printf(“ Aspect ElementCounter: # of elements = %d\n”, queue.count()); } advice execution(“% util::Queue::dequeue(…)”) && that(queue) : after(util::Queue& queue) { if (queue.count() > 0) --queue.counter; printf(“ Aspect ElementCounter: # of elements = %d\n”, queue.count()); } advice execution(“% util::Queue::Queue(…)”) && that(queue) : before(util::Queue& queue) { queue.counter = 0; } }; // within the constructor advice we ensure, that counter gets initialiazed
Joinpoints • A joinpoint denotes a position to give advise: • Code joinpoint (control flow): • execution of a function • call to a function • Name joinpoints • a named C++ program entity (identifier) • class, function, method, type namespace • Joinpoints are given by pointcut expressions • a pointcut expression describes a set of joinpoints
Pointcut Expressions • Pointcut expressions are made from: • match expressions, e.g. “% util::Queue::enqueue(…)” • are matched against C++ program entities (name joinpoints) • support wildcards • pointcut functions, e.g. execution(…), call(…), that(…), within(…), cflow(…) • execution: all points in the control flow, where a function is about to be executed (code joinpoints) • call: all points in the control flow, where a function is about to be called (code joinpoints) • Pointcut functions can be combined • using logical connectors: &&, ||, ! • example: • call(“% util::Queue::enqueue(…)”) && within(“% main(…)”) • !cflow(execution(“% util::~Queue()”))
Advice • Advice to functions • before advice • advice code is executed before the original code • advice may read/modify parameter values • after advice • advice code is executed after the original code • advice may read/modify return value • around advice • advice code is executed instead of the original code • original code may be executed explicitly: tjp->proceed() • Introductions • additional methods, data members, etc. are added to the class • can be used to extend the interface of a class or namespace
Error Handling • We want to check the following constraints: • enqueue() is never called with a NULL item • dequeue() is never called on an empty queue • In a case of an error an exception should be thrown • To implement this the advice needs an access to • the parameter passed to enqueue() • the return value returned by dequeue()
ErrorException namespace util { class QueueInvalidItemError {}; class QueueEmptyError {}; } aspect ErrorException { advice execution(“% util::Queue::enqueue(…)”) && args(item) : before(util::Item* item) { if (item == NULL) throw util::QueueInvalidItemError(); } advice execution(“% util::Queue::dequeue(…)”) && result(item) : after(util::Item* item) { if (item == NULL) throw util::QueueEmptyError(); } };
Thread Safety • Protect the queue by a mutex object • To implement this, we need to • introduce a mutex variable into class Queue • lock it before execution of enqueue/dequeue • unlock it after execution of enqueue/dequeue • The implementation should be exception safe • in the case of exception the “after” advice should called also • solution: around advice
LockingMutex aspect LockingMutex { advice “util::Queue” : os::Mutex lock; pointcut sync_methods() = “% util::Queue::%queue(…)”; advice execution(sync_methods()) && that(queue) : around(util::Queue& queue) { queue.lock.enter(); try { tjp->proceed(); } catch(…) { queue.lock.leave(); throw; } queue.lock.leave(); } };
Queue Example Summary • The Queue example has presented the most important features of the AspectC++ language • aspect, advice, joinpoint, pointcuts, introduction, etc. • Additionally, AspectC++ provides some more advanced concepts and features • to increase the expressive power of aspectual code • to write broadly reusable aspects • to deal with aspect interdependence and ordering
Advanced Concepts • The Joinpoint API • provides a uniform interface to the aspect invocation context • Abstract Aspects and Aspect Inheritance • Aspects Ordering • allows to specify the invocation order of multiple aspects • Important in the case of inter-aspect dependencies • Aspect Instantiation • allows to implement user-defined aspect instantiation models
The Joinpoint API • Inside the advice body, the current joinpoint context is available via the implicitly passedtjp variable: advice … { struct JoinPoint { … } *tjp; // implicitly available in advice code • You have already seen how to use tjp: tjp->proceed() to execute the original code in around advice • The joinpoint API provides a rich interface • to expose context independently of the aspect target • this is especially useful in writing reusable aspect code
struct JoinPoint { // result type of a function typedefJP-specific Result; // object type (initiator) typedefJP-specific That; // object type (receiver) typedefJP-specific Target; // returns the encoded type of the joinpoint // (result conforms with C++ ABI V3 spec) static AC::Type type(); // the same for result static AC::Type resulttype(); // the same for an argument static AC::Type argtype(int n); // returns an unique id for this joinpoint static unsigned int id(); // returns joinpoint type of this joinpoint // (call, execution, …) static AC::JPType jptype(); }; // number of arguments of a function call static int args(); // textual representation of this joinpoint static const char* signature(); // returns a pointer to the n’th argument // value of a function call void* arg(int n); // returns a pointer to the result value of a // function call Result* result(); // returns a pointer to the object initiating a call That* that(); // returns a pointer to the object that is target of a call Target* target(); // executes the original code in an around advice void proceed(); // returns the runtime action object AC::Action& action(); The Joinpoint API
Abstract Aspects and Inheritance • Aspects can inherit from other aspects • reusing aspect definitions • overriding methods and pointcuts • Pointcuts can be pure virtual • postponing the concrete definition to derived aspects • an aspect with a pure virtual pointcut is called abstract aspect • Common usage: resusable aspect implementation • Abstract aspect defines advice code, but pure virtual pointcuts • Aspect code uses the joinpoint API to expose context • Concrete aspect inherits the advice code and overrides pointcuts
Abstract Aspects and Inheritance #include “mutex.h” aspect LockingA { pointcut virtual sync_classes() = 0; pointcut virtual sync_methods() = 0; advice sync_classes() : os::Mutex lock; advice execution(sync_methods()) : around() { tjp->that()->lock.enter(); try { tjp->proceed(); } catch(…) { tjp->that()->lock.leave(); throw; } tjp->that()->lock.leave(); } };
Abstract Aspects and Inheritance #include “mutex.h” aspect LockingA { pointcut virtual sync_classes() = 0; pointcut virtual sync_methods() = 0; advice sync_classes() : os::Mutex lock; advice execution(sync_methods()) : around() { tjp->that()->lock.enter(); try { tjp->proceed(); } catch(…) { tjp->that()->lock.leave(); throw; } tjp->that()->lock.leave(); } }; #include “LockingA.ah” aspect LockingQueue : public LockingA { pointcut sync_classes() = “util::Queue”; pointcut sync_methods() = “% util::Queue::%queue(…)”; };
Aspect Ordering • Aspects should be independent of other aspects • however, sometimes inter-aspect dependencies are unavoidable • example: locking should be activated before any other aspect • Order advice • the aspect order can be defined by order advice advicepointcut-expr : order(high, …, low) • different aspect orders can be defined for different pointcuts • Example: advice “% util::Queue::%queue(…)” : order(“LockingQueue”, “%” && ! “LockingQueue”)
Aspect Instantiation • Aspects are singeltons by default • aspectof() returns pointer to one-and-only aspect instance • By overriding aspectof() this can be changed • e.g. one instance per client or one instance per thread aspect MyAspect { // … static MyAspect* aspectof() { static __declspec(thread) MyAspect* theAspect = NULL; if (theAspect == NULL) theAspect = new MyAspect(); return theAspect; } };
Summary • AspectC++ facilitates AOP with C++ • AspectJ like syntax and semantics • Full obliviousness and quantification • aspect code is given by advice • joinpoints are given declaratively by pointcuts • Implementation of crosscutting concerns is fully encapsulated in aspects • Good support for reusable and generic aspect code • aspect inheritance and virtual pointcuts • joinpoint API
Tool Support • ac++ compiler • open source and the base of other tools • AspectC++ add-in for MS Visual Studio • commercial product • AspectC++ plugin for Eclipse (ACDT) • open source
About ac++ • www.aspect.org (Linux, Win32, Solaris, source, docs, etc.) • Transforms AspectC++ to C++ code • machine code is created by the back-end (cross-)compiler • supports g++ and Visual C++ specific language extentions • Current version 0.9 (<1.0) • no optimizations for compilation speed • no weaving in templates • but already more than a proof of concept
Aspect Transformations • Aspects are transformed to ordinary C++ classes • One static aspect instance is created by default • Advice becomes a member function • “Generic advice” becomes a template member function • A function call is replaced by a wrapper function • A local class with the wrapper function invokes the advice code for the joinpoint • The transformed code is built using C++ compiler
Pros and Cons • AOP with pure C++ + no special tool required • requires in-depth understanding of C++ templates • the component code has to be aspect-aware • no pointcut concern, no match expressions • AspectC++ + ac++ transforms AspectC++ into C++ + various joinpoint types + support for advanced AOP concerns: cflow, joinpoint API - longer compilation time
Roadmap • Parser • full support of templates. Will accept g++ and VC++ STLs • better performance • Advice for object access, for object instantiation • Weaving in macro-generated code • cflow with context variables • Dependancy handling