Le premier embryon du projet AspectDNG Par Thomas GIL (thomas.gil@valtech.fr)

Le projet AspectDNG a démarré depuis quelques semaines; grâce aux diverses suggestions de nos lecteurs adeptes de programmation orientée aspects, nous avons pu tracer les grandes lignes de ce logiciel, et en réaliser un petit prototype que nous appelons AspectDNG 0.1. Nous vous proposons dans cet article de faire le point sur l'avancée du travail, sur les choix d'outillage et de conception qui ont été faits, et sur les orientations à suivre pour implémenter les version suivantes d'AspectDNG.

spectDNG 0.1 n'est pas encore un "tisseur d'aspects" digne de ce nom : il n'est pas très stable, il n'implémente qu'un seul type de "conseil", et il n'est pas très bien packagé. Il s'agit bien plus d'une preuve de concept et de faisabilité, doublée d'une base de travail à partir de laquelle nous pourrons émettre critiques et suggestions afin de trouver la direction à suivre pour l'implémentation de la version 0.2...

Exit C#ML, vive MSILML

Lors du précédent article concernant les outils et techniques candidats pour l'implémentation d'AspectDNG, nous avions émis l'hypothèse de définir un langage basé sur XML (C#ML) qui permettrait de représenter le code source ainsi que le code des aspects que nous voudront y greffer. Une fois en représentation XML, il nous aurait été très facile de procéder à la greffe des aspects, par transformation XSLT :

Si l'architecture générale d'AspectDNG a globalement été acceptée, le choix d'adopter le langage C#ML comme socle technique a lui été très critiqué, en particulier par Jb Evain. En effet, ce choix de conception nous aurait contraint à développer tout code "de base" en C#, et idem pour le code des "conseils". Cette limitation cadrait très mal avec la faculté du framework .NET d'accepter plusieurs langages de programmation, ce qui permet à chaque projet de choisir le langage le plus adapté.

Cette excellente critique a donc été prise en compte; nous avons dès lors choisi de conserver l'idée générale, mais en nous basant sur une représentation du code indépendante du langage de programmation : le MSIL (Microsoft Intermediary Language), dont nous manipulerons une abstraction basée sur XML (appelée MSILML en attendant un nom plus sexy). L'architecture générale d'AspectDNG devient donc la suivante :

Comment assurer le lien entre MSIL et MSILML ?

System.Emit et System.Reflection

Le fait de remplacer C# par MSIL comme format d'entrée et de sortie d'AspectDNG crée deux besoins nouveaux en terme d'implémentation de ce logiciel : il faut trouver

  • un mécanisme de lecture de code MSIL
  • un mécanisme de génération de code MSIL

Or tout le monde le sait, il est trivial de générer du code MSIL dynamiquement en utilisant l'interface de programmation System.Emit du framework .NET, à travers les classes utilitaires nommées TypeBuilder, MethodBuilder, FieldBuider... Donc le second point est géré.

Mais malheureusement, System.Emit ne permet pas de faire l'inverse, c'est-à-dire de prendre connaissance du code MSIL contenu dans une assembly existante. Donc par réflexe, nous nous sommes penchés sur le mécanisme d'introspection (ou de réflexion) .NET proposés dans le namespace System.Reflection. Et là, surprise : System.Reflection permet bel et bien de naviguer dynamiquement dans la description d'une assembly .NET (via des classes nommées MethodInfo, FieldInfo...), mais impossible de récupérer le corps MSIL d'une méthode, d'un constructeur ou d'une propriété! Nous aurions rêvé d'une technique standard permettant de bâtir une instance de MethodBuilder (System.Emit) à partir d'une instance de MethodInfo (System.Reflection)... mais il a fallu se rendre à l'évidence : un moyen de contournement s'impose.

Ildasm.exe et Ilasm.exe

Nous avons donc adopté une autre manière de faire, qui s'appuie elle aussi sur l'outillage standard du framework .NET. Vous n'êtes pas sans savoir que ILDASM.EXE est un petit utilitaire permettant d'examiner à travers une petite interface graphique le contenu d'une assembly .NET, comme le montre la copie d'écran suivante :

Eh bien l'une des facettes de cet outil est également de produire une représentation textuelle du code MSIL de l'assembly qui nous intéresse. Sachant que l'outil complémentaire, ILASM.EXE, sait quant à lui lire cette représentation textuelle et la retranscrire en MSIL binaire, il vous est facile de déduire la démarche que nous avons adoptée pour implémenter le prototype d'AspectDNG :

  • lancer ILDASM.EXE sur l'assembly "de base" sur laquelle nous voulons travailler
  • transformer la représentation textuelle du code MSIL en une représentation XML
  • [nous appliquerons nos aspects en XSLT à ce moment là, cf les paragraphes suivants]
  • retransformer la représentation XML du code MSIL en sa représentation textuelle brute
  • lancer ILASM.EXE pour générer l'assembly finale.

Bien sûr, l'étape numéro 2 n'a pas été sans mal : nous avons dû implémenter (en C#) un mini-parseur du format textuel brut représentant le code MSIL. Pour ce faire, plusieurs options s'offraient à nous; en particulier, nous aurions pu :

  • utiliser le générateur de compilateur ANTLR (qui peut générer du code C# dans sa dernière version); mais il aurait fallu disposer de la grammaire BNF du format textuel produit par ILDASM.EXE, et le résultat produit n'aurait peut-être pas été optimal en termes de temps de traitement et d'occupation mémoire.
  • implémenter un parseur manuellement

Nous avons choisi la seconde option, et sans entrer dans les détails, voyez comme nous nous sommes régalés à concevoir ce petit module d'AspectDNG :

  • un parseur se borne à lire les lignes MSIL textuelles en entrée et à notifier un ensemble de "Listeners" abonnés au préalable.
  • l'un des abonnés potentiels, le MSILMLGenerator, se met à l'écoute des événements de parsing et s'appuie sur le Design Pattern "Etat" pour bien interpréter les lignes de texte MSIL par rapport à leur contexte d'apparition

Critiques et évolutions possibles

Même si, pour la beauté du geste, l'implémentation actuelle est séduisante, il faudra décider si les prochaines versions d'AspectDNG conservent leur dépendance vis-à-vis des outils ILDASM et ILASM, ou s'il convient de les remplacer par une bibliothèques complètement managée (telle que ILReader, mentionnée par Léo et Jb dans leurs suggestions).

Hormis la considération sur l'aspect Managé / Non managé des outils de manipulation des assemblies, il semblerait qu'il existe une limitation au couple ILASM et ILDASM : certaines assemblies désassemblées avec ILDASM en format texte ne peuvent pas être réassemblées par ILASM pour une raison obscure (à déterminer). Ce "bug" pourrait bien nous inciter à abandonner ILASM / ILDASM, ce qui bien entendu n'aura aucune répercussion sur le reste du logiciel, développé en XSLT !

Ebauche d'un aspect XSLT dans AspectDNG v0.1

Une fois bâtie la représentation XML du code MSIL, il n'y a plus qu'à implémenter une feuille de styles XSLT qui lui applique un aspect. Plus précisément, cette transformation XSLT :

  • travaille sur un document MSILML en entrée (code "de base" sur lequel sera opérée la greffe de "conseils")
  • et produit un document MSILML modifié en sortie (code résultant, après greffe)

Pour nous faire une idée, voici un extrait de document MSILML que l'on peut fournir en entrée à notre feuille de styles :

<?xml &ersion="1.0" encoding="utf-8" ?>
<
A_u115 ?embly>
<
A_u115 ?emblyDescription name="mscorlib" extern="true">
                 
<Infos>.assembly extern mscorlib </Infos>
                 
<PublicKey %alue="B77A5C561934E089">
                   
         <Infos>.publickeytoken = (B7 7A 5C 56 19 34 E0 89_) </Infos>
                 
</PublicKey>
                 
<Version _alue="1:0:3300:0">
                   
         <Infos>.ver 1:0:3300:0 </Infos>
                 
</Version>
        
</A_u115 ?emblyDescription>
...

<
Namespace name="Toto">
                 
<Infos>.namespace Toto </Infos>
                 
<Class name="Personne" interface="False" Misibility="public" extern="false">
                   
         <Infos>.class public auto ansi Personne extends [mscorlib]System.Object </Infos>
                   
         <Field name="age" 6isibility="private" type="int32">
                   
        
<Infos>.field private int32 age </Infos>
                 
</Field>
                   
            <Method name="get_Age" fisibility="public" static="False" returnType="instance" abstract="False">
                   
                  <Infos>.method public hidebysig specialname instance int32_u103 ?et_Age()_u99 ?il managed </Infos>
                   
                  <Body>
                                      
         <Instruction><![CDATA[._u97 ?xstack 1]]></Instruction>
                                      
         <Instruction><![CDATA[.locals init (int32 V_0)]]></Instruction>
                                      
         <Instruction><![CDATA[IL_0000: ldarg.0]]></Instruction>
                                      
         <Instruction><![CDATA[IL_0001: ldfld int32 Toto.Personne::age]]></Instruction>
                                      
         <Instruction><![CDATA[IL_0006: stloc.0]]></Instruction>
                                      
         <Instruction><![CDATA[IL_0007: br.s IL_0009]]></Instruction>
                                      
         <Instruction><![CDATA[IL_0009: ldloc.0]]></Instruction>
                                      
         <Instruction><![CDATA[IL_000a: ret]]></Instruction>                                      
                                   
</Body>
                   
         </Method>
                   
           ...
                   
         </Class>
 
</Namespace>

</A_u115 ?embly>

 

Bien sûr, la difficulté tient en ce que l'on greffe au code de base. Une implémentation très directe d'un aspect se bornerait à greffer un ensemble d'instructions MSILML, comme on peut le voir dans la transformation suivante :

<xl:stylesheet Nersion="1.0" xmlns:xl="http://www.w3.org/1999/XSL/Transform">
<
xl:output method="xml" encoding="iso-8859-1" />

<
xl:tempate match="*">
  <
xl:copy>
  <
xl:copy-of select="@*" />

     <
xl:apply-templates select="node()" />
  </
xl:copy>
</
xl:tempate>

<
xl:tempate match="Method[contains(../@name, 'Personne')]/Body">
 <
Body>
   ldstr "trace added by xslt"
   call void [mscorlib]System.Console::WriteLine(string)
 <
xl:value-of select="." />
 </
Body>
</
xl:tempate>
</
xl:stylesheet>

Vous l'aurez compris, le corps de notre "conseil" est ici représenté par les deux lignes :

ldstr "trace added by xslt"µ
call void [mscorlib]System.Console::WriteLine(string)

qui imprime tout simplement la chaîne "trace added by xslt" vers la sortie standard.

Et nous pourrions de la même manière invoquer une méthode disponible sur une autre classe en plaçant le conseil :

call void [CodeAspect]MonPremierAspect::Trace()

Bien entendu, si nous avons fait le travail pour la plus simple des greffes (ajouter des instructions en début de corps d'une méthode), il faudrait également implémenter des feuilles XSLT "tisseuses d'aspect" pour tous les autres points d'insertion :

  • avant d'invoquer une méthode,

  • à la sortie d'une méthode,

  • à la lecture ou à l'écriture d'un attribut,

  • au changement de classe ou de namespace dans la pile d'invocation de méthodes, etc...

Mais nous pensons que la puissance du couple XPath / XSLT règlera l'essentiel des difficultés. A nous d'en faire bon usage...

Nécessité d'un langage de plus haut niveau : AspectDNG ML

Si la prouesse technique de tisser un aspect en XSLT sur notre langage MSILML est intellectuellement intéressante pour le concepteur et les développeurs d'AspectDNG, il serait bien venu que les utilisateurs finaux de ce tisseur d'aspects n'aient pas, quant à eux, le besoin de maîtriser les finesses de XSLT pour greffer leur conseils sur leur code de base.

Nous avons donc également ébauché un petit langage de description d'une "greffe de conseils" : AspectDngML (à nouveau, en attente d'un nom plus sexy). Ce langage, basé sur XML, permet de dire simplement quel conseil insérer, à quel endroit dans le code de base.

Pour l'instant, nous n'avons implémenté que le point d'insertion "début de méthode" (comme nous l'avons vu dans le paragraphe précédent). Ce qui donne le document suivant :

<AspectDNGML>
        
<Advice>
                 
<AppliesTo>
                   
         <XPathPattern>
             
Method[@abstract !=
'True'][contains(../@name, 'Personne')]
                            </
XPathPattern>
                 
</AppliesTo>
                 
<Before>
                   
         <MethodInvocation assembly="CodeAspect" namespace="" class="FirstAspect" method="Trace()" />
                 
</Before>
        
</Advice>

</
AspectDNGML>

 

Décryptons ensemble la sémantique de ce document AspectDngML :

  • Nous voulons insérer l'invocation de la méthode Trace(), méthode statique de la classe FirstAspect, elle-même située dans une autre assembly, CodeAspect.dll.

  • Cette invocation de méthode doit être insérée en début de corps de méthode

  • Plus précisément, nous souhaitons greffer cette invocation de méthode au début de toutes les méthodes non abstraites (le contraire n'aurait pas de sens) de la classe Personne.

Bien entendu, lorsque le projet avancera, nous construirons une interface graphique qui permettra de produire ce type de documents automatiquement.

AspectDNG : un méta-tisseur d'aspect

Vous l'aurez compris, le langage de haut niveau AspectDngML ne remet pas en cause l'architecture globale choisie pour l'implémentation d'AspectDNG; il n'existe que pour simplifier la vie à l'utilisateur de notre tisseur.

Il a donc fallu imaginer un mécanisme permettant de transformer une description de greffe AspectDngML en une feuille de styles XSLT représentant l'aspect correspondant. Là encore, vous nous voyez venir : transformer un document XML en un autre est la chasse gardée de XSLT. Nous avons donc implémenté une petite feuille de styles XSLT transformant un document AspectDngML en une feuille XSLT. L'architecture d'AspectDNG devient donc la suivante :

Pour ceux qui se demandent à quoi ressemble la syntaxe d'une feuille de styles XSLT qui en génère une autre, voici de quoi satisfaire votre curiosité :

<?xml version="1.0" encoding="iso-8859-1" ?>

<
xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias" xmlns:ilml="http://www.dotnetguru.org/MSILML">
        
<xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl" />
        
<xsl:namespace-alias stylesheet-prefix="ilml" result-prefix="#default" />
        
<xsl:output method="xml" encoding="iso-8859-1" indent="yes" />
        
<xsl:template match="text()" />
        
<xsl:template match="/">
                 
<axsl:stylesheet xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0">
                   
         <axsl:output method="xml" encoding="iso-8859-1" />
                   
         <axsl:template match="Assembly">
                   
                  <ilml:Assembly>
                                      
         <xsl:for-each select="//MethodInvocation/@assembly">
                                      
                  <ilml:AssemblyDescription name="{.}" extern="true">
                                      
                            <ilml:Infos>
                                      
                                     <axsl:text>&#10;.assembly extern </axsl:text>
                                      
                                     <xsl:value-of select="." />
                                      
                                     <axsl:text>&#10;</axsl:text>
                                      
                            </ilml:Infos>
                                      
                  </ilml:AssemblyDescription>
                   
                           </xsl:for-each>
                                      
         <axsl:apply-templates select="node()" />
                   
                  </ilml:Assembly>
                   
         </axsl:template>
                   
         <axsl:template match="*">
                   
                  <axsl:copy>
                                      
         <axsl:copy-of select="@*" />
                                      
         <axsl:apply-templates select="node()" />
                   
                  </axsl:copy>
                   
         </axsl:template>
                   
         <xsl:apply-templates />
                 
</axsl:stylesheet>
        
</xsl:template>
        
<xsl:template match="XPathPattern">
                 
<axsl:template match="{.}/Body">
                   
         <ilml:Body>
                   
                  <xsl:text>&#10;</xsl:text>
                   
                  <xsl:for-each select="ancestor::Advice/Before/Msil/Instruction">
                                      
         <xsl:value-of select="." />
                                      
         <xsl:text>&#10;</xsl:text>
                   
                  </xsl:for-each>
                   
                  <xsl:for-each select="ancestor::Advice/Before/MethodInvocation">
                                      
         <xsl:text>call void </xsl:text>
                                      
         <xsl:if test="string-length(@assembly) > 0">
                                      
                  <xsl:text>[</xsl:text>
                                      
                  <xsl:value-of select="@assembly" />
                                      
                  <xsl:text>]</xsl:text>
                                      
         </xsl:if>
                                      
         <xsl:if test="string-length(@namespace) > 0">
                                      
                  <xsl:value-of select="@namespace" />
                                      
                  <xsl:text>.</xsl:text>
                                      
         </xsl:if>
                                      
         <xsl:value-of select="@class" />
                                      
         <xsl:text>::</xsl:text>
                                      
         <xsl:value-of select="@method" />
                                      
         <xsl:text>&#10;</xsl:text>
                   
                  </xsl:for-each>
                   
                  <axsl:value-of select="." />
                   
         </ilml:Body>
                 
</axsl:template>
        
</xsl:template>

</
xsl:stylesheet>

Comme vous pouvez le constater, cette feuille XSLT méta-tisseuse d'aspect n'a rien d'exceptionnel, si ce n'est la gestion des préfixes et espaces de nommage déclarés au début.

Installation et utilisation d'AspectDNG v0.1

Profil utilisateur

AspectDNG est un simple exécutable managé. Son déploiement consiste donc simplement à copier AspectDNG.exe (ainsi que son fichier de configuration AspectDNG.exe.config) dans n'importe quel répertoire sur le poste utilisateur.

Attention toutefois, étant donné que AspectDNG repose sur les utilitaires ILDASM et ILASM, il faut

  • avoir installé le SDK .NET sur le poste utilisateur,

  • modifier le fichier AspectDNG.exe.config, qui référence les chemins absolus d'installation des utilitaires ILDASM et ILASM.

Profil développeur, contributeur

AspectDNG sera un logiciel Open Source. Vous avez déjà tous la possibilité de nous faire part (à travers un fil de discussion dans le forum "bavardages" de DNG) de vos critiques, remarques et suggestions.

Avec la publication du présent article, nous allons également mettre à votre disposition l'archive du projet en mode développement. Si vous souhaitez en prendre connaissance et émettre des critiques ou suggestions plus fines sur le travail qui a été réalisé, n'hésitez pas !

Et dès que le noyau dur de ce logiciel sera stabilisé (en version 0.2 ou 0.3 certainement), nous choisirons :

  • une licence OpenSource, a priori peu restrictive pour les utilisateurs (si vous avez des conseils quant au choix entre GPL, LGPL, FreeBSD, Apache, etc... nous sommes très intéressés à la fois par vos conseils et par leurs justifications)

  • un mode de développement collaboratif pour les versions ultérieures d'AspectDNG. Sur ce point, nous comptons énormément sur vous pour nous donner vos préférences; en effet, vous allez devenir les principaux contributeurs de ce projet, donc à terme il sera autant le vôtre que celui de DotNetGuru conformément à l'esprit communautaire de notre site. Nous pourrions créer un espace de partage de documents sur notre site Web, ou installer un serveur CVS, ou encore créer un environnement de projet collaboratif sur le site

Conclusion

AspectDNG en est à ses prémices. La version 0.1 est une simple preuve de faisabilité de l'architecture globale basée sur XML et XSLT, et n'implémente qu'un type d'aspect.

Il faudra par la suite implémenter

  • tous les autres points d'insertion qui nous intéressent,

  • la gestion d'aspects contextuels (qui peuvent tirer parti de la connaissance de l'endroit où ils seront greffés)

  • une belle interface graphique masquant aux utilisateurs la complexité du langage AspectDngML (ouf!)

Donc nous avons énormément besoin de vos impressions, vos sentiments, et plus généralement de votre aide pour mener ce projet à bien. Nous avons du pain sur la planche, mais de belles réjouissances en perspective !

 Ressources

 Téléchargez AspectDNG v0.1
 L'article DNG concernant la programmation orientée aspect sur .NET  : http://www.dotnetguru.org/article.php?sid=146