Catégorie : programmation C

Tout ce qui a trait à la programmation C que j’apprend jour après jour

1 – QMK firmware – clavier AZERTY – Update

Pour tous ceux qui veulent le récupérer et le compiler, cela se passe par .

Attention ce qui suit est une explication du temps que j’ai perdu, mais j’essaie de rester honnête : il y a deux gros « firmwares » que l’on peut mettre dans les claviers de geek : le firmware QMK et le firmware kiibohd. Les deux personnes qui bossent dessus sont hyper fortes et connaissent les claviers et les drivers sur le bout des doigts. De mon point de vue je ne conseille que le premier (dont j’ai mis les sources en téléchargement juste au dessus), car le kiibohd n’est que sources de problèmes.

Celui qui a fait de grosses sources, travaillé énormément dessus et qui a le plus de connaissances est Jacob Alexander (alias haata), et vous trouverez toutes ses sources ici. Seul gros problème : même s’il est plein de bonne volonté, son code est difficilement lisible et le comportement même du clavier est totalement incohérent par rapport à ce que je recherchais. Je ne conseille pas du tout de récupérer et d’utiliser ce code pour ces raisons :

  • si vous voulez afficher une image totalement différente sur votre LCD par rapport à la couche, c’est impossible : il affiche uniquement un chiffre par rapport à la couche en cours (et si on vient d’une couche à une autre, par exemple de la 3 à la 2, alors vous verrez 2 chiffres)
  • si vous voulez facilement programmer des macros, vous allez galérer à mort, je n’y suis pas arrivé
  • si vous voulez compiler facilement sous Windows, c’est la même chose, et la seule réponse c’est : utilisez une machine virtuelle Linux et compilez là. Super !

Bref, que des points bloquants, même si un seul était résolu, les autres m’empêcheraient de faire ce que je veux avec mon Infinity Ergodox, donc : go for QMK !.

Infinity ergodox

Après avoir acheté deux fois l’Infinity Ergodox (oui le premier avait des défaut de LED, celle de droite était défectueuse… je l’ai gardé car je ne l’aurais jamais revu si je l’avais renvoyé), j’ai récupéré toutes les sources et j’ai tout fait pour les recompiler.

Bref, il fonctionne :


Infinity Ergodox


Infinity Ergodox


Infinity Ergodox

Je mets en disponibilité mes sources pour la configuration complète d’un clavier AZERTY tel que je l’ai expliqué dans la page de configuration du clavier, avec quelques modifications qui me conviennent mieux..

vim : exemple concret de conversion de fichier C en fichier Pascal

Voici un aperçu des petites choses possibles avec les macros vim.
J’avais un énorme fichier include (fichier fmod.h) à convertir en Pascal, afin de pouvoir accéder à l’extraordinaire librairie de fmod. Pour information, cette librairie est gratuite et redistribuable en tant que telle si votre produit est gratuit et ne vous rapporte rien. Donc profitez en c’est une librairie aussi simple d’utilisation que puissante et stable.

Je vous explique juste le principe de base, avec un exemple concret, et vous verrez qu’il vous suffit de faire pareil pour les quelques autres conversions, et vous gagnerez quelques minutes, voire comme dans mon cas, quelques heures de travail (que vous auriez inévitablement perdues avec un autre éditeur) :

Voici un exemple de déclaration C :

typedef FMOD_RESULT
  (F_CALLBACK *FMOD_FILE_OPENCALLBACK)
    (const char *name, int unicode,
      unsigned int *filesize, void **handle, void **userdata);

La fonction finale déclarée en Pascal doit être ainsi :

FMOD_FILE_OPENCALLBACK =
  function (
    const name:Pchar; unicode:Integer;
    var filesize: Cardinal; handle:Pointer;
    userdata:Pointer):FMOD_RESULT;

Je vais vous expliquer les premiers pas : il faut convertir ces paramètres :
void **handle, void **userdata)
en :
handle:Pointer; userdata:Pointer)

Le principe des macros avec les expressions régulières est très simple, je vais la construire pas à pas :

– Va chercher la première chaine qui est void ** :
void \*\*\
Notez bien que comme les étoiles * sont des caractères d’expression régulière, il faut mettre un antislash \ pour qu’elles ne soient pas interprétées.

– Va chercher la première chaine qui est void ** et qui est suivie d’une chaine composée de au moins un caractère minuscule comprise entre a et z :
void \*\*\([a-z]\+\)
Au même titre que les étoiles *, et les antislashes \, les parenthèses () sont des caractères d’expression régulière, il faut mettre un antislash \ pour qu’elles ne soient pas interprétées.
Oui je le concède ça rend la lecture un peu difficile. Mais le temps gagné en vaut la chandelle croyez moi.
– Va chercher la première chaine qui est void ** et qui est suivie d’une chaine composée de au moins un caractère minuscule comprise entre a et z, et qui se termine par une virgule :
void \*\*\([a-z]\+\)\(,\{1\}\)
Ici aussi notez le \{1\} : ici, les crochets \{\} sont des caractères d’expression régulière donc il ne faudrait pas mettre d’antislash, mais comme il sont dans une expression qu’on recherche, il les faut. Bref, essayez une fois avec, une fois sans, et l’un des deux fonctionnera !

Expression finale :

Remplace toutes les chaines de type void **[caractères de a à z], par [caractères de a à z]: Pointer;  :
:%s/void \*\*\([a-z]\+\)\(,\{1\}\)/\1: Pointer;/g

Ainsi :
void **handle,
donnera :
handle: Pointer;.

Mais (et c’est là où c’est génial) :
void **autrenomdevariable,
donnera :
autrenomdevariable: Pointer;.

Et cela s’appliquera sur tout le fichier, quel que soit le nom de la variable.

Ensuite, il vous suffit de vous concocter toute votre séance de petites macros, et de les mettre dans un fichier.
Puis vous appelez :

vim -s macro_vim.cmd [nom du fichier]

et vim jouera la macro dessus.

Librairie gd et gdImageStringFT() : la libération à faire

Il n’est précisé nulle part, que lorsque vous utilisez des fonctions d’écriture telle que gdImageStringFT(), il faut toujours appeler gdFontCacheShutdown() à la fin du programme pour libérer les allocations faites. C’est valgrind qui m’a montré que ce n’était pas correctement libéré. Vive valgrind !

Programmer en C la librairie gd : le texte

J’ai décidé de faire un petit article qui explique rapidement comment écrire du texte « non crénelé », donc « antialiasé » du des images, en C, en utilisant la librairie gd.

Tout est très simple : vous trouverez déjà le manuel ici, pas mis à jour mais suffisant et les exemples fonctionnent, ce qui est le plus important.

Ce qui m’a posé le plus de problème, ce n’est pas de demander à écrire du texte. L’exemple ici fonctionne parfaitement, faites un copier coller, hop on y est.
Non, le problème c’est la « région » calculée dans laquelle sera dessiné le texte.
Pour résumer, on demande de pré-calculer la « boîte » dans laquelle sera dessiné le texte (toujours l’exemple ici).
Ce que j’ai mis beaucoup de temps à comprendre, c’est ce que je vais traduire dans « mon » langage : la boîte englobante qui est renvoyée, c’est l’espace qui sera utilisé en fonction du point de référence (x,y) que vous donnez pour dessiner.

Je m’explique. Vous demandez à calculer la boite pour dessiner le texte en passant la variable NULL :

err = gdImageStringFT(NULL,&brect[0],0,f,sz,0.,0,0,s);
if (err) {fprintf(stderr,err); return 1;}

Maintenant, dans brect il y aura ce genre d’informations :


     (   -1,  -57)     ( 1513,  -57)
           +-----------------+
           |                 |
           |                 |
           +-----------------+
     (   -1,   15)     ( 1513,   15)

Et en fait, c’est uniquement des informations pour vous, parce qu’après, lorsque vous lui demanderez de dessiner, il les ignorera : seules les informations x et y seront utilisées :

err = gdImageStringFT(im,&brect[0],black,f,sz,0.0,x,y,s);
if (err) {fprintf(stderr,err); return 1;}

Autrement dit, prenons un exemple : vous voulez dessiner votre texte en x=0. Seulement, le rectangle de retour est celui que j’ai affiché en haut : ça signifie que le dessin va « déborder » de (-1) pixel sur x. Autrement dit, si vous voulez le mettre en 0, il faut donner les coordonnées (x=1). Même chose pour y : les lettres vont « s’étaler » 57 pixels au dessus, et 15 pixels en dessous. Donc si vous voulez écrire le texte en (0,0), il vous faudra passer x=1, y=57.

En relisant ça parait facile, mais ça ne l’a pas été pour moi !
En espérant que cela serve à quelqu’un un jour…

Apache : rotation de log

J’ai trouvé un article sur la rotation des logs très intéressant, mais il y a une lacune du côté du serveur Apache qui n’est pas résolue. D’ailleurs qui ne semble résolue nulle part.

>_<

A savoir lorsqu’on utilise la gestion des hôtes virtuels, apache crée un processus de log *par* fichier log. Donc par exemple : un hôte virtuel, un fichier de access_log et un fichier error_log dédié = 2 process rotatelogs. J’ai 12 sites vhost (très peu de fréquentation heureusement), ça me fait un total de 24 process dans ma liste des process… J’ai beau chercher un moyen de n’avoir qu’un seul process, je n’ai trouvé que le module mod_log_rotate qui donne la possibilité d’avoir la rotation de log directement en interne dans Apache, mais, à cause de la gestion des erreurs telle qu’implémentée dans Apache, ce module ne peut gérer que les logs d’accès, pas les logs d’erreur. Autrement dit il me restera tout de même 12 logs différents qui correspondront aux logs d’erreur.

Je me demandais, si, éventuellement, il ne serait pas plus judicieux de faire comme le suggère ce monsieur, à savoir ne pas utiliser mod_log_rotate, ni le programme livré avec Apache, mais de se servir plutôt de l’outil système d’archivage, et, en pratique, juste avant de les archiver, de faire un « awstats » sur les logs. Ce qui me gêne, c’est l’éventualité d’un conflit d’écriture. Que se passe-t-il si jamais j’ai 30 visiteurs de connectés sur mon site en même temps (ce qui serait très bien), qu’Apache écrit dans le log concerné, et que Logrotate est lancé à ce moment là ? C’est la seule et unique chose qui me retient pour l’instant de faire le saut dans cette direction.

Apache : développement C et tutoriaux apr

Mes petits articles vont peut être me rendre célèbre.
Enfin chez les geeks. Et les asiatiques (les deux pages sur lesquelles je suis cité sont en Japonais)

Voilà la réponse du type qui a fait les exemples les plus pratiques, question « tutoriaux apr » :

Hi,

Sorry for the late reply.

I appreciate what you’ve done.

I put the links on the following pages (the former is written in Japanese).

http://dev.ariel-networks.com/column/tech/libarp_tutorial/
http://dev.ariel-networks.com/apr/

Thanks again.

Librairie Apr : tutoriel 12

12. DSO (Dynamic Symbol Object)

Vous connaissez peut-être les objets partagés (so = shared object) ou les dlls (dll=dynamic link library). Grossièrement on peut dire que DSO est né du principe des so/dll. Mais on parle de dso (Dynamic Symbol Object) pour les différencier des librairies dynamiques so/dll.

Pour comprendre le fonctionnement des dso, il faut savoir comment fonctionne le link et le chargement. En général, lorsqu’on utilise des librairies dynamiques, on les « link » avec le programme principal lors de la compilation. ld(1) s’occupe de cela sur Unix. ld(1) est connu en tant qu’éditeur de lien. Comme ld(1) est habituellement appelé par le compilateur lui-même gcc(1) de façon implicite, peut-être n’avez-vous jamais entendu parler de ld(1). Au mieux avez-vous vu des messages problème de « link » lors de la compilation. Cela signifie que ld(1) n’arrive pas à résoudre certains symboles que vous utilisez dans votre programme. En cours d’exécution, ld.so(8) charge les librairies dynamiques. S’il n’y arrive pas, vous verrez des messages d’erreur de chargement. En résumé, le « link » est fait lors de la compilation, et le chargement est fait lors de l’exécution. Si vous voulez en savoir plus, il vous suffit de lire les manuels ld(1) et ld.so(8).

Grâce à dso, le link et le chargement sont faits lors de l’exécution. Quel est l’intérêt d’un tel mécanisme ? La raison se résume en deux mots : architecture plugin. La seule chose que nous ayons à faire lors de la compilation est de définir les interfaces (« noms symboliques » et la manière les appeler, les paramètres à leur donner, donc) des modules qu’il devra être possible de charger. Le programme charge les modules pendant son exécution, et s’en sert au travers d’interfaces. Les modules dynamiquement « téléchargeables » peuvent ainsi être développés par des programmeurs indépendants. Vous pourrez ainsi rendre votre programme très flexible et extensible.

Voyons voir dso-sample.c. Vous pouvez y voir deux chaines : « libm.so » et « pow ». En réalité, pow(3) est une fonction basique et, en temps normal, n’a pas besoin d’être appelée sous forme de dso, mais c’est simplement pour l’exemple.

Au début, on appelle apr_dso_load() et on lui donne le nom de la librairie : « libm.so ».

/* extrait de dso-sample.c, vérif. des erreurs omise */
const char fname[] = "libm.so";
    apr_dso_handle_t *dso_h;
    apr_dso_load(&dso_h, fname, mp);

Comme vous pouvez l’imaginer, si vous voulez programmer en donnant la possibilité aux autres de faire des plugins pour votre programme, vous devez faire en sorte d’avroi un fichier dans lequel il y a tous les noms des plugins à télécharger. On a déjà quelque chose qui tourne plutôt bien : les modules apache. Leurs noms sont spécifiés, en général, dans le fichier de configuration principal : le fichier « httpd.conf ».

Lorsque apr_dso_load() renvoie une erreur, c’est, la plupart du temps, parce qu’il ne trouve pas le fichier de la librairie dynamique. Le chemin de recherche pour les librairies dépend du système d’exploitation. Sur GNU/Linux, il dépend de la variable d’environnement LD_LIBRARY_PATH. Sur MS-Windows, il dépend de la variable d’environnement PATH. Après avoir un résultat de retour de apr_dso_load() réussi, on peut appeler apr_dso_sym().

Voici le prototype :

/* extrait de apr_dso.h */
APR_DECLARE(apr_status_t) apr_dso_sym(
    apr_dso_handle_sym_t *ressym,
    apr_dso_handle_t *handle,
    const char *symname);

On peut récupérer un objet par son nom via apr_dso_sym(). Le premier argument est un argument résultat. Le second argument est un handle dso, qu’on a avec apr_dso_open() expliqué précédemment. Le troisième argument est le nom du symbole.

Le code suivant est un extrait de dso-sample.c. Le nom du symbole est « pow » et on récupère un pointeur de fontion en tant qu’objet. Comme on connait l’interface de pow(3), il nous suffit de définir la fonction en tant que type : pow_fn_t.

/* extrait de dso-sample.c, vérif. des erreurs omise */
typedef double (*pow_fn_t)(double x, double y);
pow_fn_t pow_fn;

/* récupérer la fonction pow(3) de libm.so */
apr_dso_sym(
    (apr_dso_handle_sym_t*)&pow_fn,
    dso_h, "pow");

/* appeler pow(3) */
printf("%d ^ %d = %f\n", 2, 2, pow_fn(2, 2));

Si votre programme a des plugins, vous devrez définir des noms symboliques ainsi que leurs interfaces. Par la suite, les développeurs devront s’appuyer sur ces déclarations. Enfin, on appelle apr_dso_unload() pour libérer le module. Cela diminue ainsi la consommation mémoire.

Librairie Apr : tutoriels 10 et 11

10. Options de la ligne de commande

Pour les outils en ligne de commande (CLI, Command Line Interface), les options qu’on donne en ligne de commandes sont souvent utilisées. La librairie libapr met des fonctions à disposition qui facilitent grandement la gestion des options fournies en ligne de commande. Voici un extrat de getopt-sample.c.

/* extrait de getopt-sample.c */
static const apr_getopt_option_t opt_option[] = {
    /* énumération comme suit :
     * {opt. longue}, {opt. courte},
     * {flag "argument qui suit"}, {description} :
     */

    /* -i nomfichier or --in nomfichier : */
    { "in", 'i', TRUE, "fichier entrant" },

    /* -o nomfichier or --out nomfichier : */
    { "out", 'o', TRUE, "fichier sortant" },

    /* -h or --help : */
    { "help", 'h', FALSE, "voir l'aide" },

    /* sentinelle de fin : */
    { NULL, 0, 0, NULL },
};

En premier lieu, il faut fournir un tableau d’éléments apr_getopt_option_t. On l’appelle (ici) option-list. Chaque élément a quatre variables :

  1. une option longue ;
  2. une option courte ;
  3. un flag « argument qui suit », pour préciser si cette option nécessite un argument qui doit la suivre ;
  4. une description.

Par exemple, pour l’option « help » :

  • Une option longue : ‘–help’ :
  • Une option courte : ‘-h’.

Les options courtes sont obligatoires et les options longues sont optionnelles. On peut tout à fait mettre NULL pour une option longue. La troisième variable précise si un argument supplémentaire doit suivre cette option. Par exemple si une option de ligne de commande fonctionne ainsi  ‘–in nomfichier’, c’est à dire qu’il faut obligatoirement préciser un nom de fichier, il faut mettre ce flag à vrai (TRUE). Ainsi, si le programme est exécuté et qu’on donne cette option sans donner un argument, par exemple ‘./a.out -in’, une erreur est levée.
Enfin, le tableau de la liste des options doit se terminer par une sentinelle vide qui précise que la liste est finie (voir l’exemple précédent).
Pour parcourir les options fournies dans la ligne de commande, if faut tout d’abord initialiser le parser en appelant ‘apr_getopt_init()’ (pour initialiser un objet apr_getopt_t) puis faire une boucle en s’aidant du tableau des options. Ensuite, on appelle en boucle apr_getopt_long() tant qu’il renvoie APR_SUCCESS. Voici un extrait de getopt-sample.c :

/* extrait de getopt-sample.c */
/* initialiser apr_getopt_t */
apr_getopt_t *opt;
apr_getopt_init(&opt, mp, argc, argv);
/* parcourir toutes les options via opt_option[] */
while ((rv = apr_getopt_long(opt,opt_option,
                             &optch,
                             &optarg)) == APR_SUCCESS) {
...etc.

Dans la boucle, apr_getopt_long() enumère les options passées en ligne de commande une par une. Si l’option trouvée fait partie de la liste des options, apr_getopt_long() renvoie APR_SUCCESS et initialise la valeur de optch en conséquence. Si l’option a un argument supplémentaire apr_getopt_long() le lit et initialise la valeur de optarg.

Voyons un exemple concret. Imaginons que vous lanciez le programme en y ajoutant une option et une valeur associée : ‘./getopt-sample -h -i foo.txt’. Lors de la première boucle, apr_getopt_long() trouve ‘h’ dans la liste des option. Donc, apr_getopt_long() renvoie APR_SUCCESS et initialise optch avec la valeur ‘h’. Sur la boucle suivante, apr_getopt_long() trouve ‘i’ dans la liste des options, et comme ‘i’ nécessite un argument qui doit le suivre, il y a lecture de l’argument suivant, ‘foo.txt’. Ainsi, apr_getopt_long() renvoie APR_SUCCESS, avec optch qui vaut ‘i’ et optarg qui vaut « foo.txt ». Lors de la boucle suivante, apr_getopt_long() ne trouve plus d’options et renvoie par conséquent APR_EOF.

11. memory map (mmap)

mmap signifie « mapping mémoire ». Ce qui veut dire « mapper » des fichiers en mémoire. mmap est principalement utilisé pour :

  • Lire/écrire dans des fichiers le plus rapidement possible ;
  • Allouer un espace mémoire consécutif plus gros (gestion spécifique à chaque système d’exploitation) ;
  • Partage mémoire entre différents processus.

Pensez au cas où vous voulez lire un fichier en entier en une fois. Dans ce cas il faut faire un buffer, lire du début jusqu’à la fin dans une boucle. Le code ressemblerait à cela :

/* code simpliste pour lire le contenu d'un fichier */
apr_file_t *fp;
apr_file_open(&fp, filename, APR_READ, APR_OS_DEFAULT, mp);
while (1) {
    char buf[1024];
    apr_size_t len = sizeof(buf);
    rv = apr_file_read(fp, buf, &len);
    if (rv != APR_SUCCESS) {
        break;
    }
    /* scan buf */
}
apr_file_close(fp);

On peut faire mieux, avec apr_mmap_t :

/* extrait de mmap-sample.c, vérif. des erreurs omise */
apr_file_open(&fp, filename, APR_READ, APR_OS_DEFAULT, mp);
apr_finfo_t finfo;
apr_file_info_get(&finfo, APR_FINFO_SIZE, fp);
apr_mmap_t *mmap;
apr_mmap_create(&mmap, fp, 0, finfo.size, APR_MMAP_READ, mp);
/* scan mmap->mm */
apr_mmap_delete(mmap);

Si le fichier est suffisamment gros, le code basé sur mmap sera plus rapide.
Plus important encore, mmap aide à éviter la fragmentation mémoire. La plupart des systèmes (qui doivent gérer les allocations mémoire) se trouvent confronté au problème de fragmentation mémoire, mais mmap n’entre pas dans le cadre d’allocation mémoire dans l’espace utilisateur. Malheureusement, sur certains systèmes, mmap est parfois lent et bogué.

On peut utiliser mmap pour modifier des fichiers. On ouvre le fichier avec APR_WRITE, puis on « mmap » le fichier en précisant le flag APR_MMAP_WRITE.

REMARQUE : Il est interdit de « mmap-er » un fichier ouvert avec le flag APR_BUFFERED. Le code suivant renverra toujours l’erreur APR_EBADF :

/* Exemple mmap BOGUE */
apr_file_t *fp;
apr_mmap_t *mm;
apr_file_open(&fp,
    fname,
    APR_READ|APR_BUFFERED,
    APR_OS_DEFAULT, mp);
rv = apr_mmap_create(&mm,
    fp, 0,
    finfo.size,
    APR_MMAP_READ, mp);
/* BOGUE : le résultat sera TOUJOURS :
 * rv==APR_EBADF
 */