Conception d’une bibliothèque réentrant en C – Obscurification
0Pour mon premier article, je vais me contenter de donner une petite astuce permettant de masquer des choix d’implémentation d’une bibliothèque et d’éviter les variables globales pour utiliser un enchaînement de fonctions de cette bibliothèque.
Prenons par exemple le cas d’un calcul de CRC. D’un point de vue utilisateur, nous effectuons le calcul comme cela :
- Initialisation du calculateur
- Insertion de données pour le calcul du CRC
- Récupération du CRC ou vérification
Nous allons utiliser une bibliothèque de calcul pour cela. Cette bibliothèque fournira comme fonctions :
- crc_init() : initialise le calculateur
- crc_add(unsigned char) : ajoute des données
- crc_get()
- crc_check()
Or, nous voulons que cette bibliothèque soit réentrante car nous l’utiliserons dans un environnement multi-thread. Nous allons donc utiliser un contexte que nous passerons d’un appel de fonction à l’autre. Mais on ne veut pas que ce contexte soit modifiable par le programme utilisant notre bibliothèque. Voici l’astuce :
/*********** Fichier : libCRC.h ***********/
typedef struct crc_internal * crc_context;
enum {
CRC5,
CRC8,
CRC16,
CRC32
} crc_type;
/* création d’un contexte */
crc_context crc_create_context(const enum crc_type _type);
/* initialisation du calcul */
void crc_init(const crc_context _context);
/* Insertion de données */
void crc_add(const crc_context _context, const unsigned char data);
/* Récupération du crc calculé */
int crc_get(const crc_context _context);
/* Vérification du crc */
int crc_check(const crc_context _context);
/* Libération du contexte */
void crc_delete_context(crc_context *_context);
/********** libCRC.c **********/
#include « libCRC.h »
struct crc_internal
{
enum crc_type type;
int shift_register;
int check_value;
int initial_value;
int polynomial;
};
crc_context crc_create_context(const enum crc_type _type)
{
crc_context new_context = NULL;
new_context = malloc(sizeof(struct_internal));
/** populate new_context according _type **/
return new_context;
}
void crc_delete_context(crc_context *context)
{
free(*context);
*context = NULL;
}
/********* main.c **********/
#include « libCRC.h »
int main(int argc, char *argv[])
{
unsigned char data[NB_DATA];
int crc_value;
crc_context crc;
crc = crc_create_context(CRC16);
crc_init(crc);
crc_add(crc, data[0]);
crc_add(crc, data[1]);
…
crc_value = crc_get(crc);
crc_delete_context(&crc);
return 0;
}
L’astuce consiste à déclarer un nouveau type « pointeur vers une structure », type de structure que seule notre librairie connait. Ainsi, le programme principale connait le type et la taille des données qu’il a à gérer : un pointeur sur une structure particulière.
Et voilà, on est capable de trimbaler un contexte, évitant d’avoir une variable globale causant la perte de la réentrance, tout en cachant l’implémentation de la bibliothèque aux utilisateurs.






