22 janvier 2009

Différentes façons de créer dynamiquement une méthode (2/3)

Ce billet est le 2ème de la série. Il traite de la création dynamique d'une méthode.

Avec l'espace de nom System.Reflection.Emit :

Alors que l'espace de nom System.Reflection contient les classes nécessaires pour interroger la structure du code en .Net, l'espace de nom System.Reflection.Emit permet de modifier dynamiquement les différents éléments (classes, structures, énumérations, etc …) du langage.

Pour ce qui concerne la génération d'une méthode à l'exécution, cela se fait simplement avec la classe System.Reflection.Emit.DynamicMethod ,tout du moins pour la déclaration de la méthode. Ensuite, et c'est là où ça devient un peu plus compliqué, il faut rajouter les instructions composant le corps de la méthode en injectant des instructions MSIL.

MSIL pour MicroSoft Intermediate Language ou CIL (Common Intermediate Language) est le langage intermédiaire utilisé par la machine virtuelle du Framework .Net. Celui-ci est ensuite compilé pour pouvoir être exécuté sur la machine hôte. En fait, les compilateurs de la plateforme .Net, comme celui du C# ou de VB .Net transforment le code produit par le développeur en langage MSIL.

Vous trouverez un tutorial en anglais sur le MSIL à l'adresse suivante : http://weblogs.asp.net/kennykerr/archive/2004/09/07/introduction-to-msil-part-1-hello-world.aspx. Comparé à un langage comme l'assembleur, le MSIL est orienté objet et adopte la notion de piles.

Je vous rassure, je ne suis pas du tout un expert en MSIL, enfin pas encore ! Pour m'y retrouver, j'utilise Reflector qui permet de décompiler un assemblage .Net et de voir le code MSIL produit. En comparant avec le code C# ou VB.Net, il est possible de s'y retrouver.

Passons maintenant au code, voici les instructions définissant notre méthode, faisant appel à une procédure définissant le corps de la méthode, un délégué est ensuite créé et assigné, et finalement celui-ci est appelé.

DynamicMethod dynMeth = new DynamicMethod("foo", typeof(void), null, typeof(Program));
WriteHello(dynMeth.GetILGenerator());
Method meth1 = (Method)dynMeth.CreateDelegate(typeof(Method));
meth1();


Le constructeur de DynamicMethod utilisé prend en paramètre:
  • Le nom de la méthode : "foo"

  • Le type renvoyé : typeof(void)

  • Un tableau de Type définissant les types des arguments de la méthode : null (notre méthode ne prend aucun argument)

  • Le type qui reçoit cette nouvelle méthode : typeof(Program). Pour l'exemple, c'est le nom de la classe contenant la méthode statique main.

Voici maintenant le corps de la procédure WriteHello(ILGenerator) qui définit le corps de la méthode générée :

static void WriteHello(ILGenerator cg)
{
    cg.Emit(System.Reflection.Emit.OpCodes.Ldstr, "Hello World avec System.Reflection.Emit !");
    cg.Emit(System.Reflection.Emit.OpCodes.Call,
    typeof(System.Console).GetMethod("WriteLine",
    new Type[] { typeof(string) }));
    cg.Emit(System.Reflection.Emit.OpCodes.Ret);
}


Premièrement, on ajoute sur la pile la chaine de caractères "Hello World avec System.Reflection.Emit !". En MSIL, il est obligatoire de faire cela pour utiliser une constante. La 2ème instruction fait un appel à la méthode statique Console.WriteLine prenant en argument une chaine de caractère. La chaine de caractère utilisée est celle qui est en haut de la pile, c'est dire "Hello World avec System.Reflection.Emit !". La dernière instruction signifie la fin du corps de la méthode (ceci correspond à return;).

Après compilation, vous devriez voir apparaître dans la console "Hello World avec System.Reflection.Emit !". C'est magique non ?!

Pour plus d'informations, l'archive ci-dessous reprend le code des 3 méthodes de génération dynamique présentées.

Aucun commentaire: