mercredi 17 octobre 2007

Evaluation dynamique d'expressions

Article: Evaluation dynamique d'expressions en C#
 
Langage: C#
Environnement: Microsoft Visual Studio 2005
Description: Ce petit article présente comment réaliser en quelques lignes un analyseur d'expressions mathématiques, en utilisant la compilation dynamique.
 
Premier point: L'idée
Prenons le code suivant:
public Double Run()
{
  return 1+1+1;
}
Que va-t'on obtenir en appelant Run? Réponse: 3, bien évidement. Par contre, c'est purement statique.
Supposons maintenant une string contenant le texte suivant:
string _source =
@"using System;
public class Interpreter{{
public static Double Run(){{ return {0}; }}
}}";
 
Les doubles accolades sont là pour gérer le {0}, paramètre de formatage. Ainsi, on a dans notre string le code d'une micro-classe avec sa méthode, mais pas le fameux 1+1+1. Pour fixer une expression dans notre micro-classe, il fudrait appeller String.Format(_source, "1+1+1");
L'idée est donc de créer à la volée le code d'une micro-classe, puis de le compiler et de l'executer.
 
Deuxième point: Réalisation
Créaons un projet Windows application quelconque, avec une TextBox tbSource et un bouton btnGo.
Dans notre classe, déclarer les usings suivants:
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
 
 
Déclarer aussi _source:
private string _source =
@"using System;
public class Interpreter{{
public static Double Run(){{ return {0}; }}
}}";
 
 
Puis, sur le btnGo_Click, mettre le code suivant:
CSharpCodeProvider codeprovider = new CSharpCodeProvider ();
CompilerResults results = codeprovider.CompileAssemblyFromSource(new CompilerParameters(), String.Format(_source, tbSource.Text));
if (results.Errors.Count != 0) MessageBox.Show("Error" );
Type mainType = results.CompiledAssembly.GetType("Interpreter");
MethodInfo mainMethod = mainType.GetMethod("Run");
MessageBox.Show(mainMethod.Invoke(null, null ).ToString());
 
 
Et voilà, six lignes et c'est réglé !
Explications: le CSharpCodeProvider fournit tout un tas de méthode utilisables pour compiler du code C# dans une Assembly créée dynamiquement (par exemple en utilisant CompileAssemblyFromSource).
Ensuite, GetType("Interpreter") sur l'assembly générée nous donne notre nouveau type correspondant à notre nouvelle classe Interpreter, et GetMethod("Run") permet d'en récuperer l'unique méthode.
Enfin, Invoke sert à invoquer la méthode. Etant donnée que la méthode Run est statique et sans paramètre, alors les deux paramètres d'Invoke sont null (le premier est l'instance d'Interpreter à utiliser pour appeler Run, le second la liste des paramètres).
 
 
Dernier point: On teste
Lancons le programme. 1+1+1, réponse 3, ca passe.
Plus dur (12*5) + (9*8)/12, réponse 66, ca passe.
Encore plus dur, Math.Cos(Math.PI/3), réponse 0.5. Nickel
 
Et voilà, après c'est sûr que cette application n'a pas grand interêt mais cela montre que l'utilisation du CSharpCodeProvider et de la compilation dynamique sont assez simples, à vous de trouver les applications qui vont bien avec.

mercredi 26 septembre 2007

Article: Comment créer un service Windows présentant des objets .Net Remoting

Article: Comment créer un service Windows présentant des objets .Net Remoting

Langage: C#
Environnement: Visual Studio 2005
Description:
Les services Windows sont assez pratiques pour maintenir des processus en tâche de fond. Par ailleurs, le .Net Remoting est assez puissant puisqu'il permet à un client de récuperer un pseudo-objet (le véritable objet étant côté serveur), et d'en appeller les méthodes comme s'il été une véritable instance de l'objet (en pratique, des connections Tcp assurent la liaison avec le serveur).

Cet article a pour but de présenter un service présentant un objet Logger, ainsi que son client (un application console), qui pourra logger des informations, ainsi que lire et effacer les logs existants.
Premier point: L'interface
Permet de définir l'interface de l'objet présenté en .Net Remoting.
Créez un projet Log.Serivce.Interface de type Class Library, avec un fichier ILogger.cs
ILogger.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Log.Service.Interface
{
public interface ILogger
{
void AddLog(string log);
void ClearLogs();
string Logs { get ;}
}
}
Second point: Le service
Permet de créer le service à proprement parler.
Créez un projet Log.Service de type Windows Service, avec une classe Logger.cs, et renommez Service1.cs en Service.cs
Ajoutez une réference vers Log.Service.Interface et une autre vers System.Runtime.Remoting
Logger.cs
using System;
using System.Collections.Generic;
using System.Text;
using Log.Service.Interface;
namespace Log.Service
{
//MarshalByRef est la classe de base des objets présentable en remoting
internal class Logger : MarshalByRefObject, ILogger
{
private StringBuilder _logs;
//Permet de déclarer une durée de vie infinie pour cet objet
public override object InitializeLifetimeService()
{
return null ;
}
public Logger()
{
_logs = new StringBuilder();
}

#region ILogger Members
public void AddLog(string log)
{
_logs.AppendLine(String.Format( "{0} : {1}", DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss" ), log));
}
public void ClearLogs()
{
_logs = new StringBuilder();
}
public string Logs { get { return _logs.ToString(); } }
#endregion
}
}

Service.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting;
namespace Log.Service
{
public partial class Service : ServiceBase
{
//Declaration d'un channel
private TcpChannel _channel;
public Service()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
//Initialisation du channel
_channel = new TcpChannel(12345);
ChannelServices .RegisterChannel(_channel);
//Enregistrement d'un objet remoting de type logger, en mode singleton
//Le mode singleton permet de garder toujours la même instance de Logger
//(à la différence de SingleCall)
RemotingConfiguration .RegisterWellKnownServiceType(
typeof (Logger),
"Logger" ,
WellKnownObjectMode .Singleton);
}
protected override void OnStop() { }
}
}

Troisième point: Le client
Permet d'utiliser l'objet ILogger présenté par le service
Créez un projet Log.Client de type Console Application, avec une réference vers Log.Service.Interface et System.Runtime.Remoting
Program.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
using Log.Service.Interface;
namespace Log.Client
{
class Program
{
//Declaration d'un channel et d'un ILogger
private static TcpChannel _channel;
private static ILogger _logger;
static void Main(string[] args)
{
if (args.Length == 0)
{
Console .WriteLine("Log.Client");
Console .WriteLine("Syntax:");
Console .WriteLine("Log.Client /c\t\tClears the logs");
Console .WriteLine("Log.Client /g\t\tGets current logged text");
Console .WriteLine("Log.Client TEXT\t\tLogs the current text");
return ;
}
//Initialisation du channel
_channel = new TcpChannel();
ChannelServices .RegisterChannel(_channel);
//Recuperation du ILogger
_logger = Activator .GetObject(typeof(ILogger), "tcp://localhost:12345/Logger" ) as ILogger;
if (args[0] == "/c")
{
_logger.ClearLogs();
Console .WriteLine("Logs cleared");
return ;
}
if (args[0] == "/g")
{
Console .WriteLine(_logger.Logs);
return ;
}
StringBuilder message = new StringBuilder();
foreach ( string arg in args)
message.Append(arg + " ");
_logger.AddLog(message.ToString());
Console.WriteLine( "Message {0} logged", message.ToString());
}
}
}

Dernier point: Vérifier que tout marche bien
Création du service: via le Visual Studio 2005 Command Prompt, tapez (l'espace entre "binpath=" et le chemin est nécessaire)
sc create Log.Service binpath=
Lancement du service: via le Visual Studio 2005 Command Prompt, tapez:
net start Log.Service
Test de l'application: essayez les commandes suivantes
Log.Client , plusieurs fois avec différents messages, puis
Log.Client /g pour tout récuperer, et enfin
Log.Client /c pour tout effacer.
Optionnel:
Pour arrêter le service:
net stop Log.Service
Pour supprimer le service:
sc delete Log.Service
Et voilà, libre à vous d'imaginer de nouveaux modes d'utilisation sur le même principe.
David

mercredi 19 septembre 2007

Création

Allez, c'est parti, création de mon blog.
 
Le blog, c'est bien, c'est pratique et tout et tout. Alors c'est dit je m'y met aussi. J'ai souvent des idées, des coups de gueules, des réalisations sympatiques: allez hop à partir de maintenant je les partagerai ici (si j'y pense).
 
Présentation rapide: David, ingénieur de formation, développeur durant les heures de bureau et le temps libre aussi, mais à des fins plus variées: du gadget google à la grosse application Windows .Net, du Javascript au C++, bref un panel assez large.
Réalisations actuelles: Le problème c'est qu'elles trainent un peu partout. Commencez donc par www.scarta.info, les plus anciennes sont trop éparpillées pour que je puisse les regrouper ou même m'en souvenir. Les prochaines en tout cas seront listées ici.
 
--
I push my deadlines closer than anybody else, or let's say it this way: I'm really late