1 commentaires samedi 10 octobre 2009

Dans le .NET Framework, on retrouve l'interface IEnumerable (ainsi que sa version générique, IEnumerable<T> depuis la version 2.0). Toute classe implémentant cette interface pourra être parcourue avec un foreach.

Java possède, depuis sa version 1.5, un équivalent, l'interface Iterable. Une classe implémentant Iterable pourra être parcourue avec un for.

En C#, j'ai tendance à utiliser le plus possible IEnumerable: c'est le dénominateur commun. Alors quand je me suis mis à faire du Java, j'ai décidé d'employer Iterable pour les mêmes raisons. Sauf que j'ai découvert quelque chose d'étrange.

Avec .NET, les tableaux implémentent IEnumerable. On peut donc passer un tableau à une méthode qui attend un IEnumerable en paramètre. Or, en Java, les tableaux n'implémentent pas Iterable. On peut les parcourir comme un Iterable, mais c'est probablement du à une "patch" du compilateur. Je n'ai aucune idée de pourquoi est-ce que c'est le cas, mais toujours est-il qu'on ne peut donc pas faire la même chose qu'avec .NET.

Mais ce n'est pas vraiment un problème. Il suffit de coder un adaptateur! Et c'est ce que j'ai fait:


import java.util.Iterator;
import java.util.NoSuchElementException;

/**
* Un adaptateur permettant d'utiliser un tableau là où un Iterable<T> est requis
* @author Etienne de Martel
* @param <T> Le type d'un élément du tableau
*/
public class IterableArray<T> implements Iterable<T>
{
// Un itérateur sur un tableau
private class ArrayIterator implements Iterator<T>
{
T[] _array;
int _index;

public ArrayIterator(T[] array)
{
_array = array;
_index = 0;
}

@Override
public boolean hasNext()
{
return _index < _array.length;
}

@Override
public T next()
{
if(!hasNext())
throw new NoSuchElementException();
return _array[_index++];
}

// Cet itérateur est en lecture seule
@Override
public void remove()
{
throw new UnsupportedOperationException();
}

}

private T[] _array;

/**
* Construit un nouvel adaptateur
* @param array Le tableau autour duquel l'adaptateur sera construit
* @throws NullPointerException Le tableau passé en paramêtre est null
*/
public IterableArray(T[] array) throws NullPointerException
{
if(array == null)
throw new NullPointerException("array cannot be null");
_array = array;
}

@Override
public Iterator<T> iterator()
{
return new ArrayIterator(_array);
}

}


Voilà. Have fun.

1 commentaires lundi 28 septembre 2009

Ne vous inquiétez pas, je devrais arriver avec des choses plus intéressantes dans les jours à venir (si j'ai le temps)...

Les templates de C++ permettent d'écrire du code générique, c'est à dire de se faire chier une seule fois pour ensuite pouvoir utiliser le même code partout. Enfin, c'était là leur but premier, et puis un gars (David Vandevoorde, si je me souviens bien) a découvert qu'elles étaient turing complete. Cela signifie qu'on peut écrire, avec des templates, des programmes qui s'exécuteront à la compilation.

L'exemple le plus répandu est une factorielle en O(1):


#include <iostream>

template<int N>
struct Factorial
{
static const int Value = N * Factorial<N - 1>::Value;
};

template<>
struct Factorial<0>
{
static const int Value = 1;
};

int main()
{
std::cout << Factorial<6>::Value; // affiche 720
}
La valeur de la factorielle sera évaluée à la compilation, et sera équivalente à une constante.

On peut également créer des assertions statiques (des tests qui font planter la compilation s'ils ne passent pas) avec des templates. À noter qu'une telle pratique sera rendue obsolète en C++0x, qui inclura un support standard pour de telles assertions.

En Java, pour éviter les abus de downcasts de Object à quelque chose, on a introduit dans la version 1.5 les Generics. C# a eu tôt fait de copier le concept dans sa version 2.0. Mis à part une syntaxe légèrement différente pour les contraintes, la principale nuance entre C# et Java est qu'en Java, les Generics sont remplacés par des Object avec des casts, alors qu'en C#, ils sont remplacés par le type utilisé. Ces deux approches se valent, bien que je préfère celle de C#: elle permet, entre autres, d'effectuer de la réflexion sur les types génériques.

Pour celui qui connaît peu les templates de C++, on serait porté à croire que les Generics sont très semblables, mais il y a énormément de différences entre les deux. La plus importante est que les templates sont instanciés à la compilation, et le compilateur peut donc évaluer immédiatement si elles sont correctement utilisées (et si ce n'est pas le cas, générer un message d'erreur incompréhensible). En Java et en C#, les generics sont instanciés à l'exécution, et le compilateur ne peut donc pas s'assurer qu'elles sont bien utilisées. Le programmeur doit donc insérer des "contraintes", sinon, le type générique sera considéré comme un Object, ce qui tue un peu le principe. De telles contraintes sont inutiles en C++, car les templates répondent au principe de SFINAE.

Pourquoi une telle discussion? J'ai trouvé un vieil article sur MSDN décrivant les différences entre les templates de C++ et les Generics de C#:
http://msdn.microsoft.com/en-us/library/c6cyy67b(loband).aspx
En passant, pour ceux qui ne le savent pas, le "(loband)" dans l'URL, c'est pour activer le "low bandwidth mode" sur MSDN, vous permettant de sauver de la bande passante.

Voilà.

0 commentaires dimanche 9 août 2009

(Désolé pour le titre interminable)

C'est pas compliqué, mais fallait y penser.

Disons que vous avez en tant qu'attribut une std::string, et que vous voulez l'initialiser avec le contenu d'un flux reçu en paramètre au constructeur. Vous pourriez bien sûr choisir l'approche lente:


class Script
{
std::string _script;
public:
Script(std::istream &stream)
{
std::string line;
while(std::getline(stream, line))
_script += line + '\n';
}
// ...
};
Le problème avec cette technique, c'est que l'attribut est d'abord construit avec le constructeur par défaut, puis modifié. Ce problème est dû au fait que par défaut, les objets en C++ ont une sémantique de valeur (ils sont donc traités comme des types primitifs). Si l'exemple ci-dessus avait été en C# ou en Java, _script aurait été une référence null, et donc, aucune mémoire n'aurait été allouée inutilement.

Pour court-circuiter (+10 points pour le beau terme) la construction par défaut, il faut passer par une liste d'initialisation. L'un des constructeurs de std::string prend une paire d'itérateurs représentant une séquence de char (wchar_t pour std::wstring). Avec notre connaissance des std::istream_iterator, on serait donc tenté de faire ça:

Script(std::istream &stream)
: _script(std::istream_iterator<char>(stream), std::istream_iterator<char>())
{
}
Ça compile et ça ne plante même pas! Sauf que ça n'a pas le résultat escompté.

std::istream_iterator utilise l'opérateur >>, lequel a un bien étrange comportement: il consomme tous les caractères blancs (c'est-à-dire ceux pour lesquels std::isspace() retourne true pour la locale actuellement utilisée par le flux). Avec la culture classique (celle du langage C), cela signifie les espaces, les tabulations (\t), les retours chariots (\r) et les retours de ligne (\n). Notre attribut _script ne contiendra donc pas tous les éléments!

La solution consiste donc à utiliser std::istreambuf_iterator, lequel n'effectue aucun traitement sur le contenu du flux:

Script(std::istream &stream)
: _script(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>())
{
}
Naturellement, si vous ne voulez lire qu'une partie du flux, cette technique ne fonctionnera pas.

Voilà! Comme d'habitude, j'attends vos commentaires.

0 commentaires mercredi 5 août 2009

Tcl signifie "Tool Command Language". C'est un langage qui a été conçu spécifiquement pour ajouter des fonctionnalités de scripting à d'autres langages. Puisqu'un binding existe en standard pour le C, il s'en fallu de peu pour que quelqu'un ait la bonne idée d'en faire un wrapper C++.

Ça a pris un peu de gossage, mais je suis arrivé à faire marcher le tout sous Windows avec VC++9 (AKA Visual Studio 2008).

Donc, C++/Tcl est composé de deux fichiers (un .h et un .cpp). Il a également deux dépendances: le core de Tcl et... Boost. Seuls quelques headers de Boost sont utilisés, pas besoin de linker quoique ce soit. Pour Tcl, c'est une autre histoire.

Ah, Boost. Quelle superbe bibliothèque. Je dois dire que je n'étais jamais arrivé à la faire fonctionner. Quelle n'a pas été ma surprise de découvrir qu'une entreprise du nom de BoostPro Computing offrait un installateur permettant d'installer Boost en mode "next next finish". Ne vous inquiétez pas, c'est entièrement gratuit et ça ne corromps pas la licence outre mesure. Vous pouvez obtenir ledit installateur là: http://www.boostpro.com/download. J'ai téléchargé la version 1.39, c'est-à-dire la plus récente au moment d'écrire ces lignes. L'installateur installe par défaut toutes les bibliothèques "header only". On peut également installer d'autres bibliothèques (celle qui doivent être linkées à la compilation). Assurez vous de n'installer que les versions allant avec le compilateur de votre choix (VC++7.1 pour VS2003, VC++8 pour VS2005 ou VC++9 pour VS2008), sinon vous allez télécharger trois fois trop de données que vous n'utiliserez même pas en fin de compte!

L'installateur n'aura peut-être pas ajouté le chemin à vos répertoires d'include standard. Dans ce cas, il vous faudra configurer Visual Studio en allant dans Outils > Options > Projets et solutions > Répertoires de VC++. Dans le menu en haut à droite, sélectionnez "Fichiers Include" (pour les .h) ou "Fichiers bibliothèques" (pour les .lib/.dll), dépendamment de ce que vous voulez configurer.

La meilleure façon de tester si Boost fonctionne est d'y aller avec l'exemple fournis par Boost (à noter que vous Boost.Regex n'est pas installé par défaut avec l'installateur, vous devrez donc avoir coché la case pour que cela fonctionne):


#include <boost/regex.hpp>
#include <iostream>
#include <string>

int main()
{
std::string line;
boost::regex pat( "^Subject: (Re: |Aw: )*(.*)" );

while (std::cin)
{
std::getline(std::cin, line);
boost::smatch matches;
if (boost::regex_match(line, matches, pat))
std::cout << matches[2] << std::endl;
}
}

Maintenant, Tcl. Vous pouvez l'obtenir à cet endroit: http://www.activestate.com/activetcl/ (cliquez sur le "Download Now"). Vous devrez ensuite (encore) configurer Visual Studio. À noter que cette fois-ci, pour que ça ne chie pas à l'édition des liens, j'ai du ajouter "tcl85.lib" aux propriétés de mon projet sous Éditeur de lien > Entrée > Dépendances supplémentaires.

Ok, dernière étape: C++/Tcl lui même. Rendez-vous là: http://sourceforge.net/projects/cpptcl/files/. Bon, il faut ajouter cpptcl.cc et cpptcl.h à votre projet, et vous assurer que le répertoire details fournit avec C++/Tcl soit quelque part où le préprocesseur peut le trouver. Là, problème: le code ne compilait pas pour cause d'ambiguïté entre std::exception et boost::exception. Il faut dire que le gars n'a testé son code que sous GCC. Solution? Retirer le using namespace std en haut de cpptcl.cc, et ajouter std:: partout où c'est nécessaire (en fait, à quelques endroits seulements, là où des std::string sont utilisées).

Je suis allé ramasser le Hello World sur le site de C++/Tcl, et ça marche!

#include "cpptcl.h"
#include <iostream>
#include <string>

using namespace std;
using namespace Tcl;

void hello()
{
cout << "Hello C++/Tcl!" << endl;
}

int main()
{
interpreter i;
i.def("hello", hello);

string script = "for {set i 0} {$i != 4} {incr i} { hello }";

i.eval(script);
}

Bon, je vais voir ce que ça donne. À suivre.

1 commentaires mercredi 22 juillet 2009

Java était (et est toujours, d'ailleurs) un langage "purement orienté objet". Quelle décision étrange. Non seulement Java est, selon moi, moins orienté objet que C++ (presque pas de contrôle sur la fin de vie d'un objet, notamment), mais ça voulait également dire qu'il ne pouvait faire rien d'autre que de la POO. Pourtant, je me dis que du procédural, parfois, c'est utile.

C# répète à peu près les mêmes erreurs que Java pour la POO, mais compense en s'ouvrant à d'autres paradigmes. Dans sa version 3.0, C# a donc mis un pied (ou plutôt le gros orteil) dans le monde de la programmation fonctionnelle avec des lambda expressions et des expression trees (bon, on pouvait déjà faire ça depuis 1998 en C++ avec des templates, mais bel effort quand même). Et depuis sa toute première version, C# supporte un hybride entre méthodes et fonctions libres sous la forme de délégués (un pointeur déguisé sur une fonction, en fait). Bon, d'accord, je pourrais également faire le parallèle avec C++, mais pourquoi taper sur quelqu'un qui est à terre?

Je sais pas pour vous, mais moi, écrire une classe pour ne l'utiliser qu'une seule fois, comme foncteur sans état en plus, je trouve ça lourd. C'est d'ailleurs pourquoi, en C++, je préfère les fonctions aux foncteurs lorsque c'est possible: c'est beaucoup moins verbeux (mais bon, une fonction n'a pas d'état, alors il faut parfois sortir les foncteurs). Alors bon, quand je vois, dans le .NET Framework, une méthode qui attend un IEqualityComparer<T>, je sacre. Pourquoi devrais-je implémenter une classe pour une fonctionnalité qui n'existera que là? Ne serait-il pas plus simple de passer un délégué à la place? Martin Fowler n'a-t-il pas dit que la généricité préventive était néfaste?

J'ai donc retroussé mes manches, et voici ce à quoi je suis arrivé:


public static class EqualityComparer
{
private class DelegateComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _equalityFunction; // prend deux T en paramètre et retourne un bool
private readonly Func<T, int> _hashCodeFunction; // prend un T en paramètre et retourne un int

public DelegateComparer(Func<T, T, bool> equalityFunction, Func<T, int> hashCodeFunction)
{
if (equalityFunction == null || hashCodeFunction == null)
throw new ArgumentNullException();
_equalityFunction = equalityFunction;
_hashCodeFunction = hashCodeFunction;
}

public bool Equals(T x, T y)
{
return _equalityFunction(x, y);
}

public int GetHashCode(T obj)
{
return _hashCodeFunction(obj);
}
}

public static IEqualityComparer<T> Make<T>(Func<T, T, bool> EqualityFunction, Func<T, int> HashCodeFunction)
{
return new DelegateComparer<T>(EqualityFunction, HashCodeFunction);
}
}

Bon, quel intérêt? Eh bien, admettons (cet exemple utilise des lambdas, mais vous pourriez très bien passer des méthodes, tant qu'elles ont la bonne signature):

var absComparer = EqualityComparer.Make<int>(
(p1, p2) => Math.Abs(p1) == Math.Abs(p2),
p => Math.Abs(p).GetHashCode());

Et voilà, un IEqualityComparer<T> en une ligne (étalée sur plusieurs lignes pour plus de clarté)! C'est tu pas wonderful?

Dites-moi ce que vous en pensez. Comme toujours, ce n'est pas final.

2 commentaires mercredi 15 juillet 2009

EDIT 17/07/2009: Suite à un commentaire de mon bon ami Andy, j'ai apporté quelques modifications au code.

Tous ceux qui se sont amusés un tant soit peu avec les flux d'entrée en C++ ont probablement déjà entendu parler des std::istream_iterator. Ils permettent de parcourir un flux entrée (une instance d'une classe héritant de std::istream) comme si c'était une séquence. Un appel à l'opérateur ++ d'un tel flux revient à utiliser l'opérateur >> sur le flux, et on peut ensuite utiliser les opérateurs * et -> pour accéder à l'élément précédemment lu. Un std::istream_iterator est unidirectionnel (on ne peut qu'avancer dans le flux) et en lecture seule (évidemment, puisque l'on s'en sert pour lire d'un flux). Un std::istream_iterator constant n'a donc pas de sens.

L'idéal est de passer par un exemple. Imaginons que nous voulons lire, dans un fichier, un ensemble de nombres entiers pour ensuite les insérer dans un vecteur. La première solution serait d'avoir recours à une boucle dans laquelle on insère les entiers lus dans le vecteur avec push_back(). Ou bien, on peut également faire la même chose en une ligne en mélangeant std::copy, un std::istream_iterator et un std::back_inserter:


std::ifstream file("hello.txt");
std::vector<int> v;
std::copy(std::istream_iterator<int>(file), std::istream_iterator<int>(), std::back_inserter(v));
std::cout << v.size();

Une petite explication s'impose. istream_iterator possède deux constructeurs: l'un deux reçois un flux d'entrée (std::istream) à parcourir. Le second (celui qui ne prend pas de paramètres) représente un flux invalide. Ainsi, dans notre exemple précédent, si le flux tombe dans un état invalide (la fin du fichier, par exemple), alors les deux itérateurs deviendront égaux, permettant de sortir de la boucle.
On copie donc du début (premier paramètre) à la fin (second paramètre) du flux, en envoyant les éléments à un std::back_inserter, lequel ne fait que passer l'élément reçu à la méthode push_back() du vecteur.

Malheureusement, std::istream_iterator utilise >> pour lire les données. Mais que se passe-t-il si l'on veut lire avec la si utile fonction std::getline()?

D'où l'idée que j'ai eu de coder un getline_iterator. Il fonctionne exactement comme un std::istream_iterator, mais utilise std::getline() plutôt que l'opérateur >> pour lire les données.

On commence donc par inclure les fichiers nécessaires: iterator, string et istream.
La classe est une classe template, spécialisée d'après un certain type de caractères. std::getline() ne lit en effet que des chaînes de caractère, il est donc inutile de la rendre plus générique. On peut également spécifier des traits de caractère, et les traits de base du type de caractère spécifié sont utilisés par défaut.

La classe hérite de std::iterator, et spécifie trois choses: basic_getline_iterator est un itérateur d'entrée (input_iterator), il lit des éléments de type std::basic_string<CharType, Traits>, et la distance entre les éléments est de type std::basic_string<CharType, Traits>::size_type (la même que pour une string, donc).

#ifndef GETLINE_ITERATOR_H
#define GETLINE_ITERATOR_H

#include <iterator>
#include <string>
#include <istream>

template<class CharType, class Traits = std::char_traits<CharType> >
class basic_getline_iterator : public std::iterator<std::input_iterator_tag,
std::basic_string<CharType, Traits>,
typename std::basic_string<CharType, Traits>::size_type>
{

Pour garantir l'intéropérabilité avec les algorithmes et classes de la STL, on définit quelques typedefs. Seuls les cinq premiers sont obligatoires. Le dernier, stream_type, est là pour simplifier la syntaxe, plus loin. Je l'ai laissé en public car je ne voyais pas vraiment la nécessité de le cacher.

public:
typedef std::input_iterator_tag iterator_category;
typedef typename std::basic_string<CharType, Traits> value_type;
typedef typename std::basic_string<CharType, Traits>::size_type distance;
typedef value_type & reference;
typedef value_type * pointer;

typedef typename std::basic_istream<CharType, Traits> stream_type;

On a trois attributs: l'élément en lu lors du dernier appel à l'opérateur ++ (un basic_string), un pointeur sur le flux (nous verrons pourquoi un pointeur dans quelques instants) et le caractère faisant office de séparateur (utile pour std::getline()).

private:
value_type _current;
stream_type * _stream;
const CharType _separator;

Le premier constructeur (celui qui représente un flux invalide) ne fait qu'assigner 0 au pointeur, et \0 au séparateur (le séparateur doit être initialisé puisqu'il est constant). Le second constructeur reçoit une référence sur un std::istream (tous les flux sont incopiables, donc une référence est obligatoire), et assigne l'adresse de ce flux au pointeur. Un paramètre optionnel peut également être passé pour faire office de séparateur. Par défaut, il s'agit du retour de ligne (ça ne s'appelle pas "getline" pour rien).

public:
basic_getline_iterator() : _stream(0), _separator('\0') {}
basic_getline_iterator(stream_type & stream, char separator = '\n') : _stream(&stream), _separator(separator) {}

L'opérateur++ est disponible en version préfixée (première version) et postfixée (seconde version). La version préfixée appelle std::getline en passant les paramètres requis, puis teste la valeur de retour de la fonction. std::getline() retourne ne effet le flux passé comme premier paramètre. Ce flux peut être convertit implicitement en void *. Si le pointeur est nul, alors le flux est invalide. Donc, s'il est invalide, on assigne 0 au pointeur. Suite à un commentaire, j'ai décidé d'ajouter un test à l'opérateur ++ pour empêcher un plantage lamentable si quelqu'un venait à incrémenter le pointeur alors que l'itérateur est invalide.
La version postfixée créée une copie, appelle la version préfixée, puis retourne la copie.

basic_getline_iterator operator++() throw()
{
if(_stream)
{
if(!std::getline(*_stream, _current, _separator))
_stream = 0;
}
return *this;
}
basic_getline_iterator operator++(int) throw()
{
basic_getline_iterator it(*this);
operator++();
return it;
}

Une simple comparaison de pointeurs. Ainsi, si les deux itérateurs représentent des flux invalides, ils seront considérés comme égaux.

bool operator==(const basic_getline_iterator &it) const throw()
{
return _stream == it._stream;
}
bool operator!=(const basic_getline_iterator &it) const throw()
{
return !(*this == it);
}

Déréférencer l'itérateur ne fait que retourner une référence (ou un pointeur) sur l'élément précédemment lu.

const value_type & operator *() const throw()
{
return _current;
}
const value_type * operator->() const throw()
{
return &_current;
}

Pour finir, on offre deux typedefs correspondants aux deux utilisations principales de basic_getline_iterator. Voilà!

};

typedef basic_getline_iterator<char> getline_iterator;
typedef basic_getline_iterator<wchar_t> wgetline_iterator;
#endif


Rien de mieux qu'un exemple:

#include<iostream>
#include <fstream>
#include <string>
#include <iterator>
#include <algorithm>
#include <vector>
#include "getline_iterator.h"

int main()
{
// lire les lignes d'un fichier puis les insérer dans un vecteur
std::ifstream file("test.txt");
std::vector<std::string> v;
std::copy(getline_iterator(file), getline_iterator(), std::back_inserter(v));
}


Cet itérateur est encore en développement, et il se peut que je revienne apporter des modifications (notamment la const-correctness). À suivre.

1 commentaires dimanche 17 mai 2009

Disons que vous avez une base de données contenant une table User, contenant les informations des utilisateurs de votre site. Vous voulez probablement représenter un ensembles d'options sous la forme de booléens (ENUM ou BOOLEAN sous MySQL, bit sous SQL Server, etc.). Le problème avec cette approche, est que si vous voulez ajouter des options additionnelles, vous devrez modifier la structure de la base de données. Et je ne sais pas pour vous, mais je ne suis pas très chaud à l'idée de faire des ALTER TABLE sur une BD live. Vous pouvez donc ajouter une table d'options, et une table de liaison reliant une option à un utilisateur (avec probablement la valeur de cette option). Mais c'est plutôt lourd. Une solution au problème serait donc d'utiliser une colonne, d'un type supportant un entier 32 bits (INT sous MySQL, int sous SQL Server, etc.), de votre table User, pour stocker des flags. À coup de manipulation de bits, on pourrait donc avoir 32 options différentes avec un seul int. Sauf que les manipulation de bits, c'est complexe. Et c'est l'enfer à maintenir.

C'est alors que j'ai découvert le struct BitVector32, du namespace System.Collections.Specialized. Il permet de représenter un int (System.Int32) comme un ensemble de booléens. Il a également d'autre utilisations, mais je ne les couvrirai pas ici. Pour plus d'informations, consultez MSDN.

Commençons par définir une énumération contenant les divers flags qui s'appliqueront à notre utilisateur:


[Flags]
public enum UserFlags
{
IsActive = 1,
IsBanned = 2,
ShowEmail = 4
}
Il est important que chaque élément de l'énumération ait pour valeur une puissance de 2. De même, l'attribut Flags n'est pas obligatoire, mais permettrait à notre énumération d'être utilisée comme flags dans un contexte "à la C", avec les valeurs séparées par des pipes (|).

Puis, créons une petite classe User. Elle pourrait être mappée à l'aide d'un ORM comme NHibernate ou SubSonic.

public class User
{
public string Username { get; private set; }
public string Password { get; private set; }
public string Email { get; private set; }
private BitVector32 _flags;

public User(string username, string password, string email)
{
Username = username;
Password = password;
Email = email;
_flags = new BitVector32();
}
}
Puis, on ajoute des propriétés à notre classe User:

public bool IsActive
{
get { return _flags[(int)UserFlags.IsActive]; }
set { _flags[(int)UserFlags.IsActive] = value; }
}

public bool IsBanned
{
get { return _flags[(int)UserFlags.IsBanned]; }
set { _flags[(int)UserFlags.IsBanned] = value; }
}

public bool ShowEmail
{
get { return _flags[(int)UserFlags.ShowEmail]; }
set { _flags[(int)UserFlags.ShowEmail] = value; }
}
Voilà. L'usage est entièrement transparent; du côté du code client, on a l'impression de manipuler des booléens, alors qu'en réalité, tout se passe dans un int.

On peut également, pour des fins de tests, produire une propriété RawFlags qui retournera la valeur entière de notre BitVector32:

public int RawFlags
{
get
{
return _flags.Data;
}
}
Lorsqu'il viendra le temps d'enregistrer les flags dans la BD, il suffira alors de prendre la valeur contenue dans la propriété Data du vecteur. On peut effectuer l'opération inverse en utilisant le constructeur de BitVector32 qui prend un int.

Voilà!

0 commentaires lundi 27 avril 2009

Voici ce que le W3C a à dire sur les champs de formulaire dit "disabled":

When set, the disabled attribute has the following effects on an element:
- Disabled controls do not receive focus.
- Disabled controls are skipped in tabbing navigation.
- Disabled controls cannot be successful.


Que signifie ce terme "successful"? Eh bien, toujours d'après le W3C:

A successful control is "valid" for submission. Every successful control has its control name paired with its current value as part of the submitted form data set. A successful control must be defined within a FORM element and must have a control name.


En clair: d'après le standard, les champs "disabled" ne sont pas envoyés au serveur. Si vous tenez à bloquer l'accès à un contrôle mais que vous voulez quand même qu'il soit envoyé au serveur, il y a deux choix: du JavaScript, ou encore l'attribut readonly:

When set, the readonly attribute has the following effects on an element:
- Read-only elements receive focus but cannot be modified by the user.
- Read-only elements are included in tabbing navigation.
- Read-only elements may be successful.


Jamais je n'aurais cru que le W3C m'aiderait à résoudre un bogue...

0 commentaires lundi 16 mars 2009

J'ai récemment voulu peupler les données d'un DropDownList ASP.NET avec les éléments contenus dans une énumération. Avec un peu de recherches, j'ai découvert la classe utilitaire Enum permettant d'obtenir des informations intéressantes sur une énumération. J'ai donc codé une méthode pour retourner sous la forme d'un Dictionnary les éléments d'une énumération donnée:


using System;
using System.Collections.Generic;

namespace Util
{
public static class EnumUtils
{
/// <summary>
/// Obtient les éléments composant une énumération
/// </summary>
/// <typeparam name="TEnum">Le type de l'énumération</typeparam>
/// <exception cref="ArgumentException">Lancée si TEnum n'est pas une énumération</exception>
/// <returns>Un dictionnaire avec comme clé la valeur entière d'un élément et comme valeur, son nom</returns>
public static IDictionary<int, string> GetEnumElements<TEnum>()
{

var type = typeof(TEnum);
if (!type.IsEnum)
throw new ArgumentException("Type must be an enum type");

var names = Enum.GetNames(type);
var values = Enum.GetValues(type);

var elements = new Dictionary<int, string>();
for (int i = 0; i < names.Length; ++i)
{
elements.Add((int)values.GetValue(i), names[i]);
}
return elements;
}
}
}

La présence du mot clé var force l'utilisation d'un compilateur respectant la norme 3.0 du langage, mais il suffit de retirer ceci pour permettra la compatibilité avec la version 2.0.

0 commentaires lundi 19 janvier 2009

Victoire! Je me suis débarrassé de cet ignoble objet global et j'ai en même temps appris l'existence du type v8::External, qui est un enrobage autour d'un void *.

J'ai tout d'abord commencé par définir un typedef pour éviter une verbosité excessive:


typedef TestCollection<std::string, int> Collection;

Commençons d'abord par le main:

int main()
{
// La collection
Collection testCollection;
v8::HandleScope handleScope;
// On créé l'objet global
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
// On créé la fonction print
global->Set("print", v8::FunctionTemplate::New(print));
// On créé le template de la collection
v8::Local<v8::ObjectTemplate> collectionTemplate = v8::ObjectTemplate::New();
// On créé les deux méthodes du template
collectionTemplate->Set("getObjectById", v8::FunctionTemplate::New(getObjectById));
collectionTemplate->Set("setObject", v8::FunctionTemplate::New(setObject));
// On informe le template qu'il aura un champ interne
// (si on ne le fait pas, on aura un débordement de tableau plus tard)
collectionTemplate->SetInternalFieldCount(1);
// On créé le contexte
v8::Handle<v8::Context> context = v8::Context::New(0, global);
// On créé le script
JSScript script("script.js", context);
// On instancie la collection à partir du template
v8::Local<v8::Object> collection = collectionTemplate->NewInstance();
// On assigne un pointeur sur testCollection à la collection pour qu'on puisse y accéder plus tard
collection->SetInternalField(0, v8::External::New(&testCollection));
// On assigne la collection comme appartenant à l'objet global
context->Global()->Set(v8::String::New("collection"), collection);
// On exécute le script
script.run();
}

On remarque d'abord l'objet global et la fonction print(). Puis, le template de la collection. Or, ça s'arrête là. J'ai en effet réalisé que l'on n'était pas obligé d'utiliser le template comme objet. On pourrait voir un template comme une classe: on le créé, on définit des méthodes, puis on instancie à l'aide de NewInstance().
Ah, NewInstance(). Cette méthode m'a donnée du fil à retordre. Originellement, elle causait une erreur de segmentation, jusqu'à ce que je réalise qu'on devait obligatoirement créer un Context et un ContextScope avant. Un objet ne peut en effet exister que dans un contexte: on ne peut créer un objet "dans le vide".
Une fois l'objet instancié à l'aide de NewInstance(), on peut lui assigner divers méthodes ou attributs à l'aide de Set, ou des données internes (similaires en utilisation à des attributs privés) à l'aide de ce qu'on appelle un "internal field". Un champ interne est un attribut qui ne sera pas accessible par le script. On peut donc s'en servir pour stocker diverses informations. Ici, je m'en sert pour stocker un External, qui contiendra un pointeur sur la collection. Finalement, on déclare collection comme un objet appartenant à l'objet global. Remarquez la syntaxe différente: on assigne pas des objets comme on assigne des templates. L'effet est le même, par contre.

Mais comment utiliser ce pointeur? Voici la fonction setObject():

v8::Handle<v8::Value> setObject(const v8::Arguments &args)
{
if(args.Length() == 2)
{
// args.Holder() retourne l'objet possédant la fonction: dans ce cas précis, l'objet de collection
// On va donc chercher le External dans l'objet à l'aide de GetInternalField()
// L'appel à la méthode Cast est nécessaire, car GetInternalField() retourne un Object
// alors que nous voulons un External
v8::Local<v8::External> external = v8::Local<v8::External>::Cast(args.Holder()->GetInternalField(0));
// Puis, on prend le pointeur contenu dans le External.
// Celui-ci sera un void *, alors un cast est nécessaire
Collection * collection = static_cast<Collection *>(external->Value());
// Le reste est identique
v8::HandleScope handleScope;
v8::String::Utf8Value key(args[0]);
v8::Local<v8::Value> value(args[1]);
collection->setObject(*key, value->Int32Value());
}
return v8::Undefined();
}

getObjectById() est similaire. L'important ce situe dans les premières lignes du if: le reste est inchangé (ou presque: on utilise maintenant le pointeur sur l'objet).

Voilà!

Prochaine étape: faire en sorte que getObjectById() retourne des objets JS complexes plutôt que des entiers. Cela me permettra d'expérimenter les accesseurs.

Comme toujours, j'attends vos commentaires.

Ah, oui, j'en profite pour laisser un lien sur des benchmarks des divers moteurs JavaScript sur le marché:
JavaScript Performance Rundown.
Comme vous pouvez le voir, IE ne joue pas dans la même ligue que le reste.

2 commentaires samedi 17 janvier 2009

À coup d'essais et d'erreurs, j'y suis arrivé! Enfin, presque. J'utilise, pour faire simple, un objet global. Je sais, c'est laid, mais ce n'est que pour des fins de tests.

Bon! Il faut d'abord savoir que V8 fonctionne à coup de callbacks. Et un callback V8, pour une fonction (les objets utilisent des "accesseurs" et des "intercepteurs", qui ont une signature différente), eh bien, c'est une fonction qui a le prototype suivant (remplacez "callback" par le nom de votre choix):


v8::Handle<v8::Value> callback(const v8::Arguments &args);

Un tel prototype nous apprend deux choses:

  1. Une fonction JS peut prendre un nombre arbitraire de paramètres (args encapsulant en fait un conteneur quelconque)

  2. Une fonction JS retourne toujours une valeur. Il n'existe pas de fonction "void"


M'inspirant donc de l'exemple shell.cc, j'ai créé une fonction print qui sera appelée lorsqu'un script appellera une fonction JS nommée print:

v8::Handle<v8::Value> print(const v8::Arguments &args)
{
if(args.Length() == 1) // On ne veut qu'un seul paramètre
{
v8::HandleScope handleScope; // On définit une portée pour nos Handle
v8::String::Utf8Value str(args[0]); // On obtient la valeur du premier paramètre
std::cout << *str << std::endl; // On l'affiche ainsi qu'un retour de ligne
}
return v8::Undefined(); // Les fonctions "void" sont en fait des fonctions qui retournent undefined
}


Cette fonction permettra donc à quelqu'un d'envoyer des données dans cout à travers un script JS.

Voici le concept: on veut créer un objet JS global nommé "collection" qui supportera deux méthodes: getObjectById(string), qui retourne un entier correspondant à la clé passée en paramètre (ou null si la clé n'existe pas), et setObject(string, int) qui assigne une valeur à une clé (ou la créé si la clé n'existe pas).

J'ai donc commencé par créer une classe TestCollection (un enrobage autour de std::map, en fait):

#include <map>
#include <exception>

template<class K, class V>
class TestCollection
{
public:
typedef K key_type;
typedef V value_type;
private:
std::map<key_type, value_type> collection_;
public:
value_type getObjectById(const key_type & id) const
{
std::map<key_type, value_type>::const_iterator itt = collection_.find(id);
if(itt != collection_.end())
{
return itt->second;
}
else
{
throw std::exception("Object not found");
}
}
void setObject(key_type id, value_type val)
{
collection_[id] = val;
}
};

Définissons nous ensuite un objet global (oui, je sais, c'est laid):

TestCollection<std::string, int> collection;

Puis, créons les deux callbacks:

v8::Handle<v8::Value> setObject(const v8::Arguments &args)
{
if(args.Length() == 2)
{
v8::HandleScope handleScope;
v8::String::Utf8Value key(args[0]); // La clé, c-a-d une string
v8::Local<v8::Value> value(args[1]); // La valeur, c-a-d un int
collection.setObject(*key, value->Int32Value()); // On appelle la méthode setObject de l'objet
}
return v8::Undefined();
}

v8::Handle<v8::Value> getObjectById(const v8::Arguments &args)
{
if(args.Length() == 1)
{
v8::HandleScope handleScope;
v8::String::Utf8Value str(args[0]); // On va chercher la clé
try
{
const std::string key(*str); // On créé une std::string à partir de la clé
int i = collection.getObjectById(key); // On va chercher la valeur
return v8::Int32::New(i); // On la retourne
}
catch(std::exception e)
{
// L'objet n'a pas été trouvé, on affiche le message
std::cout << e.what();
}
}
return v8::Null();
}

Ces deux fonctions ne sont en fait que des interfaces pour les méthodes du vrai objet.


int main()
{
v8::HandleScope handleScope;
// On créé l'objet global
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
// On créé la fonction print
global->Set("print", v8::FunctionTemplate::New(print));
// On créé la collection
v8::Handle<v8::ObjectTemplate> collection = v8::ObjectTemplate::New();
// On créé les deux méthodes de la collection
collection->Set("getObjectById", v8::FunctionTemplate::New(getObjectById));
collection->Set("setObject", v8::FunctionTemplate::New(setObject));
// On assigne la collection comme appartenant à global
global->Set("collection", collection);
// On créé le contexte en spécifiant l'objet global
v8::Handle<v8::Context> context = v8::Context::New(0, global);
// On exécute le script
JSScript script("script.js", context);
script.run();

system("pause");
}

Tout d'abord, l'objet global. Lorsque l'on appelle une fonction libre ou que l'on accède à une variable globale, en JS, en fait, on appelle une méthode ou on accède à un attribut de l'objet global. Cet objet est créé comme n'importe quel objet, à l'aide d'un ObjectTemplate.
Même chose pour l'objet collection: celui-ci est en fait un ObjectTemplate auquel on ajoute deux méthodes (lesquelles sont des FunctionTemplate) à l'aide de la méthode Set. On ajoute ensuite collection à global à l'aide de Set, et on créé le contexte en informant celui-ci qu'il devra utiliser un objet global déja créé (par défaut, il créé un objet vide).
Voilà.
Ah, oui, JSScript est une petite classe que j'ai créée pour automatiser la logique de création et d'exécution d'un script. Son contenu est trivial alors je ne la dévoilerai pas ici.

Pour ceux que ça intéresse, voici le contenu de script.js:

collection.setObject("test", 2);
collection.setObject("test2", collection.getObjectById("test") + 2);
print(collection.getObjectById("test"));
print(collection.getObjectById("test2"));

Ce qui affiche 2 et 4.

D'ailleurs, je crois avoir compris pourquoi la doc est si mince sur V8: j'ai obtenu le code sur leur SVN, et le numéro de version est 0.4.7. C'est donc une beta. J'ai passé leur code dans Doxygen, mais celui-ci est si peu documenté que ça ne donne pas grand chose.

EDIT: Vous pouvez maintenant télécharger la doc Doxygen de la v0.4.7 ici: doxyV8.zip. Je sais, c'est sur RapidShare, mais bon.


Prochaine étape: se débarrasser de cet horrible objet global.

3 commentaires vendredi 16 janvier 2009

Je me lance donc dans l'aventure bloggienne. On verra ce que ça va donner. Pour plus de détails, voir le message de droite.

Mon stage me laissant beaucoup de temps libre, j'ai dépoussiéré quelques vieux projets, dont un avec lequel j'ai le goût d'expérimenter des bindings C++ - JavaScript. V8 étant sortit il n'y a pas très longtemps, je me suis dit: pourquoi pas?

Déjà, la compilation m'a donné du fil à retordre. Il faut en effet utiliser Scons, outil avec lequel j'étais très peu familier. Déjà, j'ai eu besoin de quelques recherches Google pour y arriver. Puisque Scons refusait de fonctionner sous Mac OS X, j'ai finalement lancé Parallels Desktop pour y aller sur Windows. Victoire! Ça fonctionne!

J'obtint alors un gros .lib de près de 70 Mo, que je m'empressait d'inclure à un nouveau projet VC++. Réalisant à quel point cette lib était grosse, je me suis dit qu'il devait bien y avoir une meilleure façon de procéder. Je suis donc retourné dans la doc et j'ai appris qu'on pouvait décider d'obtenir une DLL à la place en entrant un certain paramètre. Tadam! J'ai maintenant une jolie DLL (compilée en release, naturellement) et un petit .lib de 150 Ko. Bon, la compilation de mon projet me donne 10 warnings qui me disent tous que telle ou telle méthode doit avoir une interface de DLL pour les clients d'une certaine classe (warning C4251, pour être plus précis). Si quelqu'un sait de quoi il s'agit, qu'il me le dise. Parce que moi et les DLL, c'est un peu nébuleux.

Mais avant tout, il me fallait évidemment du code pour voir si la configuration de mon IDE était bonne. Alors, naturellement, je suis allé sur le site officiel de V8 (aka quelques pages sur Google Code) et j'ai ramassé le "Hello World". C'était assez simple à comprendre. Je dois dire qu'à vu de nez, comme ça, ça m'a l'air assez propre, comme code.

Résultat des courses: un joli programme qui affiche "Hello World!"

Prochaine étape: faire apparaître un objet C++ comme un objet JS. Ça devrait être assez complexe, vu que la documentation de V8 se résume à un hello world, un "embedding guide" qui ne fait que survoler les fonctionnalités de la bibliothèque, ainsi que deux exemples. Et Google donne peu de résultats sur le sujet. Je risque fort probablement de me baser sur ces exemples pour mes essais.

Donc, si vous voulez vous aussi binder C++ et JavaScript, eh bien, comme disent les Anglophones: Stay tuned!