La programmation orientée objet en C avec GOject
Première partie : les classes
Date de publication : 10 Janvier 2008
Par
Nicolas JOSEPH (home) (Blog)
(Aucun commentaire)
Dans ce premier tutorial consacré à la bibliothèque GObject, nous allons
aborder les notions essentielles qui vous permettrons de créer vos premier
objets en C.
Public concerné
I. Introduction
II. Le code minimal
III. Champs public
III-A. Les méthodes
III-B. Les variables
IV. Champs privés
IV-A. Les méthodes
IV-B. Les variables
V. Constructeur
VI. Destructeur
VII. Membres de classes
VIII. Les méthodes virtuelles
IX. Les propriétés
X. GObject Builder
XI. Exemple complet
XII. Conclusion
XIII. Remerciements
Public concerné
I. Introduction
A l'origine, le langage C est dépourvu de fonctionnalité permettant de
faire de la
programmation orientée objet (POO),
il est au mieux possible de créer des
types abstrait de données (TAD).
Cependant lorsque l'on connaît parfaitement le langage C et moyennant
quelques lignes de code supplémentaires, il est possible d'obtenir
les mêmes fonctionnalités qu'en C++, par exemple.
II. Le code minimal
Le C n'offrant pas les mêmes facilités que le C++, ou autres langages
orientés objet, même avec l'aide de la bibliothèque GObject, il va
falloir écrire plus de code.
Avant toute chose, il faut préciser qu'une classe, avec GObject, est
en fait divisée en deux parties. La première portant le nom de la classe
à proprement dit (dans notre exemple Vehicule) et est relative
aux instances de notre classe (autrement dit les objets). Elle
contiendra donc les variables publiques et privées.
La seconde partie porte le nom de la classe avec le suffixe Class
(dans notre exemple VehiculeClass) qui contiendra l'ensemble des
informations relatives à notre classe (typiquement les méthodes et
variables de classes).
Même si le rôle de ces deux parties n'est pas clair, gardez en tête
cette séparation, la compréhension de la suite n'en sera que plus
rapide.
Voici donc le code minimal afin de créer une nouvelle classe :
| vehicule.h |
#ifndef H_NJ_VEHICULE_261120071937
#define H_NJ_VEHICULE_261120071937
#include <glib-object.h>
#define TYPE_VEHICULE (vehicule_get_type ())
#define VEHICULE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_VEHICULE, Vehicule))
#define VEHICULE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_VEHICULE, VehiculeClass))
#define IS_VEHICULE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_VEHICULE))
#define IS_VEHICULE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_VEHICULE))
#define VEHICULE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_VEHICULE, VehiculeClass))
typedef struct _Vehicule Vehicule;
typedef struct _VehiculeClass VehiculeClass;
struct _Vehicule
{
GObject parent;
};
struct _VehiculeClass
{
GObjectClass parent;
};
GType vehicule_get_type (void);
#endif
|
| vehicule.c |
#include "vehicule.h"
GType vehicule_get_type (void)
{
static GType type = 0;
if (type == 0)
{
static const GTypeInfo info = {
sizeof (VehiculeClass),
NULL,
NULL,
NULL,
NULL,
NULL,
sizeof (Vehicule),
0,
NULL,
NULL
};
type = g_type_register_static (G_TYPE_OBJECT, "Vehicule", &info, 0);
}
return type;
}
|
Dans le fichier d'en-tête nous déclarons un ensemble de macro qui
doivent vous être familière si vous avez déjà utilisez GTK+ :
-
TYPE_VEHICULE : il s'agit de notre nouveau type. Cette
macro sera utilisée afin d'en créer une nouvelle instance,
-
VEHICULE : cette macro est utilisée afin de faire un cast
d'un objet vers notre type,
-
VEHICULE_CLASS : c'est la même chose mais pour la classe,
-
IS_VEHICULE : vérifie que l'objet est du type souhaité,
-
IS_VEHICULE_CLASS : idem pour la classe,
-
VEHICULE_GET_CLASS : permet de récupérer la structure
représentant la classe à partir d'un objet.
Ensuite nous déclarons une première structure qui représente un objet de
notre classe. C'est un pointeur sur cette structure que nous obtiendrons
lorsque l'on créera un nouvel objet. Le premier membre de cette
structure doit impérativement être la classe mère.
La seconde partie, qui est commune à toutes les instances de notre
classe, est déclarée de manière similaire, en commençant par la classe
parent.
Pour finir, nous déclarons la seule fonction, qui est en faite une
fonction privée qui ne doit pas être utilisée directement (il faut
passer par la macro TYPE_VEHICULE) qui permet de connaître le type que
GObject à associé à notre classe. Lors du premier appel à cette
fonction, nous allons enregistrer notre nouveau type. Pour ce faire, il
faut commencer par renseigner une structure de type
GTypeInfo :
typedef struct {
guint16 class_size;
GBaseInitFunc base_init;
GBaseFinalizeFunc base_finalize;
GClassInitFunc class_init;
GClassFinalizeFunc class_finalize;
gconstpointer class_data;
guint16 instance_size;
guint16 n_preallocs;
GInstanceInitFunc instance_init;
const GTypeValueTable *value_table;
} GTypeInfo;
|
Pour commencer, nous avons juste besoin de préciser les tailles des
structures de classe et d'instance.
Nous verrons en fonction de nos besoins l'utilité des autres éléments.
Une fois notre classe créée, il ne reste plus qu'à la tester :
#include "vehicule.h"
int main (void)
{
Vehicule *v = NULL;
g_type_init ();
v = g_object_new (TYPE_VEHICULE, NULL);
g_object_unref (v);
return 0;
}
|
Nous ne pouvons pas faire grand chose avec notre classe, mais vous
pouvez déjà remarquer que nous pouvons utiliser les fonctions
spécifiques aux classes de base (GObject) sans problème.
Maintenant que nous avons vu en détails la création d'une classe, il
existe une macro afin de nous éviter le fastidieux travaille de
copier/coller :
G_DEFINE_TYPE (Vehicule, vehicule, G_TYPE_OBJECT)
|
 |
Il ne faut pas de point-virgule puisque la macro est dépliée sous la forme :
|
static void vehicule_init (Vehicule *self);
static void vehicule_class_init (VehiculeClass *klass);
static gpointer vehicule_parent_class = NULL;
static void vehicule_class_intern_init (gpointer klass)
{
vehicule_parent_class = g_type_class_peek_parent (klass);
vehicule_class_init ((VehiculeClass*) klass);
}
GType vehicule_get_type (void)
{
static GType g_define_type_id = 0;
if (G_UNLIKELY (g_define_type_id == 0))
{
g_define_type_id =
g_type_register_static_simple (TYPE_PARENT,
g_intern_static_string ("Vehicule"),
sizeof (VehiculeClass),
(GClassInitFunc)vehicule_class_intern_init,
sizeof (Vehicule),
(GInstanceInitFunc)vehicule_init,
(GTypeFlags) flags);
}
return g_define_type_id;
}
|
Il nous reste donc plus qu'à définir les deux fonctions initialisations
(éventuellement vide), avec un prototype légèrement différent, puisque
grâce à la macro, GObject connaît le vrai type de notre classe :
static void vehicule_class_init (VehiculeClass *klass)
{
g_return_if_fail (klass != NULL);
}
static void vehicule_init (Vehicule *self)
{
g_return_if_fail (self != NULL);
}
|
 |
Notez au passage que nous obtenons aussi la classe parent de notre
classe, ceci nous sera utile par la suite pour le chaînage des fonctions.
|
III. Champs public
III-A. Les méthodes
Les méthodes publiques sont de simples fonctions externes. Par
convention, ces fonctions ont un nom qui commence par celui de la
classe et leur premier argument est l'objet auquel doit s'appliquer la
fonction. C'est le this ou self passé implicitement
dans langages orientés objets.
III-B. Les variables
Pour ajouter une variable publique à notre classe, il suffit de
l'ajouter à la structure qui la définit :
struct _Vehicule
{
GObject parent;
guint32 flags;
};
|
 |
La structure parent doit toujours être le premier membre de la
structure. Nous ajoutons donc nos variables en dessous.
|
Pour accéder à notre champ, il suffit de procéder comme pour une
structure classique :
v->flags = 0;
g_print ("%d\n", v->flags);
|
IV. Champs privés
IV-A. Les méthodes
Nous pouvons aussi définir des méthodes privées à notre classe,
simplement en les définissant dans le fichier source en tant que
fonctions statiques :
static void vehicule_reset_flags (Vehicule *self)
{
g_return_if_fail (self != NULL);
g_return_if_fail (IS_VEHICULE (self));
self->flags = 0;
}
|
IV-B. Les variables
Pour définir des variables privées, il ne suffit pas de déclarer une
variables static, puisque dans ce cas elle serait partagée par
l'ensemble des instances d'une classe. GObject propose un mécanisme
qui permet à chaque objet de disposer de ses propres variables
privées.
Pour cela, il faut créer une structure qui regroupera l'ensemble des
variables privées :
typedef struct _VehiculePrivate VehiculePrivate;
struct _VehiculePrivate
{
gint vitesse;
};
|
Ensuite, nous demandons à GObject, d'enregistrer cette structure lors
de l'initialisation de notre classe :
static void vehicule_class_init (gpointer g_class, gpointer class_data)
{
g_type_class_add_private (klass, sizeof (VehiculePrivate));
}
|
Et enfin pour y accéder à partir d'une instance de notre classe, nous
créons une macro :
#define VEHICULE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
TYPE_VEHICULE, \
VehiculePrivate))
|
Et pour y accéder :
VehiculePrivate *priv = NULL;
priv = VEHICULE_GET_PRIVATE (self);
g_print ("%d\n", priv->vitesse);
|
V. Constructeur
Le constructeur et le destructeur sont automatiquement appelés par
GObject lorsque cela est nécessaire. Comme nous ne devons pas les
appeler directement, il faut les enregistrer lors de l'initialisation de
notre classe :
static void vehicule_class_init (gpointer g_class, gpointer class_data)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (g_class);
gobject_class->constructor = vehicule_constructor;
parent_class = g_type_class_peek_parent (g_class);
(void)class_data;
}
|
Et dans le constructeur, nous devons commencer par appeler le
construteur de la classe parent :
static GObject *vehicule_constructor (GType type, guint n_construct_properties,
GObjectConstructParam *construct_properties)
{
GObject *obj = NULL;
obj = parent_class->constructor (type,
n_construct_properties,
construct_properties);
return obj;
}
|
VI. Destructeur
Pour la destruction d'un objet, le principe est le même, mis à part que
l'on dispose de deux fonctions qui peuvent être surchargées :
-
dispose : sert à libérer la mémoire allouée au cours de la vie de
notre objet. Par contre l'objet lui même n'est pas détruit,
-
finalize : il s'agit du véritable destructeur de notre objet, qui
commence par un appel à la méthode dispose.
 |
Contrairement au constructeur, le chaînage (l'appel au destructeur de la
classe parent), doit se faire en dernier.
|
VII. Membres de classes
Les membres de classes (méthodes et variables) sont partagés par
l'ensemble des objets de la classes. En C++, ils sont déclarés à l'aide
du mot-clé static. Pour obtenir le même résultat, nous avons
juste à ajouter un membre dans la structure qui définie notre
classe :
struct _VehiculeClass
{
GObjectClass parent;
gint static_integer;
}
|
Vous remarquerez que nous faisons appel au bon sens des
développeurs pour ce qui est de la visibilité des variables de classes.
Pour accéder à notre variable, nous devons récupérer notre classe. Si
nous en possédons une instance, rien de plus simple, il suffit
d'utiliser la macro que nous avons écrite au dessus :
VehiculeClass *klass = VEHICULE_GET_CLASS (self);
|
Mais il est aussi possible d'obtenir notre classe en l'absence
d'instance de celle-ci (c'est la définition même des membres statiques).
Pour ce faire, il suffit d'utiliser la fonction
g_type_class_peek :
VehiculeClass *klass = g_type_class_peek (TYPE_VEHICULE);
|
VIII. Les méthodes virtuelles
Les méthodes virtuelles est un concept essentiel à la POO et si vous
avez été attentif nous avons déjà utilisé ce concept dans nos exemples
précédents. Où ? Tout simplement pour la construction et la destruction
de nos objets.
Pour créer une méthode virtuelle, il faut commencer par déclarer un
pointeur de fonction dans notre classe :
struct _VehiculeClass
{
GObjectClass parent;
void (* avancer) (Vehicule *self, gint distance);
};
|
Ce pointeur nous permettra d'appeler la bonne fonction pour faire
avancer notre véhicule quelque soit la classe de base de
l'objet.
Si vous souhaitez créer une méthode publique, il suffit de créer un
assesseur public :
void vehicule_avancer (Vehicule *self, gint distance)
{
VEHICULE_CLASS (self)->avancer (self, distance);
}
|
Sinon, comme pour les méthodes, utilisez le mot clés static pour
rendre cet assesseur, et donc votre méthode virtuelle, privé.
Il ne faut pas oublier d'initialiser notre pointeur dans la fonction
d'initialisation de notre classe :
static void vehicule_rouler (Vehicule *self, gint distance)
{
}
static void vehicule_class_init (VehiculeClass *klass)
{
g_return_if_fail (klass != NULL);
klass->avancer = vehicule_rouler;
}
|
L'avantage c'est que pour notre nouvelle classe robot qui est un
véhicule qui n'avance pas en roulant mais en marchant, nous avons
juste à écrire :
static void robot_marcher (Vehicule *self, gint distance)
{
}
static void robot_class_init (RobotClass *klass)
{
g_return_if_fail (klass != NULL);
VEHICULE_CLASS (klass)->avancer = robot_marcher;
}
|
Et dans les deux cas, l'appel à la fonction vehicule_avancer
fera avancer notre véhicule avec la bonne méthode !
 |
Il est possible d'initialiser une méthode virtuelle à NULL, dans ce cas
c'est aux classes filles de les définir. On parle alors de
méthodes virtuelles pures.
|
IX. Les propriétés
En POO, les propriétés sont semblables aux variables dans leur
utilisation. Cependant dans le cas des propriétés le fait d'accéder
ou de modifier sa valeur fait appel à une fonction ce qui permet
d'avoir une abstraction supplémentaire. Par exemple nous pourrions avoir
une propriété dont la valeur est le résultat d'une requête (et donc
sa modification entraînera une mise à jour de la base de données).
En plus d'un niveau d'abstraction supplémentaire, il est possible
d'avoir des propriétés accessible uniquement en lecture ou en écriture.
Pour commencer chaque propriété doit avoir un identifiant unique au
sein de la classe, le plus simple étant de créer une énumération :
enum
{
PROP_0,
PROP_WINDOW_OPEN
};
|
La première valeur d'une énumération étant zéro, nous utilisons un nom
bidon pour que notre première propriété ait l'identifiant 1. Comme son
nom l'indique notre unique propriété permet de savoir si la fenêtre
de notre véhicule est ouverte. Logiquement, modifier cette propriété
permettra d'ouvrir ou non la fenêtre.
Nous avons donc une propriété de type boolean que nous
définissons dans la fonction d'initialisation de notre classe :
static void vehicule_class_init (gpointer g_class, gpointer class_data)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (g_class);
GParamSpec *pspec = NULL;
pspec = g_param_spec_boolean ("window-open",
"Window open prop",
"Set window opening",
FALSE,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
|
Nous définissons donc le nom de notre propriété, sa valeur par défaut
ainsi que différentes options (voir
GParamFlags
pour plus d'informations).
Ensuite nous installons notre propriété :
g_object_class_install_property (gobject_class,
PROP_WINDOW_OPEN,
pspec);
|
Pour en finir avec notre initialisation, nous devons spécifier deux
fonctions qui serviront d'assesseurs :
gobject_class->set_property = vehicule_set_property;
gobject_class->get_property = vehicule_get_property;
|
Ces deux fonctions vont recevoir toutes les demandes d'accès ou de
modifications de propriétés. Afin de traiter correctement chacune
d'entre elles, nous effectuons un switch/case sur l'identifiant
de la propriété :
static void vehicule_set_property (GObject *object, guint property_id,
const GValue *value, GParamSpec *pspec)
{
Vehicule *self = VEHICULE (object);
VehiculePrivate *priv = NULL;
priv = VEHICULE_GET_PROVATE (self);
switch (property_id)
{
case PROP_WINDOW_OPEN:
priv->open_window = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void vehicule_get_property (GObject *object, guint property_id,
GValue *value, GParamSpec *pspec)
{
Vehicule *self = VEHICULE (object);
VehiculePrivate *priv = NULL;
priv = VEHICULE_GET_PROVATE (self);
switch (property_id)
{
case PROP_WINDOW_OPEN:
g_value_set_boolean (value, priv->open_window);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
|
Afin de simplifier le code de notre exemple, les assesseurs sont de
simples interfaces pour notre variable privée, cependant rien ne
nous empêche de faire appel à d'autres fonctions (par exemple
personne_bouge_bras, afin d'éviter tous problèmes lors de la
fermeture des fenêtres...).
Pour en finir avec les propriétés, voici comment les modifier, soit
une par une en passant pas les GValue :
GObject *vehicule = NULL;
GValue val = {0};
vehicule = g_object_new (TYPE_VEHICULE, NULL);
g_value_init (&val, G_TYPE_BOOLEAN);
g_value_set_boolean (&val, TRUE);
g_object_set_property (G_OBJECT (vehicule), "open-window", &val);
|
Soit par lot en passant directement la valeur :
g_object_set (vehicule, "open-vehicule", FALSE, NULL);
|
Syntaxe qui peut aussi être utilisée lors de la construction de l'objet
si la propriété le permet :
vehicule = g_object_new (TYPE_VEHICULE, "open-vehicule", FALSE, NULL);
|
X. GObject Builder
GOB est un pré-processeur qui permet de créer facilement des classes
dans un langage proche du C++ et de les convertir en code C.
Voici un exemple, extrêmement simple, de classe écrite pour GOB :
class Test:Object from G:Object
{
public int i;
private int j;
protected int k;
}
|
Pour obtenir notre code source :
Et nous obtenons trois fichiers portant le nom de notre classe :
| test-object.h |
#include <glib.h>
#include <glib-object.h>
#ifndef __TEST_OBJECT_H__
#define __TEST_OBJECT_H__
#ifdef __cplusplus
extern "C" {
#endif
#define TEST_TYPE_OBJECT (test_object_get_type())
#define TEST_OBJECT(obj) G_TYPE_CHECK_INSTANCE_CAST((obj), test_object_get_type(), TestObject)
#define TEST_OBJECT_CONST(obj) G_TYPE_CHECK_INSTANCE_CAST((obj), test_object_get_type(), TestObject const)
#define TEST_OBJECT_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), test_object_get_type(), TestObjectClass)
#define TEST_IS_OBJECT(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), test_object_get_type ())
#define TEST_OBJECT_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), test_object_get_type(), TestObjectClass)
typedef struct _TestObjectPrivate TestObjectPrivate;
#ifndef __TYPEDEF_TEST_OBJECT__
#define __TYPEDEF_TEST_OBJECT__
typedef struct _TestObject TestObject;
#endif
struct _TestObject {
GObject __parent__;
int i;
int k;
TestObjectPrivate *_priv;
};
typedef struct _TestObjectClass TestObjectClass;
struct _TestObjectClass {
GObjectClass __parent__;
};
GType test_object_get_type (void);
#ifdef __cplusplus
}
#endif
#endif
|
| test-object-private.h |
#ifndef __TEST_OBJECT_PRIVATE_H__
#define __TEST_OBJECT_PRIVATE_H__
#include "test-object.h"
#ifdef __cplusplus
extern "C" {
#endif
struct _TestObjectPrivate {
#line 4 "gobject-classe.gob"
int j;
#line 16 "test-object-private.h"
};
#ifdef __cplusplus
}
#endif
#endif
|
| test-gobject.c |
#define GOB_VERSION_MAJOR 2
#define GOB_VERSION_MINOR 0
#define GOB_VERSION_PATCHLEVEL 15
#define selfp (self->_priv)
#include <string.h> /* memset() */
#include "test-object.h"
#include "test-object-private.h"
#ifdef G_LIKELY
#define ___GOB_LIKELY(expr) G_LIKELY(expr)
#define ___GOB_UNLIKELY(expr) G_UNLIKELY(expr)
#else
#define ___GOB_LIKELY(expr) (expr)
#define ___GOB_UNLIKELY(expr) (expr)
#endif
#define SELF(x) TEST_OBJECT(x)
#define SELF_CONST(x) TEST_OBJECT_CONST(x)
#define IS_SELF(x) TEST_IS_OBJECT(x)
#define TYPE_SELF TEST_TYPE_OBJECT
#define SELF_CLASS(x) TEST_OBJECT_CLASS(x)
#define SELF_GET_CLASS(x) TEST_OBJECT_GET_CLASS(x)
typedef TestObject Self;
typedef TestObjectClass SelfClass;
static void test_object_init (TestObject * o) G_GNUC_UNUSED;
static void test_object_class_init (TestObjectClass * c) G_GNUC_UNUSED;
static GObjectClass *parent_class = NULL;
GType
test_object_get_type (void)
{
static GType type = 0;
if ___GOB_UNLIKELY(type == 0) {
static const GTypeInfo info = {
sizeof (TestObjectClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) test_object_class_init,
(GClassFinalizeFunc) NULL,
NULL ,
sizeof (TestObject),
0 ,
(GInstanceInitFunc) test_object_init,
NULL
};
type = g_type_register_static (G_TYPE_OBJECT, "TestObject", &info, (GTypeFlags)0);
}
return type;
}
#define GET_NEW ((TestObject *)g_object_new(test_object_get_type(), NULL))
#include <stdarg.h>
static TestObject * GET_NEW_VARG (const char *first, ...) G_GNUC_UNUSED;
static TestObject *
GET_NEW_VARG (const char *first, ...)
{
TestObject *ret;
va_list ap;
va_start (ap, first);
ret = (TestObject *)g_object_new_valist (test_object_get_type (), first, ap);
va_end (ap);
return ret;
}
static void
___finalize(GObject *obj_self)
{
#define __GOB_FUNCTION__ "Test:Object::finalize"
TestObject *self G_GNUC_UNUSED = TEST_OBJECT (obj_self);
gpointer priv G_GNUC_UNUSED = self->_priv;
if(G_OBJECT_CLASS(parent_class)->finalize) \
(* G_OBJECT_CLASS(parent_class)->finalize)(obj_self);
}
#undef __GOB_FUNCTION__
static void
test_object_init (TestObject * o G_GNUC_UNUSED)
{
#define __GOB_FUNCTION__ "Test:Object::init"
o->_priv = G_TYPE_INSTANCE_GET_PRIVATE(o,TEST_TYPE_OBJECT,TestObjectPrivate);
}
#undef __GOB_FUNCTION__
static void
test_object_class_init (TestObjectClass * c G_GNUC_UNUSED)
{
#define __GOB_FUNCTION__ "Test:Object::class_init"
GObjectClass *g_object_class G_GNUC_UNUSED = (GObjectClass*) c;
g_type_class_add_private(c,sizeof(TestObjectPrivate));
parent_class = g_type_class_ref (G_TYPE_OBJECT);
g_object_class->finalize = ___finalize;
}
#undef __GOB_FUNCTION__
|
Vous remarquerez que le code est relativement verbeux et surtout ne
correspond pas forcement à votre style de codage, je pense en
particulier aux constantes et noms de fonctions portant des noms
réservés ou encore aux encombrants extern "C" qui pourraient être
remplacés par les macro de la glib. Dans ce cas n'hésitez pas à modifier
le code source de gob2 et en particulier le fichier main.c qui
contient les fonctions permettant d'écrire les blocs de codes.
Par exemple pour cette histoire de extern "C", voici les deux
fonctions que j'ai modifié :
static void
print_header_prefixes(void)
{
char *p;
p = replace_sep(((Class *)class)->otype, '_');
gob_strup (p);
out_printf(outh, "#ifndef H_%s\n#define H_%s\n\n", p, p);
if(outph)
out_printf(outph, "#ifndef H_%s_PRIVATE\n"
"#define H_%s_PRIVATE\n\n"
"#include \"%s.h\"\n\n", p, p, filebase);
g_free(p);
if( ! no_extern_c) {
out_printf(outh, "G_BEGIN_DECLS\n\n");
if(outph)
out_printf(outph, "G_BEGIN_DECLS\n\n");
}
}
static void
print_header_postfixes(void)
{
char *p;
p = replace_sep(((Class *)class)->otype, '_');
gob_strup (p);
if( ! no_extern_c)
out_printf(outh, "\nG_END_DECLS\n");
out_printf(outh, "\n#endif /* not H_%s */\n", p);
if(outph) {
if( ! no_extern_c)
out_printf(outph, "\nG_END_DECLS\n");
out_printf(outph, "\n#endif /* not H_%s_PRIVATE */\n", p);
}
}
|
 |
Si vous êtes sous Windows il est nécessaire de compiler gob2 à partir
des sources. Cependant, vous risquez de rencontrer quelques problèmes.
Une fois les sources décompressées, lancer le script configure,
mais avant de lancer la commande make, ouvrez le fichier
./src/Makefile pour y apporter quelques modifications :
|
XI. Exemple complet
Pour illustrer mes propos j'ai utilisé une classe
Vehicule tout
au long de cet article, cependant on s'en sert rarement et elle est
loin de montrer la puissance de GObject. Voici un exemple plus concret
d'implémentation du
pattern singleton :
| singleton.h |
#ifndef H_NJ_SINGLETON_28112007140332
#define H_NJ_SINGLETON_28112007140332
#include <glib-object.h>
#define TYPE_SINGLETON (singleton_get_type ())
#define SINGLETON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_SINGLETON, Singleton))
#define SINGLETON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_SINGLETON, SingletonClass))
#define IS_SINGLETON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_SINGLETON))
#define IS_SINGLETON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_SINGLETON))
#define SINGLETON_GET_CLASS
|