1. Présentation

La programmation orientée aspect (AOP) est apparue il y a quelques années au Xerox PARC en Californie et va dans le sens du principe de la séparation des préoccupations (separation of concerns). Une application complexe peut comporter de multiples composants qui se référencent les uns les autres, et qui ne permet pas une évolution souple à laquelle on pourrait s'attendre.
La programmation orientée aspect s'intéresse en particulier à l'intégration des composants en offrant un moyen simple de les assembler.
Dans le terminologie propre à l'AOP, un aspect correspond à une préoccupation transverse, telle que la sécurité, les transactions, les traces de débogage, etc., et un tissage à la composition de plusieurs aspects.
Il existe un site officiel dédié à l'AOP et de nombreux projets y sont décrits (voir la rubrique Ressources à la fin de cet article).

Le code source C++ qui l'accompagne a servi de base à une réécriture en Pascal Objet des composants du framework (moniker personnalisé, hook délégateur).
Dans la rubrique Ressources, vous pouvez télécharger les modules et exemples de code dont il sera fait mention ; toutefois ces modules ne sont pas complètement testés et pourront évoluer par la suite.

2. framework AOP COM

Lorsqu'une application cliente s'attache à une interface d'un objet serveur COM (objet cible) pour en utiliser les méthodes, le framework permet d'exécuter du code d'interception implémenté dans un aspect avant et après l'appel de l'une de ces méthodes.
Ici un aspect est une classe COM qui implémente une interface particulière, IAspect comprenant deux méthodes, PreProcess et PostProcess ; un des arguments est le nom de la méthode invoquée sur l'objet cible, tandis qu'il est possible d'examiner la valeur des arguments passés lors de l'appel.
Le framework permet de lier plusieurs aspects à un objet cible ; si A1 et A2 sont deux aspects et qu'un client exécute la méthode f sur l'objet cible, le flot d'exécution est le suivant :

  • A1.PreProcess;
  • A2.PreProcess;
  • f;
  • A1.PostProcess;
  • A2.PostProcess;

Comment associer des classes aspects avec un objet COM serveur ? Ces méta données sont définies dans un document XML dans lequel les objets COM sont désignés par leurs CLSID, ce que l'on trouve dans le document Test.xml par exemple :

 
Sélectionnez

<AOPFramework>
  <Component Name="Objet ServerObject">
    <CLSID>{73306D0F-EC9F-473E-9263-E89E52DCD079}</CLSID>
  </Component>
  <Aspects>
    <Aspect Name="Aspect test">
      <CLSID>{63540724-3315-11D7-A726-00000E991C76}</CLSID>
  </Aspects>
</AOPFramework>

La mise en œuvre du framework est assurée par l'utilisation d'un moniker personnalisé par lequel un client peut, en indiquant le nom d'un document XML, définir les objets cible et aspects à instancier, tandis que le composant délégateur universel assure l'enchaînement des appels. Ce dernier est décrit plus loin.
En termes de l'AOP, ce document XML définit donc le tissage ou composition des aspects.

Il est intéressant de noter que le composant cible n'a pas à être modifié et qu'un composant existant peut prendre part au framework.
D'autre part, le programme client, ne fait jamais appel directement aux objets aspects ; comme ceux-ci sont bien séparés dans des classes différentes, le principe de la séparation des préoccupations est respecté. C'est en cela que la programmation orientée aspect permet une meilleur réutilisabilité ainsi qu'une plus grande souplesse.
L'ajout ou le retrait d'un aspect revient à ajouter ou retirer un élément XML " Aspect " de l'élément document Aspects du document XML.

3. Activation d'objet

Les monikers personnalisés permettent d'étendre l'espace de nommage de COM en représentant des objets sous forme de chaîne de caractères. Ce sont des classes COM implémentant l'interface IMoniker, ce qui permet à une fonction telle que GetObjet(), connu des développeurs Visual Basic, d'instancier un objet à partir d'une chaîne de caractères.
Dans le framework, la valeur passée à GetObjet indique le nouvel espace de nommage ainsi que le nom du document XML à utiliser, par exemple "AOPMoniker.Moniker:Test.xml" ; notre moniker, AOPMoniker (voir la rubrique Composants), réalise alors la liaison (BindToObject) en mettant en place toute la tuyauterie :

Image non disponible

Le moniker personnalisé AOPMoniker a la responsabilité d'analyser le document XML (utilisation du parseur MSXML), de créer l'objet cible, ainsi qu'un objet d'interception (composant Hook) transmis à un objet délégateur universel et qui supporte les interfaces COM IDelegatorHookQI et IDelegatorHookMethods2. L'objet Hook crée les objets aspects et gère les appels aux méthodes PreProcess et PostProcess de chaque aspect.

Ayant obtenu un pointeur d'interface de GetObject, le client s'adresse de manière transparente au délégateur comme si il utilisait directement un pointeur d'interface vers l'objet cible.

L'unité GetObjectImpl.pas du module ClientTest contient le code de la fonction GetObject.

4. Délégateur universel

Le délégateur universel est un simple objet COM pouvant encapsuler tout objet COM sans nécessiter d'information de types.

Fourni comme ressource de l'article en deux parties " Building a Lightweight COM Interception framework ", le délégateur universel est implémenté dans la dll delegate.dll ; comme celle-ci ne comporte pas de bibliothèque de types sous forme de ressource, nous pouvons l'obtenir à partir du fichier IDL delegate.idl fourni par l'auteur du délégateur universel.

La méthode décrite ci-dessous est très intéressante pour obtenir d'abord la description binaire de la bibliothèque de types (fichier .tlb), puis la déclaration Pascal Objet correspondante, cela à partir d'un fichier IDL produit par un environnement de développement Microsoft.
Après avoir obtenu le fichier delegate.tlb, on pourra demander à l'IDE Delphi de créer l'unité comprenant les déclarations COM qui nous permettront d'instancier et d'utiliser un objet délégateur universel.

delegate.tlb peut être généré avec le compilateur d'IDL MS midl suivant la commande :

 
Sélectionnez

 midl delegate.idl /tlb delegate.tlb

Nous sommes parti d'une version légèrement modifiée de delegate.idl qui permet de voir au final toutes les interfaces COM.
Pour que cela fonctionne, midl a également besoin des fichiers suivants : UNKNWN.IDL, WTYPES.IDL, OAIDL.IDL et OBJIDL.IDL.
Il est possible d'obtenir midl et les fichier idl mentionnés avec par exemple les outils Microsoft eMbedded Visual Tools.

DelegatorLib_TLB.pas est ensuite produit par Delphi en important delegate.tlb (Projet/Importer une bibliothèque de types...).
Vous trouverez dans le sources fournis dans cet article (voir rubrique Ressources), dans le répertoire TLB, les fichiers delegate.idl (modifié par rapport à la version originale), delegate.tlb et DelegatorLib_TLB.pas.

5. Développer un aspect avec Delphi

Un aspect étant ici un objet COM implémentant l'interface COM IAspect, il suffit d'utiliser l'assistant de création d'objet COM de la boîte de dialogue " Nouveaux éléments ", page " ActiveX ", et de stipuler IAspect comme interface à implémenter. IAspect s'obtient avec l'expert de sélection d'interface qui s'ouvre avec le bouton " Liste ", à condition d'avoir recenser la bibliothèque de types AOPTypeLib.tlb au préalable.
AOPTypeLib.tlb fait partie des modules fournis avec cet article (voir la rubrique Ressources).
Elle peut être recensée avec l'utilitaire TREGSVR situé dans le répertoire BIN de Delphi au moyen d'une commande de la forme :

 
Sélectionnez

 TREGSVR -t AOPTypeLib.tlb

Après en avoir terminé avec l'assistant de création d'objet COM, vous disposez d'une CoClasse pour laquelle il faut implémenter les méthodes de IAspect, PreProcess et PostProcess dont voici les signatures :

 
Sélectionnez

function PreProcess(const pUnkDelegatee: IUnknown;
    const strMethodName: WideString; nvtblSlot: Integer; argsIn: OleVariant): HResult; stdcall;

Pour les arguments, strMethodName contient le nom de la fonction invoquée, nvtblSlot est son index, tandis que argsIn est un OleVariant qui est un tableau de OleVariant reprenant la valeur de tous les arguments en entrée (in) passés à strMethodName.
PreProcess peut renvoyer en retour de fonction une valeur en échec (pour laquelle Failed(Result) renverrait True), ce qui outrepasserait les appels à strMethodName et PostProcess.

 
Sélectionnez

function PostProcess(hrOriginal: HResult; const pUnkDelegatee: IUnknown;
    const strMethodName: WideString; nvtblSlot: Integer; argsOut: OleVariant): HResult; stdcall;

Ici, hrOriginal est la valeur de HResult renvoyée par strMethodName et argsOut est un tableau de OleVariant fournissant la valeur de tous les arguments en sortie (out).

L'aspect implémenté dans AspectTest montre comment parcourir argsIn pour obtenir la valeur de chaque argument en entrée.

Le dernière étape consiste à ajouter un nouvel élément XML " Aspect " au document XML tissant les aspects, avec un sous-élément " CLSID " dont la valeur doit être le CLSID de la CoClasse.
Dans l'unité correspondant à la bibliothèque de types de l'aspect, on retrouve cette valeur sous la forme d'une déclaration de constante commençant par " CLASS_ " suivi du nom de la CoClasse ; ce GUID est aussi visible dans l'éditeur de la bibliothèque de types en examinant les attributs de la CoClasse.

6. Composants

Le tableau ci-dessous donne la liste des composants du framework accompagnés de composants de test.
Les modules delegate.dll, AOPMoniker.dll, Hook.dll forment la base du framework, tandis que ClientTest.exe est une application cliente de test du framework utilisant l'objet cible implémenté dans COMServerTest.dll, pour lequel on a associé l'aspect AspectTest.
Pour vérifier que l'ensemble fonctionne correctement, des messages de débogage sont émis et, en particulier, AspectTest trace le nom de la méthode invoquée (de IServerTest) par ClientTest avec les valeurs des arguments.
Les sources des composants du framework pourront être fournies sur simple demande.

Tous les composants ActiveX doivent bien sûr être recensés, également la bibliothèque de types AOPTypeLib.tlb (voir plus haut) dans le cadre du développement d'un nouvel aspect.

Composant Description Source Delphi Module Type
AOPMoniker Moniker personnalisé AOPMoniker.dpr AOPMoniker.dll ActiveX
Hook Composant hook Hook.dpr Hook.dll ActiveX
AspectTest Aspect de test implémentant IAspect AspectTest.dpr AspectTest.dll ActiveX
delegate.dll Délégateur Universel N/A delegate.dll ActiveX
delegatte.tlb Bibliothèque de types du délégateur universel DelegatorLib_TLB.pas delegate.tlb Bibliothèque de types
AOPTypeLib Bibliothèque de types de l'interface IAspect AOPTypeLib_TLB.pas AOPTypeLib.tlb Bibliothèque de types
ClientTest Programme de test framework (objet client) ClientTest.dpr ClientTest.exe Exe
COMServerTest ActiveX de test du framework (objet cible) COMServerTest.dpr COMServerTest.dll ActiveX

7. Ressources

L'article original

aspect-oriented software development
Aspect-Oriented Programming Enables Better Code Encapsulation and Reuse. MSDN Magazine, March 2002.
Building a Lightweight COM Interception framework, Part 1: The Universal Delegator.
Building a Lightweight COM Interception Framework, Part 2: The Guts of the UD.

Ce fichier comporte le framework ainsi qu'un projet de démonstration pour Delphi 5 :

  • FrameworkAOP : Framework AOP COM (delegate.dll, AOPMoniker.dll, Hook.dll, AOPTypeLib.tlb)
  • FrameworkAOPTest : Applications de test du framework AOP COM (sources de ClientTest.dpr, de COMServerTest.dpr et de AspectTest.dpr)