Article de reference

Chargement dynamique

Le chargement dynamique est un mécanisme par lequel un programme informatique peut, au moment de l'exécution , charger une bibliothèque (ou autre binaire ) en mémoire, récupérer...

Le chargement dynamique est un mécanisme par lequel un programme informatique peut, au moment de l'exécution , charger une bibliothèque (ou autre binaire ) en mémoire, récupérer les adresses des fonctions et des variables contenues dans la bibliothèque, exécuter ces fonctions ou accéder à ces variables et décharger la bibliothèque de la mémoire. C'est l'un des trois mécanismes par lesquels un programme informatique peut utiliser un autre logiciel au sein du programme ; les autres sont la liaison statique et la liaison dynamique . Contrairement à la liaison statique et à la liaison dynamique, le chargement dynamique permet à un programme informatique de démarrer en l'absence de ces bibliothèques, de découvrir les bibliothèques disponibles et d'obtenir potentiellement des fonctionnalités supplémentaires.

Histoire

Le chargement dynamique était une technique courante pour les systèmes d'exploitation IBM pour System/360 tels que OS/360 , en particulier pour les sous-routines d'E/S et pour les bibliothèques d'exécution COBOL et PL/I , et continue d'être utilisé dans les systèmes d'exploitation IBM pour z/Architecture , tels que z/OS . En ce qui concerne le programmeur d'application, le chargement est en grande partie transparent, car il est principalement géré par le système d'exploitation (ou son sous-système d'E/S). Les principaux avantages sont les suivants :

  • Les correctifs ( patchs ) apportés aux sous-systèmes ont corrigé tous les programmes à la fois, sans qu'il soit nécessaire de les relier
  • Les bibliothèques pourraient être protégées contre les modifications non autorisées

Le système de traitement des transactions stratégiques d' IBM , CICS (à partir des années 1970), utilise largement le chargement dynamique, à la fois pour son noyau et pour le chargement normal des programmes d'application . Les corrections apportées aux programmes d'application peuvent être effectuées hors ligne et de nouvelles copies des programmes modifiés peuvent être chargées dynamiquement sans avoir à redémarrer CICS 7 jours sur 7 , et le fait souvent ).

Les bibliothèques partagées ont été ajoutées à Unix dans les années 1980, mais initialement sans la possibilité de permettre à un programme de charger des bibliothèques supplémentaires après le démarrage.

Utilisations

Le chargement dynamique est le plus souvent utilisé dans la mise en œuvre de plug-ins logiciels . Par exemple, les fichiers de plug-in « objet partagé dynamique » du serveur Web Apache sont des bibliothèques qui sont chargées au moment de l'exécution avec un chargement dynamique. Le chargement dynamique est également utilisé dans la mise en œuvre de programmes informatiques où plusieurs bibliothèques différentes peuvent fournir les fonctionnalités requises et où l'utilisateur a la possibilité de sélectionner la ou les bibliothèques à fournir. *.dso

En C/C++

Tous les systèmes ne prennent pas en charge le chargement dynamique. Les systèmes d'exploitation de type Unix tels que macOS , Linux et Solaris fournissent un chargement dynamique avec la bibliothèque de langage de programmation C « dl ». Le système d'exploitation Windows fournit un chargement dynamique via l' API Windows .

Résumé

Chargement de la bibliothèque

Le chargement de la bibliothèque s'effectue avec LoadLibraryou LoadLibraryExsur Windows et avec dlopensur les systèmes d'exploitation de type Unix . Voici quelques exemples :

La plupart des systèmes d'exploitation de type Unix (Solaris, Linux, *BSD, etc.)

void * sdl_library = dlopen ( "libSDL.so" , RTLD_LAZY ); if ( sdl_library == NULL ) { // signaler une erreur ... } else { // utiliser le résultat dans un appel à dlsym }

macOS

En tant que bibliothèque Unix :

void * sdl_library = dlopen ( "libSDL.dylib" , RTLD_LAZY ); if ( sdl_library == NULL ) { // signaler une erreur ... } else { // utiliser le résultat dans un appel à dlsym }

En tant que framework macOS :

void * sdl_library = dlopen ( "/Library/Frameworks/SDL.framework/SDL" , RTLD_LAZY ); if ( sdl_library == NULL ) { // signaler une erreur ... } else { // utiliser le résultat dans un appel à dlsym }

Ou si le framework ou le bundle contient du code Objective-C :

NSBundle * bundle = [ NSBundle bundleWithPath : @"/Library/Plugins/Plugin.bundle" ]; NSError * err = nil ; if ([ bundle loadAndReturnError :& err ]) { // Utiliser les classes et les fonctions du bundle. } else { // Gérer les erreurs. }

Fenêtres

HMODULE sdl_library = LoadLibrary ( TEXT ( "SDL.dll" )); if ( sdl_library == NULL ) { // signaler une erreur ... } else { // utiliser le résultat dans un appel à GetProcAddress }

Extraction du contenu de la bibliothèque

L'extraction du contenu d'une bibliothèque chargée dynamiquement est réalisée avec GetProcAddresssur Windows et avec dlsymsur les systèmes d'exploitation de type Unix .

Systèmes d'exploitation de type Unix (Solaris, Linux, *BSD, macOS, etc.)

void * initializer = dlsym ( sdl_library , "SDL_Init" ); if ( initializer == NULL ) { // signaler une erreur ... } else { // convertir l'initialiseur en son type approprié et l'utiliser }

Sous macOS, lors de l'utilisation de bundles Objective-C, on peut également :

Class rootClass = [ bundle principalClass ]; // Alternativement, NSClassFromString() peut être utilisé pour obtenir une classe par nom. if ( rootClass ) { id object = [[ rootClass alloc ] init ]; // Utiliser l'objet. } else { // Signaler une erreur. }

Fenêtres

Initialiseur FARPROC = GetProcAddress ( sdl_library , "SDL_Init" ); if ( initialiseur == NULL ) { // signaler une erreur ... } else { // convertir l'initialiseur en son type approprié et l'utiliser }

Conversion d'un pointeur de fonction de bibliothèque

Le résultat de dlsym()ou GetProcAddress()doit être converti en un pointeur du type approprié avant de pouvoir être utilisé.

Fenêtres

Sous Windows, la conversion est simple, puisque FARPROC est déjà essentiellement un pointeur de fonction :

typedef INT_PTR ( * FARPROC )( void );

Cela peut être problématique lorsque l'adresse d'un objet doit être récupérée plutôt que celle d'une fonction. Cependant, on souhaite généralement extraire des fonctions de toute façon, donc ce n'est normalement pas un problème.

typedef void ( * sdl_init_function_type )( void ); sdl_init_function_type init_func = ( sdl_init_function_type ) initialiseur ;

Unix (POSIX)

Selon la spécification POSIX, le résultat de dlsym()est un voidpointeur. Cependant, il n'est pas nécessaire qu'un pointeur de fonction ait la même taille qu'un pointeur d'objet de données. Par conséquent, une conversion valide entre un type void*et un pointeur vers une fonction peut ne pas être facile à implémenter sur toutes les plateformes.

Sur la plupart des systèmes utilisés aujourd'hui, les pointeurs de fonction et d'objet sont convertibles de facto . L'extrait de code suivant montre une solution de contournement qui permet d'effectuer la conversion de toute façon sur de nombreux systèmes :

typedef void ( * sdl_init_function_type )( void ); sdl_init_function_type init_func = ( sdl_init_function_type ) initialiseur ;

L'extrait ci-dessus donnera un avertissement sur certains compilateurs : warning: dereferencing type-punned pointer will break strict-aliasing rules. Une autre solution de contournement est :

typedef void ( * sdl_init_function_type )( void ); union { sdl_init_function_type func ; void * obj ; } alias ; alias . obj = initialiseur ; sdl_init_function_type init_func = alias . func ;

qui désactive l'avertissement même si l'aliasing strict est en vigueur. Cela exploite le fait que la lecture à partir d'un membre d'union différent de celui sur lequel l'écriture a été effectuée le plus récemment (appelé « type punning ») est courante et explicitement autorisée même si l'aliasing strict est en vigueur, à condition que la mémoire soit accessible directement via le type d'union. Cependant, ce n'est pas strictement le cas ici, puisque le pointeur de fonction est copié pour être utilisé en dehors de l'union. Notez que cette astuce peut ne pas fonctionner sur les plateformes où la taille des pointeurs de données et la taille des pointeurs de fonction ne sont pas les mêmes.

Résolution du problème du pointeur de fonction sur les systèmes POSIX

Il n'en demeure pas moins que toute conversion entre des pointeurs de fonction et d'objet de données doit être considérée comme une extension d'implémentation (intrinsèquement non portable), et qu'il n'existe aucune méthode « correcte » pour une conversion directe, car à cet égard, les normes POSIX et ISO se contredisent.

En raison de ce problème, la documentation POSIX de dlsym()la version obsolète 6 indiquait qu'« une future version pourrait soit ajouter une nouvelle fonction pour renvoyer des pointeurs de fonction, soit l'interface actuelle pourrait être déconseillée au profit de deux nouvelles fonctions : l'une qui renvoie des pointeurs de données et l'autre qui renvoie des pointeurs de fonction ».

Pour la version ultérieure de la norme (édition 7, 2008), le problème a été discuté et la conclusion a été que les pointeurs de fonction doivent être convertibles void*pour la conformité POSIX. Cela nécessite que les créateurs de compilateurs implémentent un cast de travail pour ce cas.

Si le contenu de la bibliothèque peut être modifié (c'est-à-dire dans le cas d'une bibliothèque personnalisée), en plus de la fonction elle-même, un pointeur vers celle-ci peut être exporté. Étant donné qu'un pointeur vers un pointeur de fonction est lui-même un pointeur d'objet, ce pointeur peut toujours être récupéré légalement par appel à dlsym()et conversion ultérieure. Cependant, cette approche nécessite de conserver des pointeurs distincts vers toutes les fonctions qui doivent être utilisées en externe, et les avantages sont généralement minimes.

Déchargement de la bibliothèque

Le chargement d'une bibliothèque entraîne l'allocation de mémoire ; la bibliothèque doit être désallouée afin d'éviter une fuite de mémoire . De plus, l'échec du déchargement d'une bibliothèque peut empêcher les opérations du système de fichiers sur le fichier qui contient la bibliothèque. Le déchargement de la bibliothèque est effectué avec FreeLibrarysous Windows et avec sur les systèmes d'exploitationdlclose de type Unix . Cependant, le déchargement d'une DLL peut entraîner des plantages du programme si les objets de l'application principale font référence à la mémoire allouée dans la DLL. Par exemple, si une DLL introduit une nouvelle classe et que la DLL est fermée, d'autres opérations sur les instances de cette classe à partir de l'application principale entraîneront probablement une violation d'accès à la mémoire. De même, si la DLL introduit une fonction d'usine pour instancier des classes chargées dynamiquement, l'appel ou le déréférencement de cette fonction après la fermeture de la DLL entraîne un comportement indéfini.

Systèmes d'exploitation de type Unix (Solaris, Linux, *BSD, macOS, etc.)

dlclose ( bibliothèque_sdl );

Fenêtres

Bibliothèque libre ( sdl_library );

Bibliothèque spécialisée

Les implémentations du chargement dynamique sur les systèmes d'exploitation de type Unix et Windows permettent aux programmeurs d'extraire des symboles du processus en cours d'exécution.

Les systèmes d'exploitation de type Unix permettent aux programmeurs d'accéder à la table des symboles globale, qui comprend à la fois l'exécutable principal et les bibliothèques dynamiques chargées ultérieurement.

Windows permet aux programmeurs d'accéder aux symboles exportés par l'exécutable principal. Windows n'utilise pas de table de symboles globale et ne dispose pas d'API pour rechercher dans plusieurs modules un symbole par son nom.

Systèmes d'exploitation de type Unix (Solaris, Linux, *BSD, macOS, etc.)

void * this_process = dlopen ( NULL , 0 );

Fenêtres

HMODULE ce_processus = GetModuleHandle ( NULL );
HMODULE ce_processus_à_nouveau ; GetModuleHandleEx ( 0 , 0 , & ce_processus_à_nouveau );

En Java

Dans le langage de programmation Java , les classes peuvent être chargées dynamiquement à l'aide de l' ClassLoaderobjet. Par exemple :

Type de classe = ClassLoader . getSystemClassLoader (). loadClass ( nom ); Objet obj = type . newInstance ();

Le mécanisme de réflexion fournit également un moyen de charger une classe si elle n'est pas déjà chargée. Il utilise le chargeur de classe de la classe actuelle :

Type de classe = Classe . forName ( nom ); Objet obj = type . newInstance ();

Cependant, il n'existe pas de moyen simple de décharger une classe de manière contrôlée. Les classes chargées ne peuvent être déchargées de manière contrôlée, c'est-à-dire lorsque le programmeur le souhaite, que si le chargeur de classe utilisé pour charger la classe n'est pas le chargeur de classe système et est lui-même déchargé. Ce faisant, divers détails doivent être respectés pour s'assurer que la classe est réellement déchargée. Cela rend le déchargement des classes fastidieux.

Le déchargement implicite des classes, c'est-à-dire de manière incontrôlée par le garbage collector, a changé plusieurs fois en Java. Jusqu'à Java 1.2, le garbage collector pouvait décharger une classe chaque fois qu'il estimait avoir besoin d'espace, indépendamment du chargeur de classe utilisé pour charger la classe. À partir de Java 1.2, les classes chargées via le chargeur de classe système n'étaient jamais déchargées et les classes chargées via d'autres chargeurs de classe uniquement lorsque cet autre chargeur de classe était déchargé. À partir de Java 6, les classes peuvent contenir un marqueur interne indiquant au garbage collector qu'elles peuvent être déchargées si le garbage collector le souhaite, indépendamment du chargeur de classe utilisé pour charger la classe. Le garbage collector est libre d'ignorer cette indication.

De même, les bibliothèques implémentant des méthodes natives sont chargées dynamiquement à l'aide de la System.loadLibraryméthode. Il n'y a pas System.unloadLibraryde méthode.

Plateformes sans chargement dynamique

Malgré sa promulgation dans les années 1980 par Unix et Windows, certains systèmes ont toujours choisi de ne pas ajouter, voire de supprimer, le chargement dynamique. Par exemple, Plan 9 de Bell Labs et son successeur 9front évitent intentionnellement la liaison dynamique, car ils la considèrent comme « nuisible ». Le langage de programmation Go , développé par certains des mêmes développeurs que Plan 9, ne prenait pas non plus en charge la liaison dynamique, mais le chargement de plug-ins est disponible depuis Go 1.8 (février 2017). L'environnement d'exécution Go et toutes les fonctions de la bibliothèque sont liés statiquement dans le binaire compilé.

Plus d articles de Worldlex Wiki

Revenez a l index pour explorer davantage de pages sur l histoire, la science, la culture, la geographie et la societe en francais.

Explorer l index