La majorité des gens ici (puisque pas mal développent encore sous Windows) qui développent des "librairies" C n'en développent en réalité pas, je pensais en faire un topic, et le voici.
Compilation
La première chose que vous devriez avoir appris sur les langages de programmation, c'est la distinction entre les langages
compilés et les langages
interprétés... mais je reviens dessus pour m'assurer que c'est bien clair.
Imaginez que vous avez un livre en japonais et que vous ne comprenez que le français. Pour le traduire, vous avez plusieurs possibilités :
- séparer l'étape de traduction (compilation) et l'étape de lecture (exécution), ainsi une fois traduit le livre peut être lu autant de fois qu'il le faut et rapidement, en oubliant qu'il fallait qu'il soit traduit ;
- traduire à la volée, ce qui implique que vous devez traduire le livre en français à chaque fois que vous le lisez (à l'aide d'un traducteur, un interpréteur).
Pour le code, c'est la même chose : l'ordinateur ne lit que du code machine (le français), et vous codez dans un langage autre (le japonais peut ici représenter le C, le C++, ...). Donc la compilation, c'est l'art de transformer du texte organisé en du code machine.
Fichiers objet
À savoir, tout ce que je vais dire à partir de là concerne le format
ELF. Ce format, que vous utilisez sans doute déjà sans le savoir, est un format qui peut se décliner en plusieurs types de fichiers : des fichiers objets (repositionnables), des exécutables, et d'autres choses hors-sujet.
Le format ELF, c'est plus ou moins des symboles (avec une table de correspondance symboles/adresse dans le fichier) et des références à des symboles externes (qui ne sont pas dans le fichier), du code machine et des données brutes, organisés dans des sections.
Prenons du code C comme exemple :
// Prototypes
int fonction2();
// Globales
int globale5 = 12346;
int fonction1(void)
{
// les variables non-statiques ne génèrent pas de symbole
int variable8 = 44;
return (fonction2() + globale5 + variable8);
}
Ici, le fichier objet (une fois que GCC sera passé) contiendra deux symboles :
_globale5 et
_fonction1, et une référence à un symbole :
_fonction2 (puisque celle-ci est utilisée dans fonction1). (oui, il faut savoir que le compilateur C ajoute un underscore devant tous les noms de fonctions/globales/variables statiques).
Une question qui peut vous venir à l'esprit, c'est "mais du coup, les globales et les fonctions donnent la même chose dans le fichier objet, comment on sait que ce sont des globales et des constantes ?". Cette question est hors de la portée de ce tutoriel, mais elle est intéressante, et pour répondre à celle-ci, vous devrez comprendre au moins un assembleur (du coup, si vous êtes là, j'imagine que ce sera davantage l'assembleur SuperH).
Link
Le linker, c'est le logiciel qui va prendre des fichiers objets et des libs statiques (on va en parler après) et qui va mélanger tout ça ensemble pour en faire un exécutable.
Si vous faites un exécutable avec simplement le fichier objet présent ci-dessus, il va vous produire une erreur du genre "Le symbole
_fonction2 requis n'a pas été trouvé", il faut donc lui fournir un autre fichier objet avec le symbole _fonction2 dedans.
Par ailleurs, une fois que l'exécutable est créé, on n'a plus besoin de la table des symboles ou de la table des sections. Mais sachez que le fichier en
.elf que
le tutoriel de Lephenixnoir vous fait générer comporte encore cette table des symboles et des sections, rien ne vous empêche d'aller voir pour vérifier avec
sh3eb-elf-nm.
Bibliothèques statiques
(oui, on dit une bibliothèque, puisqu'on emprunte des objets dans des bibliothèques (et non une librairie), ou une "lib", pour faire référence au terme anglais)
Une bibliothèque statique est tout bêtement une archive (un fichier avec plein de fichiers dedans, comme les fichiers ZIP ou TAR) de fichiers objet.
On pourrait dire "oui mais du coup, au lieu de linker la lib, autant prendre ses fichiers objet et linker avec, non ?", et non, parce que la grande particularité des libs statiques, c'est que les fichiers objets qu'elle contient sont
facultatifs.
Reprenons le link. Lors de l'opération de link, tout ce qui est dans les fichiers objets que vous lui passerez directement sera mis dans l'exécutable. Seulement, il va manquer pas mal de symboles, comme au hasard,
_memcpy ou
_ML_vram_address -- eh bien, pour trouver ces symboles manquants, le linker va parcourir tous les fichiers objet présents dans les libs statiques linkées et prendre ceux où se trouvent les symboles qu'il manque à l'exécutable.
On peut donc voir les libs statiques comme des collections de symboles, donc des collections de fonctions (et éventuellement de constantes et autres données brutes) qui ne seront peut-être pas toutes ajoutées à l'exécutable d'un projet les utilisant.
Comment créer une lib statique ?
Faire une lib statique, ce n'est pas sorcier, il suffit de :
- compiler les fichiers source de la lib en fichiers objet ;
- créer une archive avec tous les fichiers objet ;
- éventuellement, créer un index des fichiers objet présents dans l'archive et de leurs symboles pour faciliter le job du linker.
Attention cependant : n'oubliez pas que le linker va inclure non pas des fonctions, mais des fichiers objet. Si vous mettez toutes les fonctions de la lib dans un fichier source et que, du coup, vous ne faites qu'un seul fichier objet, ce fichier objet sera inclus dans l'exécutable finale dès que l'un des symboles présents dedans sera requis. Divisez bien vos fonctions en fichiers, l'idéal étant une fonction par fichier, mais il y a des cas particuliers : imaginons qu'une fonction soit appelée par une seule autre fonction, et qu'elle ne soit pas prévue pour être appelée par l'utilisateur, alors il faut la mettre dans le fichier source de cette autre fonction et même en faire une fonction statique (disponible uniquement depuis le fichier).
Par exemple, pour créer la
libmonochrome, son Makefile va faire :
sh3eb-elf-gcc -c -o obj/ML_vram_address.o src/ML_vram_address.c -I ./include -Wall -Wextra
sh3eb-elf-gcc -c -o obj/ML_clear_vram.o src/ML_clear_vram.c -I ./include -Wall -Wextra
...
sh3eb-elf-ar rc libmonochrome.a ./obj/ML_vram_address.o ./obj/ML_clear_vram.o ...
sh3eb-elf-ranlib libmonochrome.a
Gérer un projet de lib statique
Faire un simple projet pour créer la lib, c'est bien, mais penser à l'utilisateur, c'est mieux. Un projet de lib statique propose généralement trois étapes :
- la
configuration : on donne (ou laisse par défaut) les dossiers d'installation des fichiers header, des fichiers lib, ...
- la
création : créer la lib (avec les commandes données ci-dessus, par exemple) ;
- l'
installation : installer les fichiers prêts à l'emploi sur la machine.
Avec la
libmonochrome par exemple, pour créer la lib puis l'installer, on réalise trois commandes :
./configure --prefix=~/opt/sh3eb-elf
make
make install
Je vous conseille vraiment de garder ça aussi simple que ça pour l'utilisateur qui va se servir de votre lib derrière.
Libs déjà créées/portées
-
libfx (
1.0) : lib du SDK, originale par CASIO
-
libinput (
2.0,
topic dédié) : manipulation avancée du clavier, originale par Ninestars.
-
libmonochrome (
1.1,
topic dédié) : originale par PierrotLL, y a-t-il encore besoin de la présenter ?
-
librtc (
0.3,
topic dédié) : manipulation du temps à l'aide de la Real-Time Clock.
(peut-être qu'un jour, quelqu'un trouvera le moyen d'en faire des packages pour les principales distributions utilisées par les membres, à savoir Arch/Manjaro et des Debian-based ?)