Comment fonctionne la bibliothèque d'importation? Détails?

88

Je sais que cela peut sembler assez basique aux geeks. Mais je veux que ce soit clair comme du cristal.

Lorsque je veux utiliser une DLL Win32, j'appelle généralement les API comme LoadLibrary () et GetProcAdderss (). Mais récemment, je développe avec DirectX9, et je dois ajouter des fichiers d3d9.lib , d3dx9.lib , etc.

J'ai assez entendu dire que LIB est pour la liaison statique et la DLL est pour la liaison dynamique.

Donc, ma compréhension actuelle est que LIB contient la mise en œuvre des méthodes et est lié statiquement au moment de la liaison dans le cadre du fichier EXE final. Alors que DLL est chargé dynamiquement au moment de l'exécution et ne fait pas partie du fichier EXE final.

Mais parfois, certains fichiers LIB sont fournis avec les fichiers DLL, donc:

  • À quoi servent ces fichiers LIB?
  • Comment réalisent-ils ce à quoi ils sont destinés?
  • Existe-t-il des outils qui me permettent d'inspecter les composants internes de ces fichiers LIB?

Mise à jour 1

Après avoir vérifié wikipedia, je me souviens que ces fichiers LIB sont appelés bibliothèque d'importation . Mais je me demande comment cela fonctionne avec mon application principale et les DLL à charger dynamiquement.

Mise à jour 2

Tout comme RBerteig l'a dit, il y a du code stub dans les fichiers LIB nés avec les DLL. Donc, la séquence d'appel devrait être comme ceci:

Mon application principale -> stub dans la LIB -> véritable DLL cible

Alors, quelles informations devraient contenir ces LIB? Je pourrais penser à ce qui suit:

  • Le fichier LIB doit contenir le chemin complet de la DLL correspondante; Ainsi, la DLL pourrait être chargée par le runtime.
  • L'adresse relative (ou le décalage de fichier?) Du point d'entrée de chaque méthode d'exportation DLL doit être codée dans le stub; Ainsi, des sauts / appels de méthode corrects peuvent être effectués.

Ai-je raison sur ce point? Y a-t-il autre chose?

BTW: Existe-t-il un outil qui peut inspecter une bibliothèque d'importation? Si je peux le voir, il n'y aura plus de doutes.

smwikipedia
la source
4
Je vois que personne n'a répondu à la dernière partie de votre question, qui concerne les outils permettant d'inspecter une bibliothèque d'importation. Avec Visual C ++, il existe au moins deux façons de procéder: lib /list xxx.libet link /dump /linkermember xxx.lib. Consultez cette question sur le débordement de pile .
Alan
De plus, dumpbin -headers xxx.libfournit des informations plus détaillées, par rapport au libet les linkservices publics.
m_katsifarakis

Réponses:

102

La liaison à un fichier DLL peut se produire implicitement au moment de la liaison de compilation , ou explicitement au moment de l'exécution. Dans tous les cas, la DLL finit par être chargée dans l'espace mémoire des processus et tous ses points d'entrée exportés sont disponibles pour l'application.

S'il est utilisé explicitement au moment de l'exécution, vous utilisez LoadLibrary()et GetProcAddress()pour charger manuellement la DLL et obtenir des pointeurs vers les fonctions que vous devez appeler.

S'ils sont liés implicitement lors de la génération du programme, les stubs pour chaque exportation DLL utilisée par le programme sont liés au programme à partir d'une bibliothèque d'importation, et ces stubs sont mis à jour lorsque l'EXE et la DLL sont chargés au lancement du processus. (Oui, j'ai simplifié plus d'un peu ici ...)

Ces stubs doivent provenir de quelque part, et dans la chaîne d'outils Microsoft, ils proviennent d'une forme spéciale de fichier .LIB appelée bibliothèque d'importation . Le .LIB requis est généralement généré en même temps que la DLL et contient un stub pour chaque fonction exportée à partir de la DLL.

De façon confuse, une version statique de la même bibliothèque serait également livrée sous forme de fichier .LIB. Il n'y a pas de moyen simple de les distinguer, sauf que les LIB qui sont des bibliothèques d'importation pour les DLL seront généralement plus petites (souvent beaucoup plus petites) que la LIB statique correspondante.

Si vous utilisez la chaîne d'outils GCC, par ailleurs, vous n'avez pas besoin de bibliothèques d'importation pour correspondre à vos DLL. La version de l'éditeur de liens Gnu portée sur Windows comprend directement les DLL et peut synthétiser la plupart des stubs requis à la volée.

Mise à jour

Si vous ne pouvez pas résister à savoir où se trouvent réellement tous les écrous et boulons et ce qui se passe réellement, il y a toujours quelque chose chez MSDN pour vous aider. L'article de Matt Pietrek Un examen approfondi du format de fichier exécutable portable Win32 est un aperçu très complet du format du fichier EXE et de la façon dont il est chargé et exécuté. Il a même été mis à jour pour couvrir .NET et plus depuis son apparition dans MSDN Magazine ca. 2002.

En outre, il peut être utile de savoir comment savoir exactement quelles DLL sont utilisées par un programme. L'outil pour cela est Dependency Walker, aka depend.exe. Une version de celui-ci est incluse avec Visual Studio, mais la dernière version est disponible auprès de son auteur à l' adresse http://www.dependencywalker.com/ . Il peut identifier toutes les DLL qui ont été spécifiées au moment de la liaison (chargement anticipé et chargement différé) et il peut également exécuter le programme et surveiller les DLL supplémentaires qu'il charge au moment de l'exécution.

Mise à jour 2

J'ai reformulé une partie du texte précédent pour le clarifier lors de la relecture, et pour utiliser les termes de l'art des liens implicites et explicites pour la cohérence avec MSDN.

Ainsi, nous avons trois façons de rendre les fonctions de bibliothèque disponibles pour être utilisées par un programme. La question de suivi évidente est alors: "Comment choisir quelle voie?"

La liaison statique est la façon dont la majeure partie du programme lui-même est liée. Tous vos fichiers objets sont répertoriés et rassemblés dans le fichier EXE par l'éditeur de liens. En cours de route, l'éditeur de liens s'occupe de tâches mineures comme la correction des références aux symboles globaux afin que vos modules puissent appeler les fonctions de l'autre. Les bibliothèques peuvent également être liées statiquement. Les fichiers objets qui composent la bibliothèque sont rassemblés par un bibliothécaire dans un fichier .LIB dans lequel l'éditeur de liens recherche les modules contenant les symboles nécessaires. L'un des effets de la liaison statique est que seuls les modules de la bibliothèque utilisés par le programme y sont liés; les autres modules sont ignorés. Par exemple, la bibliothèque mathématique C traditionnelle comprend de nombreuses fonctions de trigonométrie. Mais si vous liez contre et utilisezcos(), vous ne vous retrouvez pas avec une copie du code pour sin()ou à tan()moins que vous n'ayez également appelé ces fonctions. Pour les grandes bibliothèques avec un ensemble riche de fonctionnalités, cette inclusion sélective de modules est importante. Sur de nombreuses plates-formes telles que les systèmes embarqués, la taille totale du code disponible pour une utilisation dans la bibliothèque peut être importante par rapport à l'espace disponible pour stocker un exécutable dans l'appareil. Sans inclusion sélective, il serait plus difficile de gérer les détails des programmes de construction pour ces plates-formes.

Cependant, avoir une copie de la même bibliothèque dans chaque programme en cours d'exécution crée un fardeau pour un système qui exécute normalement de nombreux processus. Avec le bon type de système de mémoire virtuelle, les pages de mémoire qui ont un contenu identique ne doivent exister qu'une seule fois dans le système, mais peuvent être utilisées par de nombreux processus. Cela crée un avantage pour augmenter les chances que les pages contenant du code soient probablement identiques à certaines pages dans autant d'autres processus en cours que possible. Mais, si les programmes se lient statiquement à la bibliothèque d'exécution, chacun a un mélange différent de fonctions, chacune disposée dans ce qui traite la carte mémoire à différents endroits, et il n'y a pas beaucoup de pages de code partageables à moins que ce ne soit un programme qui est à lui seul courir dans plus que processus. L'idée d'une DLL a donc gagné un autre avantage majeur.

Une DLL pour une bibliothèque contient toutes ses fonctions, prêtes à être utilisées par n'importe quel programme client. Si de nombreux programmes chargent cette DLL, ils peuvent tous partager ses pages de codes. Tout le monde y gagne. (Eh bien, jusqu'à ce que vous mettiez à jour une DLL avec une nouvelle version, mais cela ne fait pas partie de cette histoire. Google DLL Hell pour ce côté de l'histoire.)

Donc, le premier grand choix à faire lors de la planification d'un nouveau projet est entre la liaison dynamique et statique. Avec la liaison statique, vous avez moins de fichiers à installer et vous êtes à l'abri des tiers mettant à jour une DLL que vous utilisez. Cependant, votre programme est plus volumineux et il n'est pas aussi bon citoyen de l'écosystème Windows. Avec la liaison dynamique, vous avez plus de fichiers à installer, vous pourriez avoir des problèmes avec un tiers mettant à jour une DLL que vous utilisez, mais vous êtes généralement plus convivial avec les autres processus du système.

Un gros avantage d'une DLL est qu'elle peut être chargée et utilisée sans recompiler ni même relier le programme principal. Cela peut permettre à un fournisseur de bibliothèque tiers (pensez à Microsoft et à l'environnement d'exécution C, par exemple) de corriger un bogue dans sa bibliothèque et de le distribuer. Une fois qu'un utilisateur final a installé la DLL mise à jour, il bénéficie immédiatement de cette correction de bogue dans tous les programmes qui utilisent cette DLL. (À moins qu'il ne casse les choses. Voir DLL Hell.)

L'autre avantage vient de la distinction entre chargement implicite et explicite. Si vous optez pour l'effort supplémentaire de chargement explicite, la DLL peut même ne pas avoir existé lorsque le programme a été écrit et publié. Cela permet des mécanismes d'extension qui peuvent découvrir et charger des plugins, par exemple.

RBerteig
la source
3
Supprimer mon message et voter pour cela, parce que vous expliquez les choses mieux que moi;) Bonne réponse.
ereOn
2
@RBerteig: Merci pour votre excellente réponse. Juste une petite correction, selon ici ( msdn.microsoft.com/en-us/library/9yd93633.aspx ), il existe 2 types de liaison dynamique vers une DLL, une liaison implicite au chargement et une liaison explicite au moment de l'exécution . Aucun lien à la compilation . Maintenant, je me demande quelle est la différence entre le lien statique traditionnel (lien vers un fichier * .lib qui contient l'implémentation complète) et le lien dynamique au moment du chargement vers une DLL (via une bibliothèque d'importation)?
smwikipedia
1
Continuer: Quels sont les avantages et les inconvénients de la liaison statique et de la liaison dynamique au moment du chargement ? Il semble que ces 2 approches chargent toutes les deux tous les fichiers nécessaires dans l'espace d'adressage au début d'un processus. Pourquoi en avons-nous besoin de 2? Merci.
smwikipedia
1
vous pouvez peut-être utiliser un outil comme "objdump" pour jeter un œil à l'intérieur d'un fichier .lib et déterminer s'il s'agit d'une bibliothèque d'importation ou d'une véritable bibliothèque statique. sous Linux lors de la compilation croisée vers une cible Windows, il est possible d'exécuter 'ar' ou 'nm' sur les fichiers .a (version mingw des fichiers .lib) et notez que les bibliothèques d'importation ont des noms de fichiers .o génériques et aucun code (juste une instruction 'jmp'), alors que les bibliothèques statiques ont beaucoup de fonctions et de code à l'intérieur.
don bright
1
Petite correction: Vous pouvez également lier implicitement au moment de l'exécution. La prise en charge de l'éditeur de liens pour les DLL à chargement différé explique cela en détail. Cela est utile si vous souhaitez modifier dynamiquement le chemin de recherche de la DLL ou gérer correctement l'échec de la résolution d'importation (pour prendre en charge les nouvelles fonctionnalités du système d'exploitation, mais toujours fonctionner sur des versions plus anciennes, par exemple).
IInspectable le
5

Ces fichiers de bibliothèque d'importation .LIB sont utilisés dans la propriété de projet suivante Linker->Input->Additional Dependencies, lors de la création d'un ensemble de dll nécessitant des informations supplémentaires au moment de la liaison, fournies par les fichiers .LIB de la bibliothèque d'importation. Dans l'exemple ci-dessous pour ne pas obtenir d'erreurs de l'éditeur de liens, je dois faire référence aux dll A, B, C et D via leurs fichiers lib. (notez que l'éditeur de liens pour trouver ces fichiers, vous devrez peut-être inclure leur chemin de déploiement dans le cas Linker->General->Additional Library Directoriescontraire, vous obtiendrez une erreur de construction indiquant que vous ne pourrez trouver aucun des fichiers lib fournis.)

Linker-> Input-> Dépendances supplémentaires

Si votre solution construit toutes les bibliothèques dynamiques, vous avez peut-être pu éviter cette spécification de dépendance explicite en vous appuyant à la place sur les indicateurs de référence exposés sous la Common Properties->Framework and Referencesboîte de dialogue. Ces indicateurs semblent faire automatiquement la liaison en votre nom en utilisant les fichiers * .lib. Cadre et références

Cependant, il s'agit comme il le dit de propriétés communes , qui ne sont pas spécifiques à la configuration ou à la plate-forme. Si vous avez besoin de prendre en charge un scénario de construction mixte comme dans notre application, nous avions une configuration de construction pour restituer une construction statique et une configuration spéciale qui a construit une construction contrainte d'un sous-ensemble d'assemblys qui ont été déployés en tant que bibliothèques dynamiques. J'avais utilisé les indicateurs Use Library Dependency Inputs et Link Library Dependenciesdéfinis sur true dans divers cas pour obtenir des éléments à construire et plus tard réaliser pour simplifier les choses, mais lors de l'introduction de mon code dans les versions statiques, j'ai introduit une tonne d'avertissements de l'éditeur de liens et la construction était incroyablement lente pour les versions statiques. J'ai fini par introduire un tas de ce genre d'avertissements ...

warning LNK4006: "bool __cdecl XXX::YYY() already defined in CoreLibrary.lib(JSource.obj); second definition ignored  D.lib(JSource.obj)

Et j'ai fini par utiliser la spécification manuelle de Additional Dependenciespour satisfaire l'éditeur de liens pour les constructions dynamiques tout en gardant les constructeurs statiques heureux en n'utilisant pas une propriété commune qui les ralentissait. Lorsque je déploie la version de sous-ensemble dynamique, je ne déploie que les fichiers dll car ces fichiers lib ne sont utilisés qu'au moment de la liaison, pas au moment de l'exécution.

jxramos
la source
3

Il existe trois types de bibliothèques: les bibliothèques statiques, partagées et chargées dynamiquement.

Les bibliothèques statiques sont liées au code lors de la phase de liaison, elles sont donc en fait dans l'exécutable, contrairement à la bibliothèque partagée, qui n'a que des stubs (symboles) à rechercher dans le fichier de bibliothèque partagée, qui est chargé au moment de l'exécution avant le la fonction principale est appelée.

Les bibliothèques chargées dynamiquement ressemblent beaucoup aux bibliothèques partagées, sauf qu'elles sont chargées quand et si le besoin se fait sentir par le code que vous avez écrit.

Zoltán Szőcs
la source
@Merci zacsek. Mais je ne suis pas sûr de votre déclaration concernant la bibliothèque partagée.
smwikipedia
@smwikipedia: Linux les a, je les utilise, donc ils existent certainement. Lisez aussi: en.wikipedia.org/wiki/Library_(computing)
Zoltán Szőcs
3
C'est une différence subtile. Les bibliothèques partagées et dynamiques sont toutes deux des fichiers DLL. La distinction est quand ils sont chargés. Les bibliothèques partagées sont chargées par le système d'exploitation avec le fichier EXE. Les bibliothèques dynamiques sont chargées par appel de code LoadLibrary()et les API associées.
RBerteig
J'ai lu dans [1] que DLL est l'implémentation par Microsoft du concept de bibliothèque partagée. [1]: en.wikipedia.org/wiki/Dynamic-link_library#Import_libraries
smwikipedia
Je ne suis pas d'accord sur le fait que c'est une différence subtile, du point de vue de la programmation, cela fait une énorme différence que la bibliothèque partagée soit chargée de manière dynamique ou non (si elle est chargée de manière dynamique, vous devez ajouter un code standard pour accéder aux fonctions).
Zoltán Szőcs