DLLOCXDRVLes fichiers de données ayant le même format qu'une DLL, mais une extension différente et ne contenant éventuellement que des sections de ressources, peuvent être appelés DLL de ressources . Les bibliothèques d'icônes , ayant parfois l'extension `.dll` ICL, et les fichiers de polices , ayant les extensions `.dll` FONet FOT`.dll`, en sont des exemples .
GDI.EXE, qui constituait l'interface utilisateur USER.EXE. Ces couches supplémentaires, s'ajoutant à DOS, devaient être partagées par tous les programmes Windows en cours d'exécution, non seulement pour permettre à Windows de fonctionner sur une machine disposant de moins d'un mégaoctet de RAM, mais aussi pour permettre aux programmes de coopérer entre eux. Le code de GDI devait traduire les commandes de dessin en opérations sur des périphériques spécifiques. À l'écran, il devait manipuler les pixels dans la mémoire tampon d'image. Lors de l'impression, les appels d'API devaient être transformés en requêtes à l'imprimante. Bien qu'il aurait été possible d'intégrer directement la prise en charge d'un nombre limité de périphériques (comme l' écran Color Graphics Adapter ou le langage de commande de l'imprimante HP LaserJet ), Microsoft a opté pour une approche différente. GDI fonctionne en chargeant différents fragments de code, appelés « pilotes de périphériques », pour gérer les différents périphériques de sortie.Le même concept architectural qui permettait à GDI de charger différents pilotes de périphériques permettait également à l' interface Windows de charger différents programmes Windows et à ces programmes d'effectuer des appels d'API depuis les bibliothèques partagées USER et GDI. Ce concept était celui de la « liaison dynamique ».
Dans une bibliothèque statique classique non partagée , les sections de code sont simplement ajoutées au programme appelant lors de la compilation de son exécutable, à l'étape de liaison. Si deux programmes appellent la même routine, celle-ci est incluse dans les deux programmes lors de leur liaison. Avec la liaison dynamique, le code partagé est placé dans un fichier unique et distinct. Les programmes qui appellent ce fichier s'y connectent à l'exécution, le système d'exploitation (ou, dans le cas des anciennes versions de Windows, son extension) effectuant la liaison.
Pour les premières versions de Windows (1.0 à 3.11), les DLL constituaient la base de l'interface graphique. Ainsi, les pilotes d'affichage étaient de simples DLL avec l'extension .DRV, fournissant des implémentations personnalisées de la même API de dessin via une interface de pilote de périphérique unifiée (DDI). Les API de dessin (GDI) et d'interface graphique (USER) correspondaient quant à elles aux appels de fonction exportés par les DLL système GDI et USER, avec l'extension .EXE.
This notion of building up the operating system from a collection of dynamically loaded libraries is a core concept of Windows that persists shared libraries, such as modularity. Modularity allows changes to be made to code and data in a single self-contained DLL shared by several applications without any change to the applications themselves.
Another benefit of modularity is the use of generic interfaces for plug-ins. A single interface may be developed which allows old as well as new modules to be integrated seamlessly at run-time into pre-existing applications, without any modification to the application itself. This concept of dynamic extensibility is taken to the extreme with the Component Object Model, the underpinnings of ActiveX.
In Windows 1.x, 2.x and 3.x, all Windows applications shared the same address space as well as the same memory. A DLL was only loaded once into this address space; from then on, all programs using the library accessed it. The library's data was shared across all the programs. This could be used as an indirect form of inter-process communication, or it could accidentally corrupt the different programs. With the introduction of 32-bit libraries in Windows 95, every process ran in its own address space. While the DLL code may be shared, the data is private except where shared data is explicitly requested by the library. That said, large swathes of Windows 95, Windows 98 and Windows Me were built from 16-bit libraries, which limited the performance of the Pentium Pro microprocessor when launched, and ultimately limited the stability and scalability of the DOS-based versions of Windows.
Although DLLs are the core of the Windows architecture, they have several drawbacks, collectively called "DLL hell"..NET Framework as one solution to the problems of DLL hell, although they now promote virtualization-based solutions such as Microsoft Virtual PC and Microsoft Application Virtualization, because they offer superior isolation between applications. An alternative mitigating solution to DLL hell has been to implement side-by-side assembly.
Features
Since DLLs are essentially the same as EXEs, the choice of which to produce as part of the linking process is for clarity, since it is possible to export functions and data from either.
Il n'est pas possible d'exécuter directement une DLL, car elle nécessite un EXE pour que le système d'exploitation la charge via un point d'entrée . D'où l'existence d'utilitaires comme RUNDLL.EXE ou RUNDLL32.EXE qui fournissent le point d'entrée et le cadre minimal pour les DLL contenant suffisamment de fonctionnalités pour s'exécuter sans beaucoup de support.
Les DLL offrent un mécanisme de partage de code et de données, permettant ainsi aux développeurs de mettre à jour les fonctionnalités de leurs applications sans avoir à les relier ni à les recompiler. Du point de vue du développement applicatif, Windows et OS/2 peuvent être considérés comme un ensemble de DLL mises à jour, permettant aux applications d'une version du système d'exploitation de fonctionner sur une version ultérieure, à condition que l'éditeur du système d'exploitation ait garanti la compatibilité des interfaces et des fonctionnalités.
Les DLL s'exécutent dans l'espace mémoire du processus appelant et avec les mêmes autorisations d'accès, ce qui signifie qu'il y a peu de surcharge lors de leur utilisation, mais aussi qu'il n'y a aucune protection pour le programme appelant si la DLL présente un quelconque bogue.
Gestion de la mémoire
Dans l'API Windows , les fichiers DLL sont organisés en sections . Chaque section possède son propre ensemble d'attributs, tels que la possibilité d'écrire ou de lire uniquement, le caractère exécutable (pour le code) ou non exécutable (pour les données), etc.
Le code d'une DLL est généralement partagé entre tous les processus qui l'utilisent ; autrement dit, il occupe un seul emplacement en mémoire physique et n'utilise pas d'espace dans le fichier d'échange . Windows n'utilise pas de code indépendant de la position pour ses DLL ; au lieu de cela, le code est relocalisé lors de son chargement, fixant les adresses de tous ses points d'entrée à des emplacements libres dans l'espace mémoire du premier processus chargeant la DLL. Dans les anciennes versions de Windows, où tous les processus en cours d'exécution occupaient un espace d'adressage commun, une seule copie du code de la DLL suffisait pour tous les processus. Cependant, dans les versions plus récentes de Windows, qui utilisent des espaces d'adressage distincts pour chaque programme, il est possible d'utiliser la même copie relocalisée de la DLL dans plusieurs programmes uniquement si chaque programme dispose des mêmes adresses virtuelles libres pour accueillir le code de la DLL. Si certains programmes (ou leurs combinaisons de DLL déjà chargées) ne disposent pas de ces adresses libres, une copie physique supplémentaire du code de la DLL devra être créée, utilisant un ensemble différent de points d'entrée relocalisés. Si la mémoire physique occupée par une section de code doit être récupérée, son contenu est supprimé, puis rechargé ultérieurement directement à partir du fichier DLL si nécessaire.
Contrairement aux sections de code, les sections de données d'une DLL sont généralement privées ; autrement dit, chaque processus utilisant la DLL possède sa propre copie de toutes ses données. Il est possible, en option, de rendre les sections de données partagées, autorisant ainsi la communication interprocessus via cette zone de mémoire partagée. Cependant, comme les restrictions utilisateur ne s'appliquent pas à l'utilisation de la mémoire partagée d'une DLL, cela crée une faille de sécurité : un processus peut corrompre les données partagées, ce qui risque d'entraîner un comportement indésirable pour tous les autres processus y ayant accès. Par exemple, un processus exécuté sous un compte invité peut ainsi corrompre un autre processus exécuté sous un compte privilégié. C'est une raison importante d'éviter l'utilisation de sections partagées dans les DLL.
Si une DLL est compressée par certains outils de compression d'exécutables (comme UPX ), toutes ses sections de code sont marquées comme accessibles en lecture et en écriture et ne seront pas partagées. Les sections de code accessibles en lecture et en écriture, tout comme les sections de données privées, sont propres à chaque processus. Par conséquent, les DLL comportant des sections de données partagées ne doivent pas être compressées si elles sont destinées à être utilisées simultanément par plusieurs programmes, car chaque instance de programme devrait alors héberger sa propre copie de la DLL, ce qui entraînerait une augmentation de la consommation de mémoire.
Bibliothèques d'importation
À l'instar des bibliothèques statiques, les bibliothèques d'importation pour les DLL sont identifiées par leur .libextension de fichier. Par exemple, kernel32.dll , la bibliothèque dynamique principale pour les fonctions de base de Windows telles que la création de fichiers et la gestion de la mémoire, est liée via .dll kernel32.lib. La méthode habituelle pour distinguer une bibliothèque d'importation d'une bibliothèque statique est la taille : la bibliothèque d'importation est beaucoup plus petite car elle ne contient que des symboles faisant référence à la DLL proprement dite, qui seront traités lors de l'édition de liens. Toutes deux sont néanmoins des fichiers au format Unix .ar .
La liaison aux bibliothèques dynamiques est généralement gérée par l'ajout d'une bibliothèque d'importation lors de la compilation ou de la liaison d'un fichier exécutable. L'exécutable ainsi créé contient une table d'adresses d'importation (IAT) référençant tous les appels de fonctions DLL (chaque fonction DLL référencée possède sa propre entrée dans l'IAT). À l'exécution, l'IAT est remplie avec les adresses appropriées pointant directement vers une fonction de la DLL chargée séparément.
Dans Cygwin/MSYS et MinGW, les bibliothèques d'importation sont généralement désignées par le suffixe `.img` .dll.a, combinant le suffixe DLL de Windows et le suffixe `.ar` d'Unix. Le format de fichier est similaire, mais les symboles utilisés pour marquer les importations diffèrent (` _head_foo_dll.img` vs `.img` __IMPORT_DESCRIPTOR_foo). Bien que la chaîne d'outils GNU Binutils puisse générer des bibliothèques d'importation et s'y lier, il est plus rapide de se lier directement à la DLL. Un outil expérimental de MinGW, appelé `genlib`, permet de générer des bibliothèques d'importation avec des symboles de style MSVC.
Résolution et liaison des symboles
Chaque fonction exportée par une DLL est identifiée par un numéro d'ordre et, éventuellement, par un nom. De même, les fonctions peuvent être importées d'une DLL soit par leur numéro d'ordre, soit par leur nom. Le numéro d'ordre représente la position du pointeur d'adresse de la fonction dans la table des adresses d'exportation de la DLL. Il est courant que les fonctions internes soient exportées uniquement par leur numéro d'ordre. Pour la plupart des fonctions de l'API Windows, seuls les noms sont conservés d'une version de Windows à l'autre ; les numéros d'ordre sont susceptibles de changer. Par conséquent, il est impossible d'importer de manière fiable les fonctions de l'API Windows par leur numéro d'ordre.
L'importation de fonctions par numéro d'ordre n'offre qu'une performance légèrement supérieure à celle de leur importation par nom : les tables d'exportation des DLL étant triées par nom, une recherche dichotomique permet de trouver une fonction. L'index du nom trouvé sert ensuite à rechercher son numéro d'ordre dans la table des numéros d'ordre d'exportation. Sous Windows 16 bits, cette table n'étant pas triée, la surcharge liée à la recherche de nom était beaucoup plus perceptible.
Il est également possible de lier un exécutable à une version spécifique d'une DLL, c'est-à-dire de résoudre les adresses des fonctions importées lors de la compilation. Pour les importations liées, l' éditeur de liens enregistre l'horodatage et la somme de contrôle de la DLL à laquelle l'importation est liée. À l'exécution, Windows vérifie si la même version de la bibliothèque est utilisée et, le cas échéant, ignore le traitement des importations. Sinon, si la bibliothèque est différente de celle à laquelle l'importation a été liée, Windows traite les importations normalement.
Les exécutables liés se chargent légèrement plus rapidement s'ils sont exécutés dans l'environnement pour lequel ils ont été compilés, et exactement au même moment s'ils sont exécutés dans un environnement différent. Lier les importations ne présente donc aucun inconvénient. Par exemple, toutes les applications Windows standard sont liées aux DLL système de leur version respective de Windows. L'installation d'une application est une excellente occasion de lier ses importations à son environnement cible. Cela permet de maintenir les bibliothèques « liées » jusqu'à la prochaine mise à jour du système d'exploitation. Cependant, cette opération modifie la somme de contrôle de l'exécutable ; elle est donc incompatible avec les programmes signés ou ceux gérés par un outil de gestion de configuration utilisant des sommes de contrôle (comme MD5 ) pour gérer les versions de fichiers. Les versions récentes de Windows ayant abandonné l'utilisation d'adresses fixes pour chaque bibliothèque chargée (pour des raisons de sécurité), l'intérêt et la pertinence de lier un exécutable diminuent.
Liaison explicite à l'exécution
Les fichiers DLL peuvent être chargés explicitement à l'exécution, un processus que Microsoft appelle simplement liaison dynamique à l'exécution , grâce à la fonction API `load` LoadLibrary(ou `load` ). Cette fonction API permet de rechercher les symboles exportés par leur nom et de décharger la DLL. Ces fonctions sont analogues à `load`, `load` et `load` dans l' API standard POSIX .LoadLibraryExGetProcAddressFreeLibrarydlopendlsymdlclose
La procédure de liaison explicite à l'exécution est la même dans tous les langages qui prennent en charge les pointeurs vers des fonctions , car elle dépend de l' API Windows plutôt que des constructions du langage.
Chargement retardé
Normalement, une application liée à une bibliothèque d'importation DLL ne démarrera pas si la DLL est introuvable, car Windows n'exécute pas l'application tant qu'il n'a pas trouvé toutes les DLL nécessaires. Cependant, une application peut être liée à une bibliothèque d'importation pour permettre le chargement différé de la bibliothèque dynamique. Dans ce cas, le système d'exploitation ne tentera pas de trouver ni de charger la DLL au démarrage de l'application ; à la place, un stub est inclus dans l'application par l'éditeur de liens, qui tentera de trouver et de charger la DLL lors de LoadLibraryl' GetProcAddressappel d'une de ses fonctions. Si la DLL est introuvable ou indisponible, ou si la fonction appelée n'existe pas, l'application générera une exception , qui pourra être interceptée et gérée de manière appropriée. Si l'application ne gère pas l'exception, elle sera interceptée par le système d'exploitation, qui interrompra le programme en affichant un message d'erreur.
Le mécanisme de chargement différé fournit également des points d'entrée de notification , permettant à l'application d'effectuer un traitement supplémentaire ou une gestion des erreurs lorsque la DLL est chargée et/ou lorsqu'une fonction de la DLL est appelée.
Considérations relatives au compilateur et au langage
Delphes
Dans un fichier source, le mot-clé libraryest utilisé à la place de program. À la fin du fichier, les fonctions à exporter sont listées dans exportsla clause.
Delphi n'a pas besoin LIBde fichiers pour importer des fonctions à partir de DLL ; pour lier une DLL, le externalmot-clé est utilisé dans la déclaration de la fonction pour indiquer le nom de la DLL, suivi de namepour nommer le symbole (s'il est différent) ou indexpour identifier l'index.
Microsoft Visual Basic
En Visual Basic (VB), seule la liaison à l'exécution est prise en charge ; mais en plus de l'utilisation LoadLibrarydes GetProcAddressfonctions API, les déclarations de fonctions importées sont autorisées.
Lors de l'importation de fonctions DLL via des déclarations, VB génère une erreur d'exécution si le DLLfichier est introuvable. Le développeur peut alors intercepter cette erreur et la gérer en conséquence.
Lors de la création de DLL en VB, l'EDI n'autorise que la création de DLL ActiveX. Cependant, des méthodes ont été créées pour permettre à l'utilisateur d'indiquer explicitement à l'éditeur de liens d'inclure un fichier .DEF définissant la position et le nom de chaque fonction exportée. Ceci permet à l'utilisateur de créer une DLL Windows standard en Visual Basic (version 6 ou antérieure) référençable via une instruction « Declare ».
C et C++
Microsoft Visual C++ (MSVC) fournit plusieurs extensions au C++ standard permettant de spécifier des fonctions comme importées ou exportées directement dans le code C++. Ces extensions ont été adoptées par d'autres compilateurs C et C++ pour Windows, notamment les versions Windows de GCC . Elles utilisent l'attribut `import` __declspecavant la déclaration d'une fonction. Il est à noter que lorsque des fonctions C sont accédées depuis du C++, elles doivent également être déclarées comme telles extern "C"dans le code C++, afin d'indiquer au compilateur que la liaison C doit être utilisée.
Outre la spécification des fonctions importées ou exportées via __declspecdes attributs, celles-ci peuvent être listées dans les sections IMPORT ou EXPORTS du Component Object ModelDLL ), le fichier `.dll` doit être placé dans l'un des répertoires listés dans la variable d'environnement PATH, dans le répertoire système par défaut ou dans le même répertoire que le programme qui l'utilise. Les DLL de serveur COM sont enregistrées à l'aide de `regsvr32.exe`, qui place l'emplacement de la DLL et son identifiant unique global ( GUID ) dans le registre. Les programmes peuvent ensuite utiliser la DLL en recherchant son GUID dans le registre pour trouver son emplacement ou créer indirectement une instance de l'objet COM à l'aide de son identificateur de classe et de son identificateur d'interface.
Exemples de programmation
Utilisation des importations DLL
Les exemples suivants montrent comment utiliser des liaisons spécifiques au langage pour importer des symboles en vue de la liaison avec une DLL au moment de la compilation.
Delphes
Utilisation de la liaison explicite à l'exécution
Les exemples suivants montrent comment utiliser les fonctionnalités de chargement et de liaison à l'exécution à l'aide de liaisons d'API Windows spécifiques au langage.
Notez que les quatre exemples sont vulnérables aux attaques par préchargement de DLL , car example.dll peut être résolu vers un emplacement non prévu par l'auteur (le répertoire de travail courant est prioritaire sur les emplacements des bibliothèques système), et donc vers une version malveillante de la bibliothèque. Consultez la documentation Microsoft relative au chargement sécurisé des bibliothèques : il est recommandé d'utiliser ` _ ( ByVal a As Double , ByVal b As Double ) As DoubleSubMain()DimResultAsDoubleResult=AddNumbers(1,2)Debug.Print"The result was: "&ResultEndSub
Delphi
Python
Le module de liaison ctypes de Python utilisera l'API POSIX sur les systèmes POSIX.
Modèle d'objet de composant
Le modèle COM (Component Object Model ) définit une norme binaire pour héberger l'implémentation d' objets dans des fichiers DLL et EXE. Il fournit des mécanismes pour localiser et versionner ces fichiers, ainsi qu'une description de l'interface indépendante du langage et lisible par machine. L'hébergement d'objets COM dans une DLL est plus léger et leur permet de partager des ressources avec le processus client. Cela permet aux objets COM d'implémenter des back-ends puissants pour des front-ends GUI simples tels que Visual Basic et ASP. Ils peuvent également être programmés à partir de langages de script.
Détournement de DLL
En raison d'une vulnérabilité communément appelée détournement de DLL, usurpation de DLL, préchargement de DLL ou implantation de binaire, de nombreux programmes chargent et exécutent une DLL malveillante située dans le même dossier qu'un fichier de données ouvert par ces programmes. Cette vulnérabilité a été découverte par Georgi Guninski en 2000. En août 2010, elle a fait l'objet d'une médiatisation mondiale après sa redécouverte par ACROS Security, révélant la vulnérabilité de plusieurs centaines de programmes. Les programmes exécutés depuis des emplacements non sécurisés, c'est-à-dire des dossiers accessibles en écriture par l'utilisateur tels que le dossier Téléchargements ou le répertoire Temp , sont presque toujours sensibles à cette vulnérabilité.