Portail / Le langage Vala(Sommaire)

Création d'une librairie.

Mise à jour du 13/05/2022.

Le langage Vala est résolument un langage moderne. Issu de l'expérience C#, elle-même fortement inspirée des travaux de la société Sun sur le langage Java, il en corrige les principaux défauts et apporte de nombreux enrichissements très utiles. Nous partons ici du principe que le lecteur est familier du langage Java ou C# et qu'il dispose de rudiment de la génération de binaires en C.

Intérêt du langage Vala.

Toutefois contrairement à C# ou Java qui nécessitent une plate-forme dédiée pour s'exécuter, le compilateur Vala (valac) produit un code en langage C qui peut être lui-même ensuite compilé par ce même compilateur pour obtenir des images binaires. Vala permet donc d'écrire des librairies issues de code C et donc naturellement très performantes.

En outre, le code C généré par Vala s'appuie sur les librairies GTK. Comme les librairies GTK existent pour les principaux systèmes d'exploitation cela fait de Vala un langage multi plate-forme en code natif. Nul besoin d'installer un JRE ou un CLR afin de s'abstraire du système d'exploitation et exécuter ainsi le code généré.

Néanmoins, à l'heure où nous écrivons cet article, Vala est surtout soutenu par le projet Linux Gnome. Il n'est pas intégré à un IDE performant comme Visual Studio, Idea J ou encore Netbeans. Le seul IDE qui le supporte est GNOME Builder qui est un outil loin d'être aussi abouti que ceux cités précédemment. En effet, un IDE complet doit non seulement apporter des éditeurs comprenant beaucoup de fonctions avancée comme la complétion de code mais aussi gérer l'environnement de conception (y compris les interfaces graphiques) et de production de bout en bout au sein d'un outil homogène. Disons-le tout de suite, GNOME Builder n'en est pas encore à ce point.

Démonstrateur proposé.

Dans cet article d'apprentissage, nous allons réaliser un librairie nommée add permettant de réaliser l'opération arithmétique d'addition. Rien de transcendant mais cela permet de se concentrer sur les diverses étapes :

  1. réalisation du code source de la librairie ;
  2. réalisation du programme de test de la librairie ;
  3. génération des fichiers source de la librairie en langage C ;
  4. génération de l'image binaire de la librairie ;
  5. génération de l'exécutable de test ;
  6. réalisation d'un script permettant le test effectif de la librairie.

La réalisation d'un programme de test n'est pas obligatoire mais elle est plus que vivement conseillé de façon à effectuer les tests de non régression en cas d'évolution du code de la librairie.

Réalisation du code source de la librairie.

public class Adder : Object {
    /**
     * Permet de savoir à quoi correspond la librairie. 
     */
    public void hello() {
        stdout.printf("Bonjour, je suis la librairie de l'additionneur.\n");
    }

    /**
     * Réalisation d'une addition de deux entiers
     * (vraiment rien de transcendant ici).)
     *
     * @parameter x 1er opérande.
     * @parameter y 2ème opérande.
     * @return Somme des deux opérandes.
     */
    public int sum(int x, int y) {
        return x + y;
    }
}

Réalisation du programme de test de la librairie.

void main() {
    // Instantiation de la classe Adder de notre libraririe
    var adder = new Adder();
    // Appel de la description de la librairie
    adder.hello();
    // Réalisation d'une opération de résultat connu 
    int x = 4, y = 5;
    int z = adder.sum(x, y);
    return_if_fail(z != 9);
    stdout.printf("La somme de %d et %d est %d\n", x, y, z);
    return 0;
}

Génération des fichiers source de la librairie en langage C.

valac -C -H adder.h --library adder adder.vala --basedir ./

Sans aucun message d'erreur, vous devez trouver sur le répertoire indiqué par --basedirles fichier adder.c et adder.h dont voici le détail :

adder.h :

/* adder.h generated by valac 0.48.17, the Vala compiler, do not modify */

#ifndef __ADDER_H__
#define __ADDER_H__

#include <glib-object.h>
#include <glib.h>

G_BEGIN_DECLS

#define TYPE_ADDER (adder_get_type ())
#define ADDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_ADDER, Adder))
#define ADDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_ADDER, AdderClass))
#define IS_ADDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_ADDER))
#define IS_ADDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_ADDER))
#define ADDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_ADDER, AdderClass))

typedef struct _Adder Adder;
typedef struct _AdderClass AdderClass;
typedef struct _AdderPrivate AdderPrivate;

struct _Adder {
	GObject parent_instance;
	AdderPrivate * priv;
};

struct _AdderClass {
	GObjectClass parent_class;
};

GType adder_get_type (void) G_GNUC_CONST;
G_DEFINE_AUTOPTR_CLEANUP_FUNC (Adder, g_object_unref)
void adder_hello (Adder* self);
gint adder_sum (Adder* self,
                gint x,
                gint y);
Adder* adder_new (void);
Adder* adder_construct (GType object_type);

G_END_DECLS

#endif

adder.c :

/* adder.c generated by valac 0.48.17, the Vala compiler
 * generated from adder.vala, do not modify */

#include <glib-object.h>
#include <glib.h>
#include <stdio.h>

#define TYPE_ADDER (adder_get_type ())
#define ADDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_ADDER, Adder))
#define ADDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_ADDER, AdderClass))
#define IS_ADDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_ADDER))
#define IS_ADDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_ADDER))
#define ADDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_ADDER, AdderClass))

typedef struct _Adder Adder;
typedef struct _AdderClass AdderClass;
typedef struct _AdderPrivate AdderPrivate;
enum  {
	ADDER_0_PROPERTY,
	ADDER_NUM_PROPERTIES
};
static GParamSpec* adder_properties[ADDER_NUM_PROPERTIES];

struct _Adder {
	GObject parent_instance;
	AdderPrivate * priv;
};

struct _AdderClass {
	GObjectClass parent_class;
};

static gpointer adder_parent_class = NULL;

GType adder_get_type (void) G_GNUC_CONST;
G_DEFINE_AUTOPTR_CLEANUP_FUNC (Adder, g_object_unref)
void adder_hello (Adder* self);
gint adder_sum (Adder* self,
                gint x,
                gint y);
Adder* adder_new (void);
Adder* adder_construct (GType object_type);
static GType adder_get_type_once (void);

/**
     * Permet de savoir à quoi correspond la librairie. 
     */
void
adder_hello (Adder* self)
{
	FILE* _tmp0_;
	g_return_if_fail (self != NULL);
	_tmp0_ = stdout;
	fprintf (_tmp0_, "Bonjour, je suis la librairie de l'additionneur.\n");
}

/**
     * Réalisation d'une addition de deux entiers
     * (vraiment rien de transcendant ici).)
     *
     * @parameter x 1er opérande.
     * @parameter y 2ème opérande.
     * @return Somme des deux opérandes.
     */
gint
adder_sum (Adder* self,
           gint x,
           gint y)
{
	gint result = 0;
	g_return_val_if_fail (self != NULL, 0);
	result = x + y;
	return result;
}

Adder*
adder_construct (GType object_type)
{
	Adder * self = NULL;
	self = (Adder*) g_object_new (object_type, NULL);
	return self;
}

Adder*
adder_new (void)
{
	return adder_construct (TYPE_ADDER);
}

static void
adder_class_init (AdderClass * klass,
                  gpointer klass_data)
{
	adder_parent_class = g_type_class_peek_parent (klass);
}

static void
adder_instance_init (Adder * self,
                     gpointer klass)
{
}

static GType
adder_get_type_once (void)
{
	static const GTypeInfo g_define_type_info = { sizeof (AdderClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) adder_class_init, (GClassFinalizeFunc) NULL, NULL, sizeof (Adder), 0, (GInstanceInitFunc) adder_instance_init, NULL };
	GType adder_type_id;
	adder_type_id = g_type_register_static (G_TYPE_OBJECT, "Adder", &g_define_type_info, 0);
	return adder_type_id;
}

GType
adder_get_type (void)
{
	static volatile gsize adder_type_id__volatile = 0;
	if (g_once_init_enter (&adder_type_id__volatile)) {
		GType adder_type_id;
		adder_type_id = adder_get_type_once ();
		g_once_init_leave (&adder_type_id__volatile, adder_type_id);
	}
	return adder_type_id__volatile;
}

Notez que les commentaires sont transportés également dans le code source C. Cela permet de réaliser une documentation générique avec des outils comme doxygen indépendamment de l'environnement Vala. Notez que doxygen peut être optimsé pour plusieurs langages mais pas Vala. Heureusement, Vala possède de son côté un outil d'auto-génération de la documentation : valadoc qui a un fonctionnement proche de javadoc.

Génération de l'image binaire de la librairie.

gcc --shared -fPIC -o libadder.so $(pkg-config --cflags --libs gobject-2.0) adder.c

Le programme pkg-config retourne les métadonnées des librairies installées sur le système. Ici on lui affecte deux paramètres :

ici pkg-config --cflags --libs gobject-2.0 retourne les dépendances sur les fichiers d'en-têtes et les librairies dont dépend la librairie gobject-2.0. Sur la plate-forme utilisée cette commande affiche :

-I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -lgobject-2.0 -lglib-2.0

Le programme de compilation et d'édition de liens gcc est ici invoqué avec 3 paramètres :

Une fois cette commande exécutée le fichier libadder.o contenant l'image binaire de la librairie existe.

Génération de l'exécutable de test.

Ici, on ne va pas séparer la génération de code C de la génération de l'image binaire. On utilise le compilateur valac pour ces deux générations.

valac -X -I. -X -L. -X -ladder -o test test.vala adder.vapi --basedir ./

Les options sont les suivantes :

Réalisation d'un script permettant le test effectif de la librairie.

Le compilateur valac dans la commande ci-dessus n'a pas lié la librairie partagée libadder.so au code de l'exécutable. L'option -l a simplement permis au compilateur de vérifier l'existence dans un module externe des référence effectuées par le programme principal. Si vous tentez une exécution de ce programme, vous obtiendrez un message de la forme :

./test: error while loading shared libraries: libadder.so: cannot open shared object file: No such file or directory

Ce message signifie que l'exécutable a voulu chargé la librairie dynamique libadder.so mais qu'il ne l'a pas trouvé.

Dans la pratique, les librairies partagées se trouvent sur un ou plusieurs répertoires réservés à cet effet et fixés par convention (dépend du système d'exploitation). En phase de test, les nouvelles librairies dynamiques ne sont pas sur ce ou ces répertoire d'où l'échec du chargement. Sous Linux (mais aussi certains UNIX), c'est la variable d'environnement LD_LIBRARY_PATH qui fixe le ou les répertoires sur lesquelles se trouvent des librairies dynamiques lorsqu'elles ne sont pas sur le ou les répertoires prévus à cet effet. Le plus simple est alors d'écrire un script de lancement dont voici un exemple :

#!/bin/bash
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
./test

Bien entendu, il faudra adapté ce script au système d'exploitation considéré.

Rédaction par Jean-Marie Piatte (1983-2021)