Lors de la création d'une bibliothèque de classes en C++, vous pouvez choisir entre des bibliothèques dynamiques (.dll
, .so
) et statiques (.lib
, .a
). Quelle est la différence entre eux et quand est-il approprié d'utiliser lequel?
Les bibliothèques statiques augmentent la taille du code dans votre binaire. Ils sont toujours chargés et quelle que soit la version du code que vous avez compilée est la version du code qui s'exécutera.
Les bibliothèques dynamiques sont stockées et versionnées séparément. Il est possible qu'une version de la bibliothèque dynamique à charger ne soit pas la version d'origine fournie avec votre code if la mise à jour est considérée comme étant compatible binaire avec la version d'origine.
De plus, les bibliothèques dynamiques ne sont pas nécessairement chargées - elles sont généralement chargées lors du premier appel - et peuvent être partagées par des composants utilisant la même bibliothèque (charges de données multiples, un chargement de code).
Les bibliothèques dynamiques étaient considérées comme la meilleure solution la plupart du temps, mais à l'origine, elles présentaient un défaut majeur (google DLL hell), qui a pratiquement été éliminé par les systèmes d'exploitation Windows les plus récents (Windows XP en particulier).
D'autres ont bien expliqué ce qu'est une bibliothèque statique, mais j'aimerais souligner certaines des mises en garde liées à l'utilisation de bibliothèques statiques, du moins sous Windows:
Singletons: Si quelque chose doit être global/statique et unique, veillez à ne pas le placer dans une bibliothèque statique. Si plusieurs DLL sont liées à cette bibliothèque statique, elles recevront chacune leur propre copie du singleton. Toutefois, si votre application est un seul fichier EXE sans DLL personnalisée, cela ne pose aucun problème.
Suppression du code non référencé: Lorsque vous vous liez à une bibliothèque statique, seules les parties de la bibliothèque statique référencées par votre DLL/EXE seront liées à votre DLL/EXE.
Par exemple, si mylib.lib
contient a.obj
et b.obj
et que votre DLL/EXE ne fait référence qu'à des fonctions ou des variables provenant de a.obj
, l'intégralité de b.obj
sera ignorée par le lieur. Si b.obj
contient des objets globaux/statiques, leurs constructeurs et leurs destructeurs ne seront pas exécutés. Si ces constructeurs/destructeurs ont des effets secondaires, vous serez peut-être déçu de leur absence.
De même, si la bibliothèque statique contient des points d’entrée spéciaux, vous devrez peut-être veiller à ce qu’ils soient réellement inclus. Un exemple de ceci dans la programmation intégrée (d'accord, pas Windows) serait un gestionnaire d'interruptions marqué comme étant à une adresse spécifique. Vous devez également marquer le gestionnaire d'interruptions comme point d'entrée pour vous assurer qu'il ne sera pas ignoré.
Une autre conséquence de ceci est qu'une bibliothèque statique peut contenir des fichiers objet qui sont complètement inutilisables à cause de références non résolues, mais cela ne provoquera pas d'erreur de l'éditeur de liens jusqu'à ce que vous référeniez une fonction ou une variable à partir de ces fichiers objet. Cela peut se produire longtemps après l’écriture de la bibliothèque.
Symboles de débogage: Vous souhaiterez peut-être un fichier PDB distinct pour chaque bibliothèque statique ou vous voudrez peut-être que les symboles de débogage soient placés dans les fichiers objets afin qu'ils soient envoyés. roulé dans le PDB pour la DLL/EXE. La documentation de Visual C++ explique les options nécessaires .
RTTI: Vous pouvez vous retrouver avec plusieurs objets type_info
pour la même classe si vous liez une seule bibliothèque statique à plusieurs DLL. Si votre programme suppose que type_info
est une donnée "singleton" et utilise &typeid()
ou type_info::before()
, vous risquez d'obtenir des résultats inattendus et surprenants.
Une lib est une unité de code intégrée à l'exécutable de votre application.
Une dll est une unité autonome de code exécutable. Il n'est chargé dans le processus que lorsqu'un appel est passé dans ce code. Une dll peut être utilisée par plusieurs applications et chargée dans plusieurs processus, tout en ne conservant qu'une seule copie du code sur le disque dur.
Dll pros: peut être utilisé pour réutiliser/partager le code entre plusieurs produits; charge dans la mémoire du processus à la demande et peut être déchargé lorsqu'il n'est pas nécessaire; peut être mis à niveau indépendamment du reste du programme.
Dll contre: impact sur les performances du chargement de la DLL et du rebasage du code; problèmes de version ("dll hell")
Lib Pro: aucun impact sur les performances car le code est toujours chargé dans le processus et n'est pas rebasé; pas de problèmes de version.
Lib cons: exécutable/processus "bloat" - tout le code est dans votre exécutable et est chargé au démarrage du processus; pas de réutilisation/partage - chaque produit a sa propre copie du code.
Outre les implications techniques des bibliothèques statiques vs dynamiques (les fichiers statiques regroupent tout dans une seule grande bibliothèque binaire vs dynamique qui permet le partage de code entre plusieurs exécutables différents), il existe également des implications juridiques .
Par exemple, si vous utilisez un code sous licence LGPL et que vous établissez un lien statique avec une bibliothèque LGPL (et créez ainsi un gros fichier binaire), votre code devient automatiquement Open Sourced ( free as in freedom) Code LGPL. Si vous créez un lien avec des objets partagés, il vous suffit alors de LGPL pour apporter les améliorations/corrections de bugs que vous apportez à la bibliothèque LGPL elle-même.
Cela devient un problème beaucoup plus important si vous décidez par exemple de compiler vos applications mobiles (dans Android, vous avez le choix entre statique et dynamique, mais pas dans iOS, c'est toujours statique).
$$:~/static [32]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/static [33]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H
void foo();
#endif
$$:~/static [34]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/static [35]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H
void foo2();
#endif
$$:~/static [36]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/static [37]> cat makefile
hello: hello.o libtest.a
cc -o hello hello.o -L. -ltest
hello.o: hello.c
cc -c hello.c -I`pwd`
libtest.a:foo.o foo2.o
ar cr libtest.a foo.o foo2.o
foo.o:foo.c
cc -c foo.c
foo2.o:foo.c
cc -c foo2.c
clean:
rm -f foo.o foo2.o libtest.a hello.o
$$:~/static [38]>
$$:~/dynamic [44]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/dynamic [45]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H
void foo();
#endif
$$:~/dynamic [46]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/dynamic [47]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H
void foo2();
#endif
$$:~/dynamic [48]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/dynamic [49]> cat makefile
hello:hello.o libtest.sl
cc -o hello hello.o -L`pwd` -ltest
hello.o:
cc -c -b hello.c -I`pwd`
libtest.sl:foo.o foo2.o
cc -G -b -o libtest.sl foo.o foo2.o
foo.o:foo.c
cc -c -b foo.c
foo2.o:foo.c
cc -c -b foo2.c
clean:
rm -f libtest.sl foo.o foo
2.o hello.o
$$:~/dynamic [50]>
Les programmes C++ sont construits en deux phases
La bibliothèque statique (.lib) est juste un paquet de fichiers .obj et n'est donc pas un programme complet. Il n'a pas encore traversé la deuxième phase (de liaison) de la création d'un programme. Les dll, en revanche, sont comme les exe et sont donc des programmes complets.
Si vous construisez une bibliothèque statique, celle-ci n'est pas encore liée et les utilisateurs de votre bibliothèque statique devront donc utiliser le même compilateur que vous avez utilisé (si vous avez utilisé g ++, ils devront utiliser g ++).
Si au lieu de cela vous avez construit une dll (et l'avez construite correctement ), vous avez construit un programme complet que tous les utilisateurs peuvent utiliser, quel que soit le compilateur qu'ils utilisent. Il existe cependant plusieurs restrictions lors de l'exportation à partir d'une dll, si la compatibilité entre compilateurs croisés est souhaitée.
Vous devez bien réfléchir aux changements dans le temps, aux versions, à la stabilité, à la compatibilité, etc.
Si deux applications utilisent le code partagé, voulez-vous forcer ces applications à changer ensemble, au cas où elles devraient être compatibles entre elles? Ensuite, utilisez la DLL. Tous les exe utiliseront le même code.
Ou voulez-vous les isoler les uns des autres pour pouvoir en changer un et avoir la certitude que vous n'avez pas cassé l'autre? Ensuite, utilisez la bibliothèque statique.
Les enfers des DLLs sont ceux où vous auriez probablement dû utiliser une bibliothèque statique, mais vous avez utilisé une dll à la place, et tous les exes ne sont pas compatibles avec celle-ci.
Une bibliothèque statique est compilée dans le client. Une .lib est utilisée au moment de la compilation et le contenu de la bibliothèque devient une partie de l’exécutable consommateur.
Une bibliothèque dynamique est chargée à l'exécution et n'est pas compilée dans l'exécutable du client. Les bibliothèques dynamiques sont plus flexibles car plusieurs exécutables clients peuvent charger une DLL et utiliser ses fonctionnalités. Cela permet également de minimiser la taille globale et la maintenabilité de votre code client.
Une bibliothèque statique doit être liée à l'exécutable final. il fait partie de l'exécutable et le suit partout où il passe. Une bibliothèque dynamique est chargée chaque fois que l'exécutable est exécuté et reste séparée de celui-ci sous la forme d'un fichier DLL.
Vous utiliseriez DLL lorsque vous souhaitez pouvoir modifier les fonctionnalités fournies par la bibliothèque sans devoir relier l'exécutable (remplacez simplement le fichier DLL, sans avoir à remplacer le fichier exécutable).
Vous utiliseriez une bibliothèque statique chaque fois que vous n'avez pas de raison d'utiliser une bibliothèque dynamique.
L'article de Ulrich Drepper sur " Comment écrire des bibliothèques partagées " est également une bonne ressource qui explique comment tirer le meilleur parti des bibliothèques partagées, ou ce qu'il appelle des "objets partagés dynamiques" (DSO). Il se concentre davantage sur les bibliothèques partagées au format binaire ELF , mais certaines discussions conviennent également aux DLL Windows.
Pour une excellente discussion sur ce sujet, lisez cet article de Sun.
Cela va dans tous les avantages, y compris de pouvoir insérer des bibliothèques interposées. Vous trouverez plus de détails sur l'interposition dans cet article ici .
En réalité, le compromis que vous faites (dans un grand projet) est au moment du chargement initial, les bibliothèques vont être reliées à un moment ou à un autre, la décision à prendre est de savoir si le lien durera assez longtemps pour que le compilateur ait besoin mordre la balle et le faire à l’avance, ou l’éditeur de liens dynamique peut-il le faire au moment du chargement.
Si votre bibliothèque doit être partagée entre plusieurs exécutables, il est souvent logique de la rendre dynamique afin de réduire la taille des exécutables. Sinon, rendez-le définitivement statique.
Il y a plusieurs inconvénients à utiliser une DLL. Il y a des frais généraux supplémentaires pour le chargement et le déchargement. Il y a aussi une dépendance supplémentaire. Si vous changez la dll pour la rendre incompatible avec votre exécutalbes, ils cesseront de fonctionner. Par contre, si vous modifiez une bibliothèque statique, vos exécutables compilés utilisant l'ancienne version ne seront pas affectés.
Si la bibliothèque est statique, au moment de la liaison, le code est lié à votre exécutable. Cela rend votre exécutable plus grand (que si vous utilisiez la route dynamique).
Si la bibliothèque est dynamique, au moment du lien, les références aux méthodes requises sont intégrées à votre exécutable. Cela signifie que vous devez expédier votre exécutable et la bibliothèque dynamique. Vous devez également déterminer si l'accès partagé au code de la bibliothèque est sûr, une adresse de chargement préférée, entre autres.
Si vous pouvez vivre avec la bibliothèque statique, allez avec la bibliothèque statique.
Les bibliothèques statiques sont des archives contenant le code objet de la bibliothèque. Lorsque ce dernier est lié à une application, ce code est compilé dans l'exécutable. Les bibliothèques partagées sont différentes en ce qu'elles ne sont pas compilées dans l'exécutable. Au lieu de cela, l’éditeur de liens dynamique cherche dans certains répertoires la (les) bibliothèque (s) dont il a besoin, puis les charge en mémoire. Plus d'un exécutable peut utiliser la même bibliothèque partagée en même temps, réduisant ainsi l'utilisation de la mémoire et la taille de l'exécutable. Cependant, il y a alors plus de fichiers à distribuer avec l'exécutable. Vous devez vous assurer que la bibliothèque est installée sur le système uses où le lieur peut la trouver. La liaison statique élimine ce problème mais génère un fichier exécutable plus volumineux.
Nous utilisons beaucoup de DLL (> 100) dans notre projet. Ces DLL ont des dépendances entre elles et nous avons donc choisi la configuration de la liaison dynamique. Cependant, il présente les inconvénients suivants:
Peut-être qu'une meilleure configuration consistait à faire tout une bibliothèque statique (et donc vous n'avez qu'un seul exécutable). Cela ne fonctionne que si aucune duplication de code n'a lieu. Un test semble confirmer cette hypothèse, mais je n'ai pas pu trouver de devis MSDN officiel. Donc par exemple faire 1 exe avec:
Le code et les variables de shared_lib2 ne doivent figurer qu'une seule fois dans l'exécutable fusionné final. Quelqu'un peut-il soutenir cette question?
Si vous travaillez uniquement sur des projets incorporés ou sur des plates-formes spécialisées, les bibliothèques statiques sont la seule solution à prendre, mais elles sont souvent moins fastidieuses à compiler dans votre application. De plus, avoir des projets et des fichiers Make qui incluent tout rend la vie plus heureuse.
Je donnerais comme règle générale que si vous avez une grande base de code, toutes construites sur des bibliothèques de niveau inférieur (par exemple, un framework Utils ou Gui), que vous voulez partitionner en bibliothèques plus faciles à gérer, puis en faire des bibliothèques statiques. Les bibliothèques dynamiques ne vous achètent pas vraiment et il y a moins de surprises - il n'y aura qu'une seule instance de singletons par exemple.
Si vous avez une bibliothèque entièrement séparée du reste de la base de code (par exemple, une bibliothèque tierce), envisagez de la transformer en une DLL. Si la bibliothèque est LGPL, vous devrez peut-être utiliser une dll de toute façon en raison des conditions de licence.