Xavier Lamorlette

Bibliothèques dynamiques en C++

Types de bibliothèques

Bibliothèques statiques

Fichiers .a : archive.
Créés par ar : « concaténation » de fichiers objets .o.
Windows : .lib.

Bibliothèques dynamiques / partagées

Fichiers .so : shared object
Windows : .dll : dynamic link libraries

Vocabulaire :

Un DSO n'a pas d'adresse fixe de chargement. Il a donc besoin du dynamic linker, qui charge les dépendances d'une application et fait les relocalisations nécessaires.

Pour générer un DSO (plutôt qu'une application), il faut ajouter l'option --shared au linker ld.

Il faut compiler avec l'option -fpic : Position Independent Code. Si la GOT devient trop grosse, il faut utiliser -fPIC.

Résolution des dépendances

À l'édition de lien, on peut donner le chemin vers la bibliothèque avec l'option -L, sinon il faudra le mettre dans le LD_LIBRARY_PATH.

LD_PRELOAD perment de faire de l'interposition de définition.

À l'édition de lien, on peut aussi définier un run path, qui sera examiné après le LD_LIBRARY_PATH.
Il faut donner l'option -rpath (ou -R) au linker :

gcc -Wl,-rpath,/dir1:/foo/dir2,-R,/dir3 file.o

Note : pour passer une option au linker via gcc: gcc -Wl,option

Le dynamic string token, DST, permet de spécifier un run path relatif au fichier execétable : -R $ORIGIN/../lib.

Optimisations des dépendances

static, en C, indique de ne pas exporter une variable ou une fonction.
Les namespaces anonymes, en C++, font la même chose.

Si une variable est const, le compilateur peut mettre son contenu dans en mémoire partagée en lecture seule.

Options du linker :

Outils

ar

Liste les fichiers objets d'une bibliothèque archive.

ldd

Liste les dépendances d'un exécutable.

Pour voir les dépendances superflues :

ldd -u -r foo.so

nm

Liste les symboles d'un fichier objet, d'une bibliothèque archive ou d'une bibliothèque partagée.

nm -u : liste les symboles non définis.

objdump

Liste les informations d'un fichier objet.

readelf

Liste les symboles d'une biliothèque partagée.

Chargement dynamique

Fonctions de base

Références :

En C++ moderne

Interface :

class Base {
public:
    virtual ~Base() {}
    virtual void foo() const = 0;
};

using Base_creator_t = Base *(*)();

Bibliothèque :

class Derived: public Base {
public:
    void foo() const override {}
};

extern "C" {
Base * create() {
    return new Derived;
}
}

Client :

#ifdef _WINDOWS

#include <windows.h>

class Derived_factory {
public:
    Derived_factory() {
        handler = LoadLibrary("derived.dll");
        if (! handler) {
            throw std::runtime_error(Get_error_message());
        }
        FARPROC farproc = GetProcAddress(handler, "create");
        if (! farproc) {
            throw std::runtime_error(Get_error_message());
        }
        creator = reinterpret_cast<Base_creator_t>(farproc);
    }

    std::unique_ptr<Base> create() const {
        return std::unique_ptr<Base>(creator());
    }

    ~Derived_factory() {
        if (handler) {
            FreeLibrary(handler);
        }
    }

private:
    HINSTANCE handler = nullptr;
    Base_creator_t creator = nullptr;

    static std::string Get_error_message() {
        LPVOID message_buffer;
        DWORD dw = GetLastError();
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, dw, 0, (LPTSTR) & message_buffer, 0, NULL);
        std::string error_message(static_cast<char *>(message_buffer));
        LocalFree(message_buffer);
        return error_message;
    }
};

#else

#include <dlfcn.h>

class Derived_factory {
public:
    Derived_factory() {
        handler = dlopen("libderived.so", RTLD_NOW);
        if (! handler) {
            throw std::runtime_error(dlerror());
        }
        Reset_dlerror();
        creator = reinterpret_cast<Base_creator_t>(dlsym(handler, "create"));
        Check_dlerror();
    }

    std::unique_ptr<Base> create() const {
        return std::unique_ptr<Base>(creator());
    }

    ~Derived_factory() {
        if (handler) {
            dlclose(handler);
        }
    }

private:
    void * handler = nullptr;
    Base_creator_t creator = nullptr;

    static void Reset_dlerror() {
        dlerror();
    }

    static void Check_dlerror() {
        const char * dlsym_error = dlerror();
        if (dlsym_error) {
            throw std::runtime_error(dlsym_error);
        }
    }
};

#endif

{
    Derived_factory factory;
    std::unique_ptr<Base> base = factory.create();
    base->foo();
}

Références

“HowTo Write Shared Libraries” d'Ulrich Drepper

La dernière mise à jour de cette page date d'avril 2019.

Le contenu de ce site est, en tant qu'œuvre originale de l'esprit, protégé par le droit d'auteur.
Pour tout commentaire, vous pouvez m'écrire à xavier.lamorlette@gmail.com.