Différence entre les objets partagés (.so), les bibliothèques statiques (.a) et les DLL (.so)?


273

J'ai participé à un débat sur les bibliothèques sous Linux et je voudrais confirmer certaines choses.

C'est à ma connaissance (veuillez me corriger si je me trompe et je modifierai mon article plus tard), qu'il existe deux façons d'utiliser les bibliothèques lors de la construction d'une application:

  1. Bibliothèques statiques (fichiers .a): au moment du lien, une copie de la bibliothèque entière est placée dans l'application finale afin que les fonctions de la bibliothèque soient toujours disponibles pour l'application appelante.
  2. Objets partagés (fichiers .so): au moment de la liaison, l'objet est simplement vérifié par rapport à son API via le fichier d'en-tête correspondant (.h). La bibliothèque n'est réellement utilisée qu'au moment de l'exécution, là où elle est nécessaire.

L'avantage évident des bibliothèques statiques est qu'elles permettent à l'application entière d'être autonome, tandis que l'avantage des bibliothèques dynamiques est que le fichier ".so" peut être remplacé (c'est-à-dire: au cas où il devrait être mis à jour en raison d'une sécurité bug) sans que l’application de base soit recompilée.

J'ai entendu certaines personnes faire une distinction entre les objets partagés et les bibliothèques liées dynamiques (DLL), même s'il s'agit à la fois de fichiers ".so". Existe-t-il une distinction entre les objets partagés et les DLL en ce qui concerne le développement C / C ++ sous Linux ou tout autre système d'exploitation compatible POSIX (par exemple: MINIX, UNIX, QNX, etc.)? On me dit qu'une différence clé (jusqu'à présent) est que les objets partagés sont juste utilisés au moment de l'exécution, tandis que les DLL doivent être ouvertes en premier en utilisant l'appel dlopen () dans l'application.

Enfin, j'ai également entendu certains développeurs mentionner les "archives partagées", qui, à ma connaissance, sont également des bibliothèques statiques elles-mêmes, mais ne sont jamais utilisées directement par une application. Au lieu de cela, d'autres bibliothèques statiques se lieront aux "archives partagées" pour extraire certaines (mais pas toutes) des fonctions / ressources de l'archive partagée dans la bibliothèque statique en cours de construction.

Merci d'avance à tous pour votre aide.

Mettre à jour


Dans le contexte dans lequel ces termes m'ont été fournis, il s'agissait effectivement de termes erronés utilisés par une équipe de développeurs Windows qui devaient apprendre Linux. J'ai essayé de les corriger, mais les normes linguistiques (incorrectes) sont restées.

  1. Objet partagé: bibliothèque qui est automatiquement liée à un programme au démarrage du programme et existe en tant que fichier autonome. La bibliothèque est incluse dans la liste de liens au moment de la compilation (c'est-à-dire: LDOPTS+=-lmylibpour un fichier de bibliothèque nommé mylib.so). La bibliothèque doit être présente au moment de la compilation et au démarrage de l'application.
  2. Bibliothèque statique: bibliothèque qui est fusionnée dans le programme lui-même au moment de la construction pour une seule application (plus grande) contenant le code d'application et le code de bibliothèque qui est automatiquement lié à un programme lors de la construction du programme, et le binaire final contenant les deux le programme principal et la bibliothèque elle-même existent en tant que fichier binaire autonome unique. La bibliothèque est incluse dans la liste de liens au moment de la compilation (c'est-à-dire: LDOPTS+=-lmylibpour un fichier de bibliothèque nommé mylib.a). La bibliothèque doit être présente au moment de la compilation.
  3. DLL: essentiellement identique à un objet partagé, mais plutôt que d'être incluse dans la liste de liens au moment de la compilation, la bibliothèque est chargée via dlopen()/ dlsym()commandes afin que la bibliothèque n'ait pas besoin d'être présente au moment de la compilation pour que le programme compile. De plus, la bibliothèque n'a pas besoin d'être présente (nécessairement) au démarrage de l'application ou au moment de la compilation , car elle n'est nécessaire qu'au moment où les appels dlopen/ dlsymsont effectués.
  4. Archive partagée: essentiellement identique à une bibliothèque statique, mais est compilée avec les indicateurs "export-shared" et "-fPIC". La bibliothèque est incluse dans la liste de liens au moment de la compilation (c'est-à-dire: LDOPTS+=-lmylibSpour un fichier de bibliothèque nommé mylibS.a). La distinction entre les deux est que cet indicateur supplémentaire est requis si un objet partagé ou une DLL veut lier statiquement l'archive partagée dans son propre code ET pouvoir rendre les fonctions de l'objet partagé disponibles pour d'autres programmes, plutôt que de simplement les utiliser interne à la DLL. Ceci est utile dans le cas où quelqu'un vous fournit une bibliothèque statique et que vous souhaitez la reconditionner en tant que SO. La bibliothèque doit être présente au moment de la compilation.

Mise à jour supplémentaire

La distinction entre " DLL" et " shared library" n'était qu'un langage familier (paresseux, inexact) dans l'entreprise dans laquelle je travaillais à l'époque (les développeurs Windows étant obligés de passer au développement Linux, et le terme est bloqué), en respectant les descriptions mentionnées ci-dessus.

De plus, le " S" littéral de fin après le nom de la bibliothèque, dans le cas des "archives partagées", n'était qu'une convention utilisée dans cette entreprise, et non dans l'industrie en général.


14
Pour les .afichiers, le "a" signifie en fait "archove", et c'est simplement une archive de fichiers objets. Les éditeurs de liens modernes devraient être assez bons pour ne pas avoir besoin d'inclure la bibliothèque while, juste les fichiers objets dans l'archive qui sont nécessaires, et pourraient même simplement utiliser les sections de code / données dans les fichiers objets référencés.
Un programmeur mec

4
DLL n'est que la terminologie de Windows. Il n'est pas utilisé sur les unités.
R .. GitHub ARRÊTEZ D'AIDER LA GLACE



2
@DevNull "arch i ve" bien sûr. :)
Un programmeur du

Réponses:


94

J'ai toujours pensé que les DLL et les objets partagés ne sont que des termes différents pour la même chose - Windows les appelle DLL, tandis que sur les systèmes UNIX ce sont des objets partagés, avec le terme général - bibliothèque liée dynamiquement - couvrant les deux (même la fonction pour ouvrir un .so sous UNIX est appelé dlopen()après «bibliothèque dynamique»).

Ils ne sont en effet liés qu'au démarrage de l'application, cependant votre notion de vérification par rapport au fichier d'en-tête est incorrecte. Le fichier d'en-tête définit les prototypes qui sont nécessaires pour compiler le code qui utilise la bibliothèque, mais au moment du lien, l'éditeur de liens regarde à l'intérieur de la bibliothèque elle-même pour s'assurer que les fonctions dont il a besoin sont bien là. L'éditeur de liens doit trouver les corps de fonction quelque part au moment de la liaison, sinon il provoquera une erreur. Il le fait également au moment de l'exécution, car comme vous le faites remarquer à juste titre, la bibliothèque elle-même peut avoir changé depuis la compilation du programme. C'est pourquoi la stabilité d'ABI est si importante dans les bibliothèques de plates-formes, car le changement d'ABI est ce qui rompt les programmes existants compilés avec les anciennes versions.

Les bibliothèques statiques ne sont que des paquets de fichiers objets directement sortis du compilateur, tout comme ceux que vous construisez vous-même dans le cadre de la compilation de votre projet, de sorte qu'ils sont extraits et alimentés dans l'éditeur de liens de la même manière, et les bits inutilisés sont tombé exactement de la même manière.


1
Pourquoi certains projets que je vois sous Linux doivent-ils utiliser l'appel dlopen () pour accéder aux fonctions dans un fichier ".so", et certains n'ont pas du tout à le faire? Merci, au fait!
Cloud

9
Ceux qui ne le font pas reçoivent les fonctions qui leur sont confiées par le chargeur de processus, c'est-à-dire le chargeur elf de linux. dlopen existe si l'application veut ouvrir et utiliser un .so ou .dll qui n'était pas là lors de la compilation ou simplement ajouter des fonctionnalités supplémentaires, comme des plugins.
rapadura

Mais l'application ne se compilera-t-elle pas du tout si le .so n'est pas présent au moment de la construction? Est-il possible de forcer l'éditeur de liens à simplement construire le programme final sans le .so présent du tout? Je vous remercie.
Cloud

1
Je pense que cela dépend de la façon dont vous utilisez les fonctions de .so, mais ici ma connaissance de cela s'arrête: / Bonnes questions.
rapadura

1
En ce qui concerne dlopen () et sa famille de fonctions, je crois comprendre que cela est utilisé pour ouvrir / fermer par programmation une DLL afin qu'elle ne doive pas être chargée en mémoire tout au long de l'exécution de l'application. Sinon, vous devez indiquer à l'éditeur de liens dans ses arguments de ligne de commande (aka votre makefile) que vous voulez que la bibliothèque soit chargée. Il sera chargé au moment de l'exécution et restera chargé en mémoire jusqu'à la fermeture de l'application. Il peut y avoir plus de choses qui peuvent se produire au niveau du système d'exploitation, mais c'est à peu près ce qui se passe en ce qui concerne votre application.
Taylor Price

198

Une bibliothèque statique (.a) est une bibliothèque qui peut être liée directement à l'exécutable final produit par l'éditeur de liens, elle y est contenue et il n'est pas nécessaire d'avoir la bibliothèque dans le système où l'exécutable sera déployé.

Une bibliothèque partagée (.so) est une bibliothèque qui est liée mais non intégrée dans l'exécutable final, elle sera donc chargée au lancement de l'exécutable et devra être présente dans le système sur lequel l'exécutable est déployé.

Une bibliothèque de liens dynamiques sur Windows (.dll) est comme une bibliothèque partagée (.so) sur Linux, mais il existe quelques différences entre les deux implémentations liées au système d'exploitation (Windows vs Linux):

Une DLL peut définir deux types de fonctions: exportées et internes. Les fonctions exportées sont destinées à être appelées par d'autres modules, ainsi qu'à partir de la DLL où elles sont définies. Les fonctions internes sont généralement destinées à être appelées uniquement à partir de la DLL où elles sont définies.

Une bibliothèque SO sous Linux n'a pas besoin d'une instruction d'exportation spéciale pour indiquer les symboles exportables, car tous les symboles sont disponibles pour un processus d'interrogation.


1
+1 belle explication simple. Si une fonction est déclarée "interne" dans une DLL, cela signifie-t-il qu'elle ne peut pas être appelée depuis l'extérieur de la bibliothèque?
Mike

23
Il n'est pas nécessairement vrai que tous les symboles sont disponibles dans une bibliothèque SO. Les symboles masqués sont possibles et recommandés car il n'y a aucune bonne raison pour que les utilisateurs de la bibliothèque voient tous vos symboles.
Zan Lynx

3
Pour info: g ++ a une __attribute__syntaxe pour les symboles d'export sélectifs:#define DLLEXPORT __attribute__ ((visibility("default"))) #define DLLLOCAL __attribute__ ((visibility("hidden")))
Brian Haak

33

Je peux développer les détails des DLL dans Windows pour aider à clarifier ces mystères à mes amis ici dans * NIX-land ...

Une DLL est comme un fichier d'objet partagé. Les deux sont des images, prêtes à être chargées en mémoire par le programme de chargement du système d'exploitation respectif. Les images sont accompagnées de divers bits de métadonnées pour aider les éditeurs de liens et les chargeurs à faire les associations nécessaires et à utiliser la bibliothèque de code.

Les DLL Windows ont une table d'exportation. Les exportations peuvent être par nom ou par position de table (numérique). Cette dernière méthode est considérée comme «à l'ancienne» et est beaucoup plus fragile - la reconstruction de la DLL et la modification de la position d'une fonction dans la table se termineront en catastrophe, alors qu'il n'y a pas de vrai problème si la liaison des points d'entrée est nominative. Donc, oubliez cela comme un problème, mais sachez qu'il est là si vous travaillez avec du code "dinosaure" tel que des bibliothèques de fournisseurs tiers.

Les DLL Windows sont construites en compilant et en liant, comme vous le feriez pour un EXE (application exécutable), mais la DLL est conçue pour ne pas être autonome, tout comme un SO est destiné à être utilisé par une application, soit via un chargement dynamique, soit par liaison de temps de liaison (la référence au SO est incorporée dans les métadonnées du binaire de l'application, et le chargeur de programme OS chargera automatiquement les SO référencés). Les DLL peuvent référencer d'autres DLL, tout comme les SO peuvent référencer d'autres SO.

Sous Windows, les DLL ne rendront disponibles que des points d'entrée spécifiques. Celles-ci sont appelées "exportations". Le développeur peut soit utiliser un mot clé spécial du compilateur pour rendre un symbole visible de l'extérieur (pour les autres éditeurs de liens et le chargeur dynamique), soit les exportations peuvent être répertoriées dans un fichier de définition de module qui est utilisé au moment de la liaison lorsque la DLL elle-même est Étant créé. La pratique moderne consiste à décorer la définition de fonction avec le mot-clé pour exporter le nom du symbole. Il est également possible de créer des fichiers d'en-tête avec des mots clés qui déclareront ce symbole comme un à importer à partir d'une DLL en dehors de l'unité de compilation actuelle. Recherchez les mots clés __declspec (dllexport) et __declspec (dllimport) pour plus d'informations.

L'une des caractéristiques intéressantes des DLL est qu'elles peuvent déclarer une fonction de gestionnaire standard "lors du chargement / déchargement". Chaque fois que la DLL est chargée ou déchargée, la DLL peut effectuer une initialisation ou un nettoyage, selon le cas. Cela correspond bien à la présence d'une DLL comme gestionnaire de ressources orienté objet, tel qu'un pilote de périphérique ou une interface d'objet partagé.

Lorsqu'un développeur souhaite utiliser une DLL déjà construite, il doit soit référencer une "bibliothèque d'exportation" (* .LIB) créée par le développeur de DLL lorsqu'il a créé la DLL, soit il doit explicitement charger la DLL au moment de l'exécution et demander la adresse du point d'entrée par son nom via les mécanismes LoadLibrary () et GetProcAddress (). La plupart du temps, la liaison avec un fichier LIB (qui contient simplement les métadonnées de l'éditeur de liens pour les points d'entrée exportés de la DLL) est la façon dont les DLL sont utilisées. Le chargement dynamique est généralement réservé à l'implémentation du «polymorphisme» ou de la «configurabilité d'exécution» dans les comportements du programme (accès à des modules complémentaires ou à des fonctionnalités définies ultérieurement, alias «plugins»).

La façon de faire de Windows peut parfois causer de la confusion; le système utilise l'extension .LIB pour faire référence à la fois aux bibliothèques statiques normales (archives, comme les fichiers POSIX * .a) et aux bibliothèques "d'export stub" nécessaires pour lier une application à une DLL au moment de la liaison. Par conséquent, il faut toujours vérifier si un fichier * .LIB a un fichier * .DLL du même nom; sinon, les chances sont bonnes que le fichier * .LIB soit une archive de bibliothèque statique et n'exporte pas les métadonnées de liaison pour une DLL.


4

Vous avez raison en ce que les fichiers statiques sont copiés dans l'application au moment de la liaison et que les fichiers partagés sont simplement vérifiés au moment de la liaison et chargés au moment de l'exécution.

L'appel dlopen ne concerne pas uniquement les objets partagés, si l'application souhaite le faire lors de l'exécution en son nom, sinon les objets partagés sont chargés automatiquement au démarrage de l'application. DLLS et .so sont la même chose. le dlopen existe pour ajouter des capacités de chargement dynamique encore plus fines pour les processus. Vous n'avez pas besoin d'utiliser vous-même dlopen pour ouvrir / utiliser les DLL, cela se produit également au démarrage de l'application.


Quel serait un exemple d'utilisation de dlopen () pour plus de contrôle de chargement? Si le SO / DLL est chargé automatiquement au démarrage, dlopen () le ferme-t-il et le rouvre-t-il avec différentes autorisations ou restrictions, par exemple? Je vous remercie.
Cloud

1
Je pense que le dlopen est destiné aux plugins ou fonctionnalités similaires. Les autorisations / restrictions doivent être les mêmes que pour le chargement automatique, et de toute façon un dlopen chargera récursivement des bibliothèques dépendantes.
rapadura

DLL et ne.so sont pas exactement la même chose. Voir cette réponse
Basile Starynkevitch
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.