I. Public concerné▲
Je réserve ce tutoriel à un public averti, non pas que ce langage soit compliqué, mais il reste relativement jeune et il n'est pas rare de devoir mettre les mains dans le cambouis. Cependant cela devient de plus en plus rare et pour une application simple un débutant ne devrait pas rencontrer de problème.
II. Qu'est ce que Vala ?▲
Vala est un nouveau langage de programmation. Nouveau puisqu'il existe depuis 2006 (comparé au langage C qui existe depuis le début des années 70), et aussi nouveau par sa conception puisqu'il ne s'agit pas réellement d'un langage de programmation.
La première chose que l'on remarque lorsque l'on voit du code écrit en Vala c'est la forte similitude avec le C#, aussi bien au niveau syntaxe que fonctionnalités. La deuxième chose, plus étonnante, c'est que le compilateur Vala, va générer un code dans un langage intermédiaire, comme peuvent le faire le C# ou le Java.
A première vue, ça a l'odeur, la couleur et le goût du C#. S'il n'existait pas mono l'initiative serait intéressante. Mais alors où se trouve l'intérêt d'un tel langage ? Toute l'astuce de Vala réside dans le langage intermédiaire utilisé, comme je le laisse sous-entendre dans le titre de l'article, il s'agit bien sûr du C ! Du C à l'aide de la bibliothèque GObject.
Pour en finir avec cette présentation, le mariage entre le C#, pour l'écriture, et le C, pour la compilation, procure à Vala un certain nombre n'avantages, dont voici un résumé :
- Une facilité d'écriture : il est indéniable que le C# reste plus lisible que le C,
- Un langage réellement orienté object : alors que le C/GObject accuse parfois d'une syntaxe bancale vis-à-vis de la POO (en particulier pour la définition d'une classe), Vala permet de corriger ceci,
- Une rapidité d'exécution proche du C pur : comme le code est traduit en C, on se retrouve avec des vitesses d'exécution proche de celle d'un programme écrit en C(1),
- La compatibilité binaire avec le C est conservée ce qui permet de ré-utiliser de nombreux outils et bibliothèques éprouvées,
- Ne dépend pas d'une machine binaire pour être exécuté.
Maintenant que les présentations sont faites, passons à la pratique.
III. Vala in a nuts▲
Nous nous contenterons ici d'une noix(2) pour aborder la syntaxe du langage Vala, tout simplement parce qu'elle est proche du C# et les habitués du C++ ou de Java devraient s'y retrouver très rapidement.
Voici la liste des fonctionnalités proposées par le langage :
- Les classes
- Les interfaces
- Les classes abstraites
- Les propriétés
- Les génériques
- Foreach
- Les expressions lambda
- Les signaux
- Les types nullables
- La gestion assistée de la mémoire
- Les exceptions
Vous pourrez trouver l'ensemble des mots clés sur le wiki
officiel : http://live.gnome.org/Vala/Syntax.
Histoire de vous familiariser avec ce langage et en guise de pense bête, le vais détailler rapidement chaque fonctionnalité avec un exemple de quelques lignes.
III-A. Les classes▲
Il est possible de créer des classes, qu'elles soient publiques ou privées (portée limitée au fichier). Il est aussi possible d'utiliser l'héritage (simple uniquement).
Pour créer une classe, il faut qu'elle hérite (directement ou par l'intermédiaire de sa classe mère) de GLib.Object (3) :
Pour finir, il est possible d'inclure une classe dans un espace de nom, soit en utilisant le mot clé namespace soit en préfixant le nom de la classe.
Voici quelques exemples pour illustrer ceci :
/* Une simple structure de données */
public
MaClasse
{
/* Une vrai classe inclue dans l'espace de nom Dvp */
namespace
Dvp
{
public
MaClasse :
Object
{
/* Une classe privée aussi dans l'espace de nom Dvp */
private
Dvp.
MaClasse :
Object
{
La GLib étant la bibliothèque standard de Vala, il est inutile d'écrire GLib.Object, l'utilisation du namespace est implicite.
III-A-1. Constructeur▲
La construction d'un objet est quelque peu différente par rapport à ce qui existe dans d'autres langages.
Voici la séquence de création d'un objet :
- L'utilisateur demande création d'un nouvel object grâce à l'opérateur new,
- Le système appelle le constructeur de classe pour l'ensemble de la hiérarchie en partant de la classe de base,
- Le système appelle le constructeur d'instance associé à notre classe.
Le constructeur de classe porte le nom construct et ne possède pas de portée, type de retour ou paramètre :
public
class
MaClasse
{
construct
{
Concernant le constructeur d'instance, il porte le nom de la classe sans type de retour :
public
class
MaClasse
{
public
MaClasse (
)
{
Il est possible de spécifier un ou plusieurs paramètres cependant il est uniquement possible de modifier les propriétés de l'objet dans cette partie (nous reviendrons plus tard sur les propriétés) :
public
class
MaClasse
{
public
string
label {
...
}
public
MaClasse (
string
label)
{
this
.
label =
label;
Pour finir avec ce type de constructeur il est possible de le surcharger en utilisant un suffixe :
public
class
MaClasse
{
public
string
label {
...
}
public
MaClasse.
with_label (
string
label)
{
this
.
label =
label;
}
public
MaClasse (
)
{
this
.
label =
"default"
;
}
III-A-2. Destructeur▲
Comme très souvent, le destructeur porte le nom de la classe précédé d'un tild :
public
class
MaClasse
{
~
MaClasse (
)
{
}
III-B. Interfaces▲
La déclaration d'une interface reste très classique, les champs à implémenter étant spécifiés comme abstraits.
interface
MonInterface
{
public
abstract
void
foo (
);
Il est possible de profiter de l'héritage entre interface et une classe peut implémenter une ou plusieurs interfaces de la même manière que pour l'héritage :
class
Dvp.
SuperClass :
MaClass,
MonInterface
{
public
override
void
foo (
)
{
III-C. Les classes abstraites▲
Si vous avez compris les interfaces, inutile de s'étendre sur les classes abstraites :
abstract
class
AbstractClass
{
public
abstract
void
foo (
);
public
void
bar (
)
{
III-D. Les propriétés▲
La syntaxe des propriétés est très riche, je vous renvoie au manuel de référence pour un aperçu complet des possibilités.
Voici tout de même un exemple simple de propriété :
class
MaClass :
Object
{
private
string
_prop;
public
string
prop
{
get
{
return
this
.
_prop;
}
set
{
this
.
_prop =
value
:
}
}
public
static
int
main (
string
[]
args)
{
var
c =
new
MaClass (
);
c.
prop =
"test"
;
print (
"%s
\n
"
,
c.
prop);
return
0
;
}
}
Pour pouvoir bénéficier des propriétés, la classe doit hériter de GLib.Object.
Pour simplifier l'écriture de propriétés "standard" (c'est-à-dire comme ci-dessus, qui ne font que manipuler une variable privée), Vala propose un mécanisme de propriétés automatiques qui cache cette partie du code :
class
MaClass :
Object
{
public
string
prop
{
get
;
set
;
}
}
en plus de la visibilité de la propriété, il est possible d'affiner la visibilité des assesseurs :
class
MaClass :
Object
{
public
string
prop
{
get
;
private
set
;
}
}
Ce qui rendra la propriété lisible par tous, mais modifiable uniquement pas les instances de la classe.
Pour finir, si vous souhaitez qu'une propriété ne soit modifiée uniquement lors de la construction de l'objet, vous pouvez remplacer l'assesseur set par construct :
class
MaClass :
Object
{
public
string
prop
{
get
;
construct;
}
public
MaClass (
string
prop)
{
this
.
prop =
prop;
}
}
De plus ceci permet de mettre en place la valeur de la propriété avant l'appel au constructeur de classe construct (par défaut, la valeur n'est modifiée qu'ensuite).
Ceci n'étant pas très clair, voici un exemple qui devrait vous convaincre de l'utilité de ce mot clés :
class
MaClass :
Object
{
public
string
prop
{
get
;
construct;
}
public
string
prop2
{
get
;
set
;
}
public
MaClass (
string
prop,
string
prop2)
{
this
.
prop =
prop;
this
.
prop2 =
prop2;
}
construct
{
print (
"MaClass.prop = %s
\n
"
,
this
.
prop);
print (
"MaClass.prop2 = %s
\n
"
,
this
.
prop2);
}
}
public
class
Main
{
public
static
int
main (
string
[]
args)
{
new
MaClass (
"1"
,
"2"
);
return
0
;
}
}
MaClass.prop = 1
MaClass.prop2 = (null)
III-E. Génériques▲
Vala supporte les génériques (ou template pour les adeptes du C++), de manière très classique :
class
Wrapper<
T>
:
Object {
...
}
new
Wrapper<
Object>
(
);
Les différentes bibliothèques (en particulier la glib), ont été enrichies avec les génériques qui n'existent pas en C.
Leur utilisation n'est pas obligatoire (par exemple pour les listes) mais cela améliore la lisibilité du code est permet d'utiliser certaines fonctionnalités du langage, comme le foreach.
III-F. Foreach▲
Pour illustrer mes propos précédents, voici un exemple avec les listes :
List<
string
>
files;
/* ... */
foreach
(
string
file in
files)
{
print (
"%s
\n
"
,
file);
}
Le foreach est utilisable avec les tableaux, les listes (simple ou doublement chaînées), les tables de hachages et toutes les classes qui implémente l'interface Gee.Iterable.
III-G. Délégué et expressions lambda▲
Les délégués (ou delegates, en anglais), sont utilisés à la place des pointeurs de fonctions et on les rencontre le plus fréquemment pour la gestion des événements (signaux). Pour associer une fonction callbask à un signal, il suffit de l'ajouter :
var
win =
new
Gtk.
Window (
);
win.
destroy +=
Gtk.
main_quit;
La même syntaxe est utilisée pour déconnecter la fonction :
win.
destroy -=
Gtk.
main_quit;
Et plutôt que de créer une fonction pour les signaux demandant peu de traitement, il est préférable d'utiliser les expressions lambda qui permettent d'inclure le corps de la fonction directement :
win.
destroy +=
(
s) =>
{
print (
"Quit
\n
"
);
Gtk.
main_quit (
);
};
III-H. Les signaux▲
Nous venons de voir comment intercepter un signal, nous allons donc nous attarder sur la création d'un signal pour notre objet.
C'est extrêmement simple, il suffit de déclarer que votre objet envoi un signal à l'aide du code suivant :
public
signal void
sig_1 (
int
a);
Ensuite, votre objet émet ce signal tout naturellement :
this
.
sig_1 (
10
);
Et pour finir, si vous souhaitez intercepter le signal, il suffit d'utiliser les delegates ou les fonctions lambda :
o.
sig_1 +=
(
s,
a) =>
{
print (
"sig_1 : %d
\n
"
,
a);
};
Le premier paramètre correspond à l'objet qui à lancé le signal (s pour sender), il est passé explicitement.
III-I. Les exceptions▲
Pour ce qui est de la gestion des exceptions, nous retrouvons une syntaxe traditionnelle :
class
MaClass :
Object
{
/* Fonction pouvant lancer une execption de type Error */
public
void
foo (
) throws Error {
...
}
public
static
int
main (
string
[]
args)
{
try
{
foo (
);
}
catch
(
Error e)
{
warning (
e.
message);
}
finally
{
/* ... */
}
return
0
;
}
}
Par contre pour la création et le lancement d'une exception, nous devons passer par les spécificités de la GLib(4).
Il faut commencer par créer un nouveau domaine d'erreur avec les différents types d'erreur possible :
public
errordomain MyError
{
ERROR_1,
ERROR_2
}
Et ensuite, nous pouvons créer et lancer notre exception :
throw
new
MyError.
ERROR_1 (
"Error message"
);
IV. Utilisez vos anciennes bibliothèques▲
Comme précisé en introduction, il est possible de réutiliser vos bibliothèques écrites en C pour vos nouveaux développements en Vala. Pour cela, il suffit de créer un fichier d'en-tête vapi. Ce fichier contient simplement la déclaration des classes. Il est bien sûr nécessaire que votre interface corresponde à une approche objet (même s'il est possible de corriger certaines imperfections grâce à l'annotation CCode).
S'il est possible d'écrire ces fichiers à la main pour de petites bibliothèques, cela n'est pas envisageable pour les interfaces plus conséquentes (par exemple pour GTK+). Il existe donc le programme gobject-introspection qui pourra vous aider dans cette tâche.
V. FAQ▲
Voici une mini-FAQ qui vous permettra de répondre à quelques questions qui ne se trouvent pas forcement sur le site officiel.
V-A. Commet fonctionnent les callback ?▲
Comme précisé ci-dessus pour intercepter un signal, il faut utiliser les delegates. La connexion est extrêmement simple, cependant le prototype de la fonction callback possède quelques subtilités non précisées dans la documentation. Premièrement, il faut utiliser l'annotation Callback, ceci permet au compilateur de créer un swrapper afin d'assouplir la liste des arguments. Deuxièmement il est possible d'utiliser indifféremment une méthode de classe ou d'instance pour gérer le signal. Voici quelques exemples de callback :
public
class
TestCb :
Object
{
[Callback]
private
void
cb_1 (
)
{
}
[Callback]
private
void
cb_2 (
Gtk.
Button sender)
{
}
[Callback]
private
static
void
cb_3 (
)
{
}
public
static
int
main (
string
[]
args)
{
/* ... */
Gtk.
Button btn =
new
Gtk.
Button.
with_label (
"Click!"
);
// Méthode d'instance sans paramètre
btn.
clicked +=
this
.
cb_1;
// Méthode d'instance avec un paramètre
btn.
clicked +=
this
.
cb_2;
// Méthode de classe
btn.
clicked +=
TestCb.
cb_3;
/* ... */
}
}
Le compilateur se chargera de passer la référence vers l'instance si nécessaire et de compléter la liste des arguments.
V-B. ref, out, weak et transfert de propriété, de quoi s'agit-il ?▲
Voici des mots clés peu connus des développeurs C.
Le mot clé ref est utilisé pour spécifier qu'un paramètre est passé par référence, donc peut être modifié par la fonction.
Le mot clé out précise que le paramètre est utilisé pour stocker une valeur de retour.
Le mot clé weak désigne une référence en la définissant comme faible, c'est à dire que si toutes les références sur un objet sont faibles, l'objet sera détruit.
Le transfert de propriété, indiqué par le caractère #, permet de transférer la propriété d'une référence d'une variable à une autre. Par exemple :
Foo foo =
#bar;
Normalement, la référence de l'objet bar est copiée dans foo et le nombre de références augmentée d'un. Ici, foo va contenir la référence de bar puis bar sera mis à null sans que le compteur de référence soit incrémenté.
Ceci est particulièrement utilisé lorsqu'une fonction retourne une chaîne de caractère créée localement, afin que celle-ci continue d'exister après le retour de la fonction.
Ceci se retrouve en C# dans les propriétés avec le mot clés new.
V-C. L'avenir de vala▲
Vala est encore jeune et cela se ressent lorsqu'on développe une application importante : il n'est pas rare de devoir modifier les fichiers vapi à la main pour corriger un problème généralement dû à une ambiguïté dans le nommage des fonctions en C.
Au fils de mes développements j'ai constitué un patch pour le fichier gtk+-2.0.vapi que vous pouvez trouver ici. Il contient les bugs qu'il est difficile de corriger.
V-D. Comment utiliser le fichier config.h ?▲
Les développeurs du monde Linux, en particulier ceux familiers des autotools connaissent le fameux fichier config.h. Pour pouvoir l'utiliser, il suffit de créer un fichier d'interface vapi ressemblant à l'exemple suivant :
[CCode (cprefix =
""
, lower_case_cprefix =
""
, cheader_filename =
"config.h"
)]
namespace
Config
{
public
const
string
PACKAGE;
public
const
string
PACKAGE_BUGREPORT;
public
const
string
PACKAGE_NAME;
public
const
string
PACKAGE_STRING;
}
V-E. Quel système de construction utiliser ?▲
Pour les adeptes des autotools, il est parfaitement possible de les utiliser avec vala. Le programme vala-gen-project vous génère tous les fichiers nécessaires pour débuter un projet avec les autotools.
Ou, si comme moi, vous trouvez les autotools repoussant, il existe waf qui prend aussi en compte le langage Vala.
VI. Références▲
- The Vala Programming Language, sur GNOME Live!
- Hackers' Guide to Vala
VII. Remerciements▲
Merci à Adrien Artero pour la relecture attentive de cet article.