Les bibliothèques

Les bibliothèques (libraries) permettent de modulariser le code, de fournir des fonctionnalités et de réduire le temps de compilation et/ou la taille de l'exécutable. Il en existe trois sortes en C++ :

Bibliothèques header-only

Ce format est populaire grâce à sa simplicité d'utilisation : il suffit d'inclure un unique fichier pour disposer du code. Aucun problème d'ABI (Application Binary Interface) ou de portabilité (différents OS, compilateurs et machines) n'apparaît puisque le code est recompilé lorsqu'il est inclut, avec les options du compilateur de l'utilisateur.

Parfois, ces librairies incluent un second fichier, le fichier source de la librairie. Il peut être contenu dans le premier fichier : il suffit alors de définir une macro pour que le fichier source soit défini.

// ------ my_library.hpp

#pragma once

// Contenu du header ...

// Macro à définir pour définir le fichier source
#if defined MY_LIBRARY_SOURCES

// Contenu du fichier source ...

#endif

// ------ my_library_usage.cpp

#define MY_LIBRARY_SOURCES
#include <my_library.hpp>

Cependant, ces librairies vont augmenter le temps de compilation, et nécessitent de mettre le code source à disposition.

Bibliothèques statiques

Une bibliothèque statique est similaire à un aggrégat d'Object Files. Elle est compilée séparément du code de l'utilisateur de la bibliothèque : il faut donc qu'elle soit compatible. Pour cela, un système de build avec le code source et/ou des distributions de la bibliothèque (debug/release, linux/windows, 32/64bits, ...) sont mis à disposition.

Les outils ar (pour Linux) et lib (pour Windows, utilisé par défaut) permettent de créer des bibliothèques statiques incorporant d'autres bibliothèques statiques.

De la même façon que les Object Files, la bibliothèque est ajoutée à l'exécutable lors du linker. Elle peut donc bénéficier des Link-Time Optimisation (même si les librairies et l'exécutable ne sont pas écrits dans le même langage).

Bibliothèques dynamiques

Les bibliothèques dynamiques sont similaires à des exécutables qui ne disposent pas du point d'entrée main mais de plusieurs autres points d'entrée (les fonctions mises à disposition). Pour cela, des modificateurs de visibilité sont utilisés (propres au compilateur).

// ----- my_library.hpp


// On définit les macros suivantes :
//   PUBLIC_API  va exporter la variable, la fonction ou la classe.
//   PRIVATE_API ne va pas exporter le symbole.

// On traite d'abord MSVC.
#if defined _MSC_VER
    #if defined BUILD_SHARED_LIBRARY
        // Si on build la bibliothèque, il faut exporter les symboles.
        #define PUBLIC_API __declspec(dllexport)
        // Les symboles ne sont pas exportés par défaut.
        #define PRIVATE_API
    #else
        // Si la bibliothèque est utilisée, il faut importer les symboles.
        #define PUBLIC_API __declspec(dllimport)
        #define PRIVATE_API
    #endif
// Si on n'utilise pas MSVC, on part du principe que l'on utilise gcc ou clang.
#else
        // Il n'y a pas besoin de différencier l'exportation de l'importation.
        // Les symboles sont exportés par défaut.
        // On peut préciser [[gnu::visibility("default")]].
        #define PUBLIC_API
        #define PRIVATE_API [[gnu::visibility("hidden")]]
#endif

// Seul 'make_int' sera utilisable.
PUBLIC_API int make_int();
PRIVATE_API void int_utility(int& ref);

// ----- my_library.cpp

#include <my_library.hpp>

PUBLIC_API int make_int() {
    int i;
    int_utility(i);
    return i;
}

PRIVATE_API void int_utility(int& ref) {
    ref = 42;
}

Par rapport aux bibliothèques statiques, les bibliothèques dynamiques sont moins performantes, car elles sont chargées a l'exécution du programme (les Link-Time Optimisation sont impossibles). Elles doivent rester disponibles lors de l'utilisation de l'exécutable, car elles ne sont pas intégrées à l'intérieur (ce qui diminue sa taille) : cela permet à plusieurs exécutables d'utiliser la même bibliothèque dynamique. Tant que son ABI est stable, la bibliothèque dynamique peut être changée sans toucher à l'exécutable.

Attention au phénomène de DLL Hell (terme apparu pour certains programmes sur Windows), qui arrive lorsqu'il y a trop de dépendances entre les bibliothèques dynamiques utilisées.

Chargement dynamique

Les bibliothèques dynamiques peuvent être chargées manuellement, pendant que l'exécutable est lancé. Cela peut être utile par exemple pour utiliser des plugins depuis le programme.

Pour cela, les fonctions dlopen et dlclose (Linux) ou LoadLibrary et FreeLibrary (Windows) permettent de charger et libérer une bibliothèque dynamique. Ensuite, ses symboles peuvent être accédés avec dlsym ou GetProcAddress.

Si ces fonctions retournent un pointeur nul, elles ont échoué. Leur erreur peut être lue avec dlerror (sur Linux) ou GetLastError (sur Windows).

Pour aller plus loin

Afin d'utiliser la bibliothèque dans d'autres langages, les fonctions en C++ doivent généralement être exportées avec la convention du C : cela se fait avec extern "C".

La compabilité entre les librairies et les exécutables est importante, il faut donc faire attention à la stabilité de l'ABI proposée.

Les symboles des bibliothèques dynamiques peuvent être renommés avec objcopy (sur Linux). Il faut alors également modifier son header.

Last updated