Evaluation/Exécution d’une chaine

décembre19, 2007

Petit article présentant comment évaluer/exécuter une chaine de caractères comme un morceau de code. Cette technique peut être utile, par exemple, si vous voulez coder une application dont certaines parties du code sont stockées en base de données. Cela vous permet donc de créer une application qui sera ensuite « extensible » (dans une certaine mesure…) via des mises à jour de la base de données.

Pas convaincu ? Un exemple un peu plus concret vous aiderait ? Ok, en voilà un parmis tant d’autres…

Imaginez une application de dessin. Dans votre barre de menu, entre autre, vous retrouvez un menu « Transformation », qui permet d’effectuer différents traitements sur votre image tels que la rotation à 90° à droite, ou l’inversion des couleurs. Quelque chose de ce style, quoi :

Exemple d’interface - Evaluation de chaine

Que diriez-vous de pouvoir augmenter les options de ce menu par la suite en mettant simplement une table à jour dans votre base de données, décrivant le nom de la commande et le code à exécuter ?

Et bien la méthode à utiliser est somme toute assez simple, mais quand on ne la connait pas, et bien… Ça ne s’invente pas !

Reprenons l’exemple de notre application de dessin. On peut raisonnablement estimer que pour chaque commande correspondant au menu transformation, la fonction qui exécutera le code correspondant devra prendre une image en argument, et renvoyer l’image transformée. On peut donc définir une partie du code de notre application (je n’expliquerai pas les parties de connexion à la base de données) :

Private _img As Image
 
Public Property Image() As Image
    Get
        Return Me._img
    End Get
    Set(ByVal value As Image)
        Me._img = Image
    End Set
End Property
 
Public Sub New()
 
    ' Requis par le Concepteur Windows Form.
    InitializeComponent()
 
    ' Création de la liste des transformation :
    ' - Le tag de chaque item contiendra l'identifiant en base de données.
    ' - On utilisera la méthode AddHandler pour gérer le clic sur un item
    '   avec la fonction tsmiTransformation_Click.
    InitLstTransformations()
 
End Sub
 
Private Sub tsmiTransformation_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
 
    'Récupération de l'item cliquée.
    Dim myItem As ToolStripMenuItem = TryCast(sender, ToolStripMenuItem)
    If myItem Is Nothing Then Exit Sub
 
    ' Récupération du code à exécuter qui est stocké en BDD.
    Dim vbCode As String = Me.getTransformationCode(myItem.Tag.ToString)
 
    Me.Image = ExecuteTransformation(vbCode, Me.Image)
 
End Sub

On sait que la fonction ExecuteTransformation aura le prototype suivant :

Private Function ExecuteTransformation(ByVal vbCode As String, ByVal img As Image) As Image

Bien ! Maintenant que le prototypage est fait, il ne reste plus grand chose… Ah ben si, on n’a toujours pas vu comment évaluer/exécuter la chaine vbCode comme si c’était du code VB !

J’ai dit que ce n’était pas très compliqué, mais si je vous donnais le code sans explication, vous pourriez trouver ça complexe ! Ben oui, les classes utilisées pour cette opération ne sont pas si courantes que ça… Mais pas de panique, après une brève explication de la démarche, vous trouverez ça aussi simple que compter jusqu’à 10 en binaires !

Dans le Framework .Net, à l’intérieur de l’espace de noms Microsoft.VisualBasic, se trouve une classe nommée VBCodeProvider. Cette classe vous « fournit un accès aux instances du générateur de code et du compilateur de code Visual Basic » (dixit la MSDN), ce qui veut dire que grâce à cette classe, vous allez pouvoir générer des assembly et/ou des exécutable .Net à partir d’une chaine représentant du code source VB.Net. Dans notre exemple, la génération d’un assembly pour chaque commande du menu transformation changerait rapidement le répertoire de notre application en un gigantesque foutoir. Sauf que VBCodeProvider permet également de générer ces assembly directement en mémoire ! Et donc, avec un peu d’imagination, d’exécuter du code à la volée ! Pour cela, on va donc utiliser un objet VBCodeProvider, ainsi qu’un objet CompilerParameters (je vous laisse regarder la MSDN) qui permet de configurer le compilateur VB. On va compiler une classe (appelons la EvalVbCode) qui contiendra une méthode (appelons la EvalTransformation) qui contiendra le code récupéré dans la base de données. Une fois l’assembly généré, il ne reste plus qu’à appeler la méthode (avec la Reflexion) et à récupérer l’image transformée… Simple, non ?!

Prêts ? Alors, prenez une bonne inspiration, et c’est parti :

Private Function ExecuteTransformation(ByVal vbCode As String, ByVal img As Image) As Image
    Dim vbCodeProv As VBCodeProvider = New VBCodeProvider
    Dim cParam As CompilerParameters = New CompilerParameters
 
    ' Ajout des références
    cParam.ReferencedAssemblies.Add("System.dll")
    cParam.ReferencedAssemblies.Add("System.Drawing.dll")
 
    ' Options du compilateur
    cParam.CompilerOptions = "/t:library"  'L'assembly est une bibliothèque de classe,
    cParam.GenerateInMemory = True         'générée uniquement en mémoire.
 
    ' Génération du code source
    Dim sCode As System.Text.StringBuilder = New System.Text.StringBuilder("")
    sCode.AppendLine("Imports System")
    sCode.AppendLine("Imports System.Drawing")
    sCode.AppendLine("Imports System.Diagnostics")
    sCode.AppendLine()
    sCode.AppendLine("Namespace VotreNamespace")
    sCode.AppendLine(vbTab & "Class EvalVbCode")
    sCode.AppendLine(vbTab & vbTab & "Public Function EvalTransformation(ByVal img As Image) as Image")
    sCode.AppendLine(vbTab & vbTab & vbTab & "Try")
    sCode.AppendLine()
    sCode.AppendLine(vbCode)
    sCode.AppendLine()
    sCode.AppendLine(vbTab & vbTab & vbTab & "Catch ex As Exception")
    ' En cas d'erreur : console de debug ET renvoie Nothing
    sCode.AppendLine(vbTab & vbTab & vbTab & vbTab & "Debug.WriteLine(ex.Message)")
    sCode.AppendLine(vbTab & vbTab & vbTab & vbTab & "Return Nothing")
    sCode.AppendLine(vbTab & vbTab & vbTab & "End Try")
    sCode.AppendLine(vbTab & vbTab & "End Function")
    sCode.AppendLine(vbTab & "End Class")
    sCode.AppendLine("End Namespace")
    ' Code de la classe dans la console de debug
    Debug.WriteLine(sCode.ToString())
 
    ' Résultat de la compilation
    Dim cResult As CompilerResults = vbCodeProv.CompileAssemblyFromSource(cParam, sCode.ToString())
    ' Récupération de l'assembly généré
    Dim myAssembly As System.Reflection.Assembly = cResult.CompiledAssembly
    ' Instanciation de EvalVbCode
    Dim oEvalVbCode As Object = myAssembly.CreateInstance("VotreNamespace.EvalVbCode")
    ' Récupération du type de EvalVbCode
    Dim tEvalVbCode As Type = oEvalVbCode.GetType()
    ' Récupération de la méthode EvalTransformation
    Dim methodEvalTrans As MethodInfo = tEvalVbCode.GetMethod("EvalTransformation")
    ' Invocation de la méthode EvalTransformation
    Dim myImage As Image = methodEvalTrans.Invoke(oEvalVbCode, New Object() {img})
 
    Return myImage
End Function

Et voilà ! Le code est exécuté à la volée ! N’oubliez pas d’indenter un minimum votre code, afin de pouvoir le lire dans la console de debug. Et maintenant, réfléchissez un peu plus aux applications d’une telle technique. Et n’oubliez pas que vous pouvez également générez les assembly et/ou applications sous forme de fichier sur le disque, puis utiliser la réflection pour les charger dynamiquement durant l’exécution de l’application.

Bon code !

Vous pouvez suivre les réponses à cet article grâce à ce flux RSS 2.0.
Vous pouvez laisser un commentaire, ou faire un trackback.

Une Réponse à “Evaluation/Exécution d’une chaine”


  1. Julien :

    Excellent !
    Nettement mieux que de coller le code dans un fichier texte et d’exécuter le compilateur…
    Merci !

Laissez un Commentaire


Vers le haut