2007-07-08

Un système de "logging" en C

Je travail sur une librairie depuis quelques mois et je désirais y intégrer un système de "logging". Le but est donc de pouvoir suivre ce que fait cette librairie lors de son utilisation. J'ai découvert les fonctions vprintf, etc,.. qui se comportent exactement comme les fonctions printf, mais qui ont le mérite de prendre une va_list comme argument. Du coup, c'est extrêmement pratique pour un système de "logging".

Deux en-têtes sont donc indispensables. Les vprintf appartiennent au stdio.h tout comme les printf, et le va_list est disponible depuis le stdarg.h. Cet article sous-entend donc que vous connaissez déjà ces deux en-têtes. Et donc que vous savez que le stdarg à le mérite de permettre d'écrire des fonctions à paramètres optionnels.

Les messages de log
Nous voulons par exemple générer un message dans le stderr à l'aide de notre fonction de log. Mais dans ce message on aimerait pouvoir passer des valeurs de variables. Comme par exemple:

my_log("variable x=%i, variable y=%s", my_int, my_string);

Il est donc nécessaire de considérer que plusieurs variables peuvent être entrées tout comme pour une fonction printf. Donc notre fonction my_log() doit forcément accepter un nombre optionnel de paramètres.

Exemple:

static void my_log(const char *msg, ...) {
..// (...)
}


Maintenant essayez d'imaginer l'implémentation de cette fonction si vous utilisiez printf ou fprintf. Il serait nécessaire de récupérer chaque paramètres optionnels puis de les interpréter afin de les afficher l'un après l'autre tout en ayant analysé le *msg pour déterminer leurs emplacements. Bref, c'est beaucoup de travail pour pas grand chose. C'est là qu'interviennent les fonctions vprintf.

Quand vous utilisez les arguments optionnels, vous déclarez forcément une variable va_list. Et la fonction va_start() permet de peupler la variable va_list avec les paramètres. Il se trouve que vprintf réclame une variable de type va_list.

Reprenons alors notre exemple en le complétant:

static void my_log(const char *msg, ...) {
..va_list va;

..va_start(va, msg);
..if (msg) {
....vfprintf(stderr, msg, va);
....fprintf(stderr, "\n");
..}
..va_end(va);
}


vfprintf va donc nous éviter un code monstrueux en exploitant directement la liste de paramètres. Ainsi notre fonction de "logging" (minimaliste) est terminée!

2007-06-29

Pseudo-polymorphisme en C

Mais kesako? Je ne suis pas là pour vous expliquer ce qu'est le polymorphisme. Tout programmeur en langage à objets (ou orienté objets) y a touché de près ou de loin. Mais tout l'intérêt de mon message réside dans le fait de faire du polymorphisme, mais sans objet!
L'intérêt? Pourquoi pas? Tout ce qui est bon est à prendre. Il peut arriver d'en dans certains cas d'en avoir besoin et de regretter de ne pas travailler en langage à objets. Alors je vais vous montrer une implémentation très simple du polymorphisme en langage C.


Le langage C
Le langage C est tout sauf objet (c'est un langage impératif), c'est bien pour cela que le C++ a été inventé. Ainsi l'implémentation que je vais vous montrer atteint très vite ses limites. Mais dans des cas particuliers cela peut s'avérer très utile. En objet pour réaliser du polymorphisme il nous serait nécessaire de créer des classes et donc des objets. Voici les correspondances que l'on va faire :

classe > structure
objet > pointeur sur une variable déclarée comme étant une structure
méthode > référence dans la structure à une fonction

Cas en exemple
Comme exemple nous allons prendre un cas où il y a deux pseudo-objets, et chacun de ces pseudo-objet à une pseudo-méthode print(). Nous voulons appeler celle-ci depuis un même tableau de ces pseudo-objets. Commençons par définir nos pseudo-classes.

typedef struct myh_s {
..void (*print) (void);
..int x;
} myh_t;

typedef struct myb_s {
..void (*print) (void);
..double x;
} myb_t;

/* Generic pseudo-class */
typedef struct gen_s {
..void (*print) (void);
} gen_t;

Notre exemple comportera deux pseudo-objets. Une pseudo-classe générique est nécessaire afin de pouvoir déclarer un tableau pour contenir nos pseudo-objets (une sorte de pseudo-héritage). Attention, il faut veiller à définir les pseudo-méthodes dans le même ordre partout! Il faut les définir au début de chaque structure. Les variables double et int sont là uniquement pour l'exemple.

Maintenant que nous avons les structures, il faut définir les fonctions qui feront office de pseudo-méthode.

void print_hello(void) {
..printf("Hello Linuxfr users!\n");
}

void print_bye(void) {
..printf("Bye Linuxfr users!\n");
}


Avant de s'attaquer à la fonction main(), il est important de créer encore deux fonctions pour déclarer nos pseudo-objets en mémoire. Pour cela nous allons faire de l'allocation de mémoire dynamique avec malloc(). Sans oublier d'assigner les références des fonctions à la pseudo-méthode de chaque pseudo-objet.

myh_t *new_h(void) {
..myh_t *my;

..my = malloc(sizeof(myh_t));
..my->print = print_hello;

..return my;
}

myb_t *new_b(void) {
..myb_t *my;

..my = malloc(sizeof(myb_t));
..my->print = print_bye;

..return my;
}


Bien sûr, il faudrait tester les variables afin de s'assurer que l'allocation à pu s'effectuer avec succès. Néanmoins pour cet exemple ce n'est pas fatal. A vous de faire les choses le plus correctement possible dans votre implémentation.
A l'aide de ce travail, notre main() se présente un peu comme un langage à objets et le pseudo-polymorphisme peut agir dans la boucle for.

int main(void) {
..int i;
..myh_t *my_hello;
..myb_t *my_bye;
..gen_t *my[2];

../* Create pseudo-objects */
..my_hello = new_h();
..my_bye = new_b();

..my[0] = (gen_t *)my_hello;
..my[1] = (gen_t *)my_bye;

../* Use pseudo-objects */
..for (i = 0; i < 2; i++)
....my[i]->print();

../* Delete pseudo-objects */
..free(my_hello);
..free(my_bye);

..return 0;
}


La sortie du programme correspond donc à:

Hello Linuxfr users!
Bye Linuxfr users!

Je vous conseil néanmoins de ne pas abuser de ce genre "d'astuce", car il est bien clair que le C n'est pas un langage fait pour l'objet.

2006-01-07

X-Wing Alliance & NVidia ...

Ce message a pour but de vous expliquer comment régler vos éventuels problèmes avec le jeu X-Wing Alliance. Ce superbe jeu est en fait le dernier vrai simulateur spatial StarWars de LucasArts. Ce qu'on entend par simulateur spatial, c'est le fait de piloter des vaisseaux de manière plus évoluée qu'un jeu d'arcade. Il faut bien sûr aimer les 50 combinaisons de touches du clavier et les missions périlleuses dans le monde imaginaire de George Lucas.

Avant-propos
Mais je ne suis pas là pour vous faire l'histoire des simulateurs spatiaux, mais pour vous expliquer le problème que j'ai eu pour y rejouer dans de bonnes conditions. Avant cela il faut savoir que ce simulateur commence à se faire vieux (1999) et la société LucasArts ne semble pas prête d'en resortir un nouveau. La tendance actuelle étant malheuresement aux jeux d'arcades trop facilement abordable.
De ce fait j'ai voulus me replonger dans cette simulation, et quand bien même je l'avais installé, patché, configuré et exécuté sans défaut, un problème pas si majeur que ça mais néanmoins désagréable à la vue, a tout de suite coupé mes ardeurs de pilote.

Un problème de contre-mesures, vous dîtes?
Mais non voyons, laissons les contre-mesures où elles sont, d'ailleur, la première mission ne permet pas d'en équiper notre vaisseau (le fameux YT-1300). Le problème est relatif aux graphismes qui sont désagréables à l'oeil. Pour deux raisons, la première est que les textes sont à la limite du lisible (surtout en pleine mission), non pas qu'ils soient trop petits, mais plutôt qu'ils ne sont pas rendus correctement. La seconde est que le mip-mapping ainsi que le bilinear filtering ne sont pas du tout activé. Je ne suis pas là pour vous expliquer les termes techniques, google peut très bien s'en charger pour vous. Par contre, la photo ci-contre devrait être assez explicite. Peut-être que pour vous ce n'est pas un problème? Dans ce cas je ne vous oblige pas à lire le reste, par contre pour moi c'en est un et je ne peux pas m'empêcher de chercher par tous les moyens possibles pour le corriger et rejouer dans des conditions au moins aussi bonnes qu'à l'époque. Avant de m'avancer plus loin dans le sujet, je souhaiterais qu'en même vous faire part de la configuration matériel et logiciel de l'ordinateur sur lequel "je pilote".

Matériels : Pentium 3 1Ghz/133, 512 Mo RAM, GeForce 4 4800 SE
Logiciels : X-Wing Alliance 2.02, Windows XP Pro SP2, ForceWare 81.98


Comme vous pouvez le voir, il n'y a rien d'extraordinaire, un matériel relativement vieux certe, mais pas plus que le jeu. Et en ce qui concerne les versions logiciels, se sont les plus à jour au moment où j'ai écris ces lignes.

Attention aux tirs croisés!
Ma démarche était très simple, d'abord il fallait m'assurer que je n'étais pas le seul avec ce problème. J'ai donc fais de multiples recherches sur le web mais sans grand succès. Personne ne semblait se plaindre et pourtant il existe de nombreux sites et fans des séries de jeux X-Wing. J'ai donc tenté en vain de trouver une solution en réinstallant les drivers de la carte graphique, en réinstallant les drivers du chipset de la carte mère. Mais rien n'y faisait. Même que ça m'a apporté plus de problèmes que de solutions. Des problèmes que je n'aborderais pas ici. J'ai alors tenté le tout pour le tout et me suis reconnecté sous mon installation Linux, en espérant que WINE puisse me sortir de cette bagarre logiciel.
Pour les moins connaisseurs d'entre vous, WINE n'est pas un bon verre de vin chaud, mais plutôt une API libre (hwww.winehq.org) qui permet d'exécuter des logiciels sous Linux, qui originellement sont fait pour Windows. J'ai donc remis à niveau l'API, tenté de faire fonctionner le jeu et je me suis confronté à un nouveau problème. X-Wing Alliance ne veut pas s'exécuter s'il n'y a pas de joystick connecté à l'ordinateur. Avant de trouver la solution pour le faire reconnaître par WINE, j'en profite pour parcourir les forums francophones comme anglophones afin de poser mes questions. Quelques utilisateurs me répondent qu'ils n'ont pas de problème, l'un d'eux me dit qu'il confirme qu'il a aussi de mauvais graphismes mais avec une GeForce 4400 (équivalente à la mienne). Néanmoins personne ne connait vraiment la cause.
Sur un forum anglophone une personne fait des tests avec sa GeForce 5600, et n'a aucun soucis particulier. Et d'ailleur, tous les utilisateurs de carte graphique ATI ont des graphismes sans la moindre bavure. D'où le titre de ce message. Pendant ce temps, je continue mes investigations avec WINE, je trouve le moyen de lui faire démarrer le jeu, et quand je pense avoir résolu, celui-ci ne veut pas exécuter la partie. WINE étant toujours en plein développement, on ne peut en aucun cas lui reprocher ce genre de faiblesse. Et l'évolution de la compatibilité du jeu peut être constamment suivie à cette adresse.

Enfin une balise hyperespace!
Les forums reste les meilleurs moyens de s'en sortir. Tout ses tests réalisés par les autre joueurs prouvaient quelque chose. Les GeForces de la série 4 posent plus de problème que ceux de la série 5 et plus. Un seul problème persistait chez tout le monde, les textes resortent mal en haute définition comme 1280x1024 par exemple. Mais cela concernant tout le monde (ou tout du moins tous les utilisateurs de GeForce) que LucasArts a mis à disposition les moyens de le corriger. D'ailleur voici l'URI en question chez LucasFiles. Et pour être plus précis, le problème des caractères se manifeste avec l'utilisation de l'anticrénelage (antialiasing) et de l'anisotropie.

Mais revenons à nos GeForce 4. Le moyen de s'en résoudre est simple, mais ennuyeux à la fois. Il faut tout simplement utiliser de vieux pilotes NVidia. Malheuresement la série de pilote 81.nn n'apporte que des ennuies avec XwA, par contre, les 66.93 fonctionne à merveille. Mais cela peut poser bien d'autre problèmes au niveaux de certains jeux videos qui réclament toujours, ce qu'il y a de plus récent.

Je ne peux que vous conseillez de garder les anciens le temps d'y jouer jusqu'à ce que vous en soyez dégouté, pour ensuite remettre tout votre système à jour dans les cas où les dernières versions seraient indispensable. Et si vous regardez la copie d'écran ci-contre, peut-être ne verrez-vous pas de quoi sauter au plafond, mais n'oublions rien, le jeu est vieux et la simulation reste sont point le plus fort, suffisamment fort pour vous faire oublier son âge.

Je vous remercie de m'avoir lu, j'ai été, peut-être, un peu long, mais ça m'a fait plaisir. Et je n'ai pas été plus long que le temps qu'il m'a fallut à comprendre d'où vennait la faille. Car je ne vous ai pas tout dis, comme par exemple, que j'ai tenté de forcer l'installation des vieux pilotes DirectX 6. Mais c'est une autre histoire que je ne ferais pas partager ici.