Génération de code : Création d'un Custom Tool pour Visual Studio 2008

Cet article décrit comment créer, tester et déployer un Custom Tool en utilisant Visual Studio 2008

N'hésitez pas à commenter cet article ! Commentez Donner une note  l'article (5)

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

Avant-Propos

Souvent, nous utilisons des Custom Tools sans le savoir. Ces outils simplifient le quotidien du développeur en générant par exemple des classes utilitaires à partir de fichiers XML. Visual Studio propose déjà un ensemble de Custom Tools, comme un générateur de code pour les fichiers de ressources et de configuration. Ces outils permettent de gagner un temps non négligeable pendant la phase de développement d'un projet. Il est bien plus facile d'accéder aux valeurs d'un fichier de ressources par l'intermédiaire d'une classe qu'en réalisant une requête XPath ou LinQ.

Visual Studio offre la possibilité de créer de tels outils et cet article explique chaque étape de la création d'un Custom Tool. Dans un 1er temps, nous verrons ce qu'est un Custom Tool et à quoi sert le Custom Tool proposé par l'article. Dans une 2ème partie, nous créerons une librairie où nous implémenterons le Custom Tool. Une fois créé, nous le testerons avec une application console. Finalement, nous réaliserons un programme d'installation afin de le déployer aisément. Il est à noter que le langage utilisé est le C#, mais qu'il est possible de traduire le code de l'article en VB .Net. assez facilement.

1. Qu'est ce qu'un Custom Tool ?

Un Custom Tool n'existe pas sans parler de Visual Studio. On peut dire simplement que c'est un générateur de code. Visual studio utilise par exemple un Custom Tool pour générer la classe utilitaire associée à un fichier de type " resx ". Celui-ci prend en entrée le fichier de ressource et construit une classe facilitant l'accès aux données XML. On en déduit qu'un Custom Tool accepte un fichier en entrée et crée un fichier en sortie.

Image non disponible

Le fichier créé est rattaché au fichier source. Visual Studio le traduit visuellement comme cela :

Image non disponible

La plupart des Custom Tools génère du code à partir de fichiers XML. Mais il est possible de faire l'inverse ou par exemple de générer un fichier de documentation à partir d'une classe commentée. On remarque que tout fichier contenu dans un projet Visual Studio peut avoir un Custom Tool rattaché. Ceci est visible dans la fenêtre de propriété :

Image non disponible

Pour exécuter un Custom Tool, il suffit de sauvegarder le fichier où le Custom Tool est rattaché. Ceci a pour effet d'exécuter la méthode " Generate " définit par l'interface " IVsSingleFileGenerator " de l'outil, que nous présenterons plus en détail lors de la phase d'implémentation. La méthode génère ensuite le fichier en sortie et Visual Studio 2008 l'attache au fichier source.

2. Objectif du Custom Tool proposé dans l'exemple

L'exemple choisit crée un Custom Tool qui utilise en entrée un fichier XML de déclaration de champs " fields " pour un site SharePoint, et produit une classe facilitant l'accès aux données.

Le Custom Tool proposé permet de récupérer l'identifiant unique (GUID) et le nom de chaque champ déclaré dans le fichier XML. L'outil génère ensuite une classe statique composée de propriétés statiques retournant les identifiants de chaque champ. Voici un extrait du fichier XML utilisé (se trouvant dans l'archive compressée de l'article) :

Image non disponible

3. Réalisation

3.1. Prérequis logiciels

Voici la liste des composants logiciels nécessaires pour la réalisation du Custom Tool :

3.2. Création du projet

Tout d'abord, il est nécessaire de créer un projet de type " Class Library ". Nommez le " HelloCustomTool " et cochez la case " Create directory for solution ". Laissez le nom de la solution comme tel et finalement validez la création du projet.

Image non disponible

Une fois le projet créé, il faut ensuite définir une référence à l'assembly " Microsoft.VisualStudio.Shell.Interop ". Celui-ci est fourni dans le SDK de Visual Studio que vous avez préalablement téléchargé. Cliquez avec le bouton droit sur le projet dans l'explorateur de solution et sélectionnez " Add reference ". Dans l'onglet " .Net ", vous trouvez l'assembly.

Avant de commencer à développer le Custom Tool, il est nécessaire de signer la librairie. Allez dans les propriétés du projet (Clic droit sur le projet, puis sélectionnez Properties). Dans l'onglet " Signing ", signez fortement l'assembly avec une nouvelle clé. Nommez-la " key.snk " et désactivez la protection par mot de passe.

Renommez ensuite le fichier " Class1.cs " en " HelloCustomTool.cs ". Nous sommes enfin prêts à implémenter le Custom Tool.

3.3. Implémentation de la classe principale : HelloCustomTool

Ajoutons tout d'abord les clauses " using " nécessaires à la classe " HelloCustom Tool " :

 
Sélectionnez

using System;
using System.CodeDom.Compiler;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell.Interop;				
				

Dans un 2ème temps, faites hériter la classe " HelloCustomTool " de l'interface " IVsSingleFileGenerator " et définissez les corps des méthodes suivantes " Generate " et " DefaultExtension ". Nous devons ensuite attacher un identifiant unique à notre classe. Pour cela, utilisez un attribut " Guid ". Allez dans le menu " Tools " et sélectionnez " Create GUID ". Choisissez l'option 4 et appuyez sur le bouton " Copy " et " Exit ". Ajoutez ensuite un attribut " Guid " à la classe " HelloCustomTool " et passez en paramètre l'identifiant stocké dans le presse papier (Ctrl+V). Voici le code de la classe " HelloCustomTool " :

 
Sélectionnez

namespace HelloCustomTool
{
    [Guid("E9832F96-4614-4530-ABF6-0438953D2355")]
    public class HelloCustomTool : IVsSingleFileGenerator
    {
        /// <summary>
        /// Determine l'extension du fichier créé
        /// </summary>
        /// <param name="ext"></param>
        /// <returns></returns>
        public int DefaultExtension(out string pExt)
        {
            pExt = ".cs";
            return 0;
        }

        /// <summary>
        /// Méthode appelée lors de la sauvegarde du fichier source
        /// </summary>
        /// <param name="pInputFilePath">Chemin du fichier source</param>
        /// <param name="pInputFileContents">Contenu du fichier source</param>
        /// <param name="pNamespace"></param>
        /// <param name="pOutputFileContents">Pointeur stockant l'adresse du contenu généré</param>
        /// <param name="pOutputFileContentSize">Taille du contenu généré</param>
        /// <param name="pGenerateProgress">Indicateur de progression du Custom Tool</param>
        /// <returns></returns>
        public int Generate(string pInputFilePath, 
                            string pInputFileContents, 
                            string pNamespace,
                            IntPtr[] pOutputFileContents,
                            out uint pOutputFileContentSize, 
                            IVsGeneratorProgress pGenerateProgress)
        {
            if (pInputFileContents == null)
                throw new ArgumentNullException(pInputFileContents);

            // Création du fournisseur de code permettant de transformer un graphe d'instructions et d'expressions en code
            CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("C#");

            // Génération de la classe "Fields" composée de propriétés statiques renvoyant des GUID
            byte[] generatedStuff = Generator.GenerateCode(pInputFileContents, codeProvider, pNamespace, pGenerateProgress);

            if (generatedStuff == null)
            {
                pOutputFileContents[0] = IntPtr.Zero;
                pOutputFileContentSize = 0;
            }
            else
            {
                // Copie du flux en mémoire pour que Visual Studio puisse le récupérer
                pOutputFileContents[0] = Marshal.AllocCoTaskMem(generatedStuff.Length);
                Marshal.Copy(generatedStuff, 0, pOutputFileContents[0], generatedStuff.Length);
                pOutputFileContentSize = (uint)generatedStuff.Length;
            }

            return 0;
        }

    }
}

La fonction " DefaultExtension " permet de définir l'extension du fichier créé par le Custom Tool. Elle prend en paramètre l'extension à renvoyer. La méthode " Generate " est appelée pour générer le fichier en sortie. Dans le corps de celle-ci, une instance de la classe " CodeDomProvider " est créée. Celle-ci permet de générer le code au format souhaité (ici en C#) à partir d'un graphe d'instructions et d'expressions. Ce graphe est construit grâce aux classes et méthodes de la bibliothèque " CodeDom ". La chaine de caractères " pInputFileContents" contient l'intégralité du fichier XML. Cette chaine servira à générer la classe " Fields " avec ses propriétés.

Notez l'appel à la méthode " Generator.GenerateCode ". Celle-ci prend en entrée le contenu du fichier XML, le provider de code créé, l'espace de nom ainsi que l'indicateur de progression du Custom Tool. Dans la prochaine partie, nous allons créer la classe " Generator " qui a pour rôle de générer le code de la classe " Fields ".

3.4. Implémentation de la classe Generator

Commencez par ajouter une classe au projet " HelloCustomTool ". Nommez cette classe " Generator ". Voici la liste des " using " à insérer dans notre nouvelle classe:

 
Sélectionnez

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using Microsoft.VisualStudio.Shell.Interop;

Avant d'implémenter la méthode " GenerateCode ", rendez la classe publique et statique en ajoutant les mots clés " public " et " static " comme suit :

 
Sélectionnez

namespace HelloCustomTool
{
    public static class Generator
    {

Voici le corps de la méthode " GenerateCode " :

 
Sélectionnez

/// <summary>
/// Méthode générant la classe publique contenant l'ensemble des accesseurs statiques
/// </summary>
/// <param name="pInputFileContents">Contenu du fichier XML</param>
/// <param name="pCodeProvider">Objet servant à générer le code à partir du graph d'instructions et d'expressions créé</param>
/// <param name="pCodeGeneratorProgress">Indicateur de progression du Custom Tool</param>
/// <returns>Tableau d'octets représentant le code de la classe générée</returns>
public static byte[] GenerateCode(string pInputFileContents,
                                  CodeDomProvider pCodeProvider,
                                  string pNamespace,
                                  IVsGeneratorProgress pCodeGeneratorProgress)
{
    CodeCompileUnit compileUnit;
    StreamWriter writer = new StreamWriter(new MemoryStream(), Encoding.UTF8);

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(pInputFileContents);

    // Generate class graph
    compileUnit = CreateClass(doc, pNamespace);

    if (pCodeGeneratorProgress != null)
        pCodeGeneratorProgress.Progress(0x4b, 100);

    // Generate Code
    pCodeProvider.GenerateCodeFromCompileUnit(compileUnit, writer, null);

    if (pCodeGeneratorProgress != null)
    {
        int errCode = pCodeGeneratorProgress.Progress(100, 100);
        if (errCode < 0)
        {
            Marshal.ThrowExceptionForHR(errCode);
        }
    }

    writer.Flush();
    return writer.BaseStream.StreamToBytes();

}

Dans un 1er temps, nous initialisons un objet " StreamWriter " qui permet de récupérer sous forme de chaine de caractère le code créé. Le contenu XML de la variable " pInputFileContent " est ensuite chargé dans un objet " XmlDocument " afin de récupérer facilement les éléments et attributs XML. Celui-ci est ensuite passé à la méthode " CreateClass " chargée de construire le graphe " CodeDom " représentant la classe. Le graphe en question est ensuite converti en code C# et stocké dans l'objet " StreamWriter " créé préalablement. Et finalement, le code généré est renvoyé sous la forme d'un tableau d'octets (byte[]). Notons l'utilisation de la méthode " SteamToBytes " sur la propriété " BaseStream " de l'objet " writer ". En réalité, c'est une méthode d'extension dont voici la définition et l'implémentation :

 
Sélectionnez

/// <summary>
/// Méthode renyant un tableau d'octets à partir d'un flux
/// </summary>
/// <param name="pStream"></param>
/// <returns></returns>
public static byte[] StreamToBytes(this Stream pStream)
{
    if (pStream.Length == 0)
        return new byte[0];

    long pos = pStream.Position;
    pStream.Position = 0;
    byte[] buffer = new byte[(int)pStream.Length];
    pStream.Read(buffer, 0, buffer.Length);
    pStream.Position = pos;

    return buffer;
}

Insérez cette méthode dans la classe " Generator ". Voyons maintenant la méthode " CreateClass ", qu'il vous faut aussi insérer dans la classe " Generator " :

 
Sélectionnez

/// <summary>
/// Méthode construisant la classe publique "Fields".
/// Celle-ci appelle la méthode CreateFields qui ajoute les propriétés statiques
/// </summary>
/// <param name="pXmlDoc"></param>
/// <returns></returns>
public static CodeCompileUnit CreateClass(XmlDocument pXmlDoc, string pNamespace)
{
    // Configuration du graph
    CodeCompileUnit code = new CodeCompileUnit();
    code.UserData.Add("AllowLateBound", false);
    code.UserData.Add("RequireVariableDeclaration", true);

    // Définition du namespace
    CodeNamespace nameSpace = new CodeNamespace(pNamespace);
    code.Namespaces.Add(nameSpace);

    // Définition de la classe publique "Fields"
    CodeTypeDeclaration classObject = new CodeTypeDeclaration("Fields");
    nameSpace.Types.Add(classObject);
    classObject.TypeAttributes = TypeAttributes.Public;

    // Génération des propriétés statiques de la classe
    XmlElement rootElement = pXmlDoc.DocumentElement;
    XmlNodeList xmlFields = rootElement.GetElementsByTagName("Field");
    CreateFields(classObject, xmlFields);

    CodeGenerator.ValidateIdentifiers(code);
    return code;
}

Cette méthode statique crée la classe publique " Fields " et insère pour chaque élément XML <Field> une propriété statique. Cette opération est d'ailleurs déléguée à la méthode " CreateFields " que voici et que vous devez insérer dans la classe " Generator " :

 
Sélectionnez

/// <summary>
/// Méthode qui ajoute une propriété statique pour chaque noeud XML
/// </summary>
/// <param name="pClassObject"></param>
/// <param name="pXmlFields"></param>
public static void CreateFields(CodeTypeDeclaration pClassObject,
                                XmlNodeList pXmlFields)
{
    foreach (XmlNode xmlField in pXmlFields)
    {
        string propName = string.Empty, propId = string.Empty;

        // Récupération des valeurs des attributs ID et Name 
        if (xmlField.Attributes.GetNamedItem("ID") != null)
            propId = xmlField.Attributes.GetNamedItem("ID").Value;

        if (xmlField.Attributes.GetNamedItem("Name") != null)
            propName = xmlField.Attributes.GetNamedItem("Name").Value;

        // Création de la propriété public et statique, renvoyant un guid
        // Exemple :
        // public static global::System.Guid TTT_Headlines {
        //    get { return new System.Guid("{B6684A6E-FBF5-4223-9679-56A7B4C3A356}"); }
        // }
        Type propertyType = typeof(Guid);
        CodeTypeReference propertyTypeReference = new CodeTypeReference(propertyType, CodeTypeReferenceOptions.GlobalReference);
        CodeMemberProperty property = new CodeMemberProperty();
        property.Name = propName;
        property.HasGet = true;
        property.HasSet = false;
        property.Type = propertyTypeReference;
        property.Attributes = MemberAttributes.Public | MemberAttributes.Static;

        // Création de l'instruction return
        CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement(new CodeObjectCreateExpression(typeof(Guid), new CodePrimitiveExpression(propId)));
        property.GetStatements.Add(returnStatement);

        // Ajout de la propriété à la classe "Fields"
        pClassObject.Members.Add(property);
    }
}

Pour chaque élément <Field> contenu dans le fichier XML sont récupérés les attributs " ID " et " Name ". L'attribut " Name " sert de nom pour la propriété statique et " ID " est utilisé dans l'instanciation du GUID retourné. Chaque propriété générée est ensuite ajoutée à la classe " Fields ".

La classe " Generator " est dès à présent fonctionnelle. Cependant, nous allons tout d'abord testé que le code généré soit bien formé. Pour cela, nous allons ajouter une application de type console à notre solution.

4. Test du Custom Tool à l'aide d'une application Console

Cliquez avec le bouton droit sur la solution dans l'explorateur de solution et ajouter un nouveau projet. Choisissez le modèle " Console Application ", nommez le projet " Test " et validez la création.

Il faut ensuite ajouter le projet " HelloCustomTool " en tant que référence dans le projet " Test ". Vous le trouverez dans l'onglet " Project " de la fenêtre " Add Reference ".

Dans l'archive compressée de l'article, se trouve le fichier XML " fields.xml ". Récupérez-le et ajoutez-le au projet en cliquant avec le bouton droit sur le projet " Test " dans l'explorateur de solution et en choisissant " Add Existing Item ". Sélectionnez-le, validez et définissez la propriété " Copy to Output directory " à " Copy Always " dans la fenêtre de propriété du fichier XML. Cette manipulation ordonne à Visual Studio de copier le fichier XML dans le répertoire de sortie du projet " Test " à chaque compilation du programme.

Image non disponible

Voici le corps de la méthode " Main " de la classe " Program ". La logique suivante appelle la méthode " GenerateCode " en passant le contenu du fichier XML " fields.xml " sous forme de chaîne de caractères.

 
Sélectionnez

using System;
using System.Xml;
using HelloCustomTool;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load("fields.xml");

            string result = Generator.GenerateCode(xmlDoc.OuterXml);
            Console.WriteLine(result);
            Console.Read();
        }
    }
}

Afin de tester plus facilement notre Custom Tool, la méthode " GenerateCode " de la classe " Generator " a été surchargée par une nouvelle méthode ne prenant que le contenu du fichier XML en entrée, dont voici l'implémentation :

 
Sélectionnez

/// <summary>
/// Méthode de test du Custom Tool
/// </summary>
/// <param name="pInputFileContents"></param>
/// <returns></returns>
public static string GenerateCode(string pInputFileContents)
{
    CodeCompileUnit compileUnit;
    StreamWriter writer = new StreamWriter(new MemoryStream(), Encoding.UTF8);

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(pInputFileContents);

    compileUnit = CreateClass(doc, "HelloCustomTool");

    CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("C#");
    codeProvider.GenerateCodeFromCompileUnit(compileUnit, writer, null);

    writer.Flush();
    System.Text.Encoding enc = System.Text.Encoding.UTF8;

    return enc.GetString(StreamToBytes(writer.BaseStream));
}

Rajoutez cette méthode à la classe statique " Generator " de votre librairie " HelloCustomTool ".

Nous sommes enfin prêts à tester notre Custom Tool ! Pressez la touche F5 pour compiler et lancer l'application de test. Auparavant, assurez-vous d'avoir sélectionné le projet " Test " en tant que projet de démarrage.

Image non disponible

Normalement, vous devriez voir l'intégralité du code de la classe " Fields " générée dans une fenêtre de commande.

5. Déploiement du Custom Tool

Pour que Visual Studio puisse exécuter notre Custom Tool, nous avons besoin de rajouter quelques clés dans la base de registres aux endroits suivants :

  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\CLSID
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}

Nous allons profiter de notre programme d'installation pour définir automatiquement ces clés dans la base de registres. De même, nous insèrerons la librairie " HelloCustomTool " dans le GAC.

Commençons par ajouter le 3ème et dernier projet à notre solution de type " Setup Project ". Nommez le " Installation " et validez sa création.

Image non disponible

Il faut maintenant définir l'installation de la librairie " HelloCustomTool " dans le GAC. Faites un clique droit sur le projet " Installation " dans l'explorateur de solution et sélectionnez " View > File System ". Sur la partie gauche de la fenêtre venant d'apparaître, faites un clic droit et sélectionnez " Add Special Folder > Global Assembly Cache Folder ". Assurez-vous de sélectionner ce nouveau répertoire, et dans la partie droite, faites un clic droit et " Add > Project Output ". Validez la fenêtre qui apparaît.

Image non disponible

Il ne reste plus qu'à définir les clés dans la base de registre. Pour cela, sélectionnez le projet d'installation dans l'explorateur de solution, cliquez droit dessus et sélectionnez " View > Registry ". Dans l'arborescence à gauche, créez les clés suivantes (clique droit sur un dossier et " New > Key "):

  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\CLSID
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}

Pour la clé " CLSID ", ajoutez une clé égale à la valeur du GUID de la classe " HelloCustomTool ". Dans notre exemple le GUID correspond à {E9832F96-4614-4530-ABF6-0438953D2355}.

Image non disponible

Ajoutez les chaines de caractères suivantes :

  • (Default) : Hello Custom Tool (Description, laissez le champ (Name) vide)
  • Assembly : HelloCustomTool, Version=1.0.0.0, Culture=neutral, PublicKeyToken=XXXXXXXXXX (Nom de l'assembly stocké dans le GAC)
  • Class : HelloCustomTool.HelloCustomTool (Nom complet de la classe du Custom Tool)
  • InprocServer32: [SystemFolder]mscoree.dll
  • ThreadingModel: Both

Voici ce que cela donne en image :

Image non disponible

Pour récupérer le PublicKeyToken de la librairie " HelloCustomTool ", vous pouvez utiliser l'utilitaire " sn.exe ". En ouvrant une fenêtre de commande Visual Studio 2008, placez vous dans le répertoire " Debug " de votre projet " HelloCustomTool " et exécutez la commande suivante :

sn.exe -T HelloCustomTool.dll

Vous pouvez aussi utiliser un " External Tool " dédié à cela. Le lien suivant vous explique comment faire :

http://blogs.msdn.com/miah/archive/2008/02/19/visual-studio-tip-get-public-key-token-for-a-stong-named-assembly.aspx

Concernant la clé finissant par {FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}, celle-ci indique que notre Custom Tool concerne les projets C#. Rajoutez une clé nommée " HelloCustomTool "à l'intérieur et définissez les valeurs suivantes :

  • (Default) : Hello Custom Tool (Description du Custom Tool)
  • CLSID: {E9832F96-4614-4530-ABF6-0438953D2355} (GUID de la classe HelloCustomTool)
  • GeneratesDesignTimeSource (DWORD):1
Image non disponible

Il ne reste plus qu'à installer et tester votre Custom Tool. Pour cela, compilez votre projet d'installation. Fermez Visual Studio 2008, allez dans le répertoire " Debug " de votre projet d'installation et double cliquez sur l'installeur. Une fois l'installation terminée, relancez Visual Studio 2008, ouvrez la solution, sélectionnez le fichier " fields.xml " et dans sa fenêtre de propriété, définissez " Custom Tool " à " HelloCustomTool ". Ouvrez le fichier " Fields.xml " et faites un " Ctrl +S ". Vous verrez apparaître un fichier " Fields.cs " contenant votre classe " Fields ".

Image non disponible

Conclusion

Cet article montre comment créer un Custom Tool dans Visual Studio 2008, le debugger et générer un programme d'installation. L'exemple peut servir de base pour créer d'autres Custom Tools qui simplifieront le travail du développeur. Visual Studio 2008 propose bien d'autres technologies qui ont pour finalité de générer du code, comme par exemple les DSL, pour " Domain Specific Language ". Ceux-ci permettent la création de langages graphiques utilisés pour générer n'importe quel type de sortie, dont nous verrons la création dans un prochain article.

En dernier lieu, je tiens à remercier Louis-Guillaume Morand pour son aide précieuse.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2008 Paul Musso. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.