Ecriture de bibliothèques à chargement dynamique

De RAD Studio
Aller à : navigation, rechercher

Remonter à Bibliothèques et packages - Index


Remarque : Les bibliothèques sont beaucoup plus limitées que les packages dans leur contenu d'exportation. Les bibliothèques ne peuvent pas exporter des constantes, des types et des variables normales. Ainsi, les types de classes définis dans une bibliothèque ne seront pas vus dans un programme en utilisant cette bibliothèque.

Pour exporter des éléments autres que de simples procédures et fonctions, les packages représentent l'alternative recommandée. Les bibliothèques doivent seulement être considérées quand l'interopérabilité avec d'autres programmations est une exigence.


Les rubriques suivantes décrivent les éléments impliqués dans l'écriture de bibliothèques à chargement dynamique, notamment :

  • La clause exports.
  • Le code d'initialisation d'une bibliothèque.
  • Les variables globales.
  • Les bibliothèques et les variables système.

Utilisation de la clause d'exportation dans les bibliothèques

Le code source principal d'une bibliothèque à chargement dynamique est identique à celui d'un programme, mais il commence par le mot réservé library (au lieu de program).

Seule les routines qu'une bibliothèque exporte de façon explicite peuvent être importées par d'autres bibliothèques ou programmes. L'exemple suivant présente une bibliothèque avec deux fonctions exportées, Min et Max :

library MinMax;
function Min(X, Y: Integer): Integer; stdcall;
begin
  if X < Y then Min := X else Min := Y;
end;
function Max(X, Y: Integer): Integer; stdcall;
begin
  if X > Y then Max := X else Max := Y;
end;
exports
  Min,
  Max;
  begin
 end.

Si vous voulez utiliser votre bibliothèque dans des applications écrites dans d'autres langages, il est plus prudent de spécifier la directive stdcall dans les déclarations des fonctions exportées. Certains langages ne supportent pas la convention d'appel par défaut register de Delphi.

Il est possible de concevoir une bibliothèque à partir de plusieurs unités. Dans ce cas, le fichier source de la bibliothèque est souvent réduit à une clause uses, une clause exports et le code d'initialisation. Par exemple :

library Editors;
uses EdInit, EdInOut, EdFormat, EdPrint;
exports
  InitEditors,
  DoneEditors name Done,
  InsertText name Insert,
  DeleteSelection name Delete,
  FormatSelection,
  PrintSelection name Print,
    .
    .
    .
  SetErrorHandler;
 begin
   InitLibrary;
 end.

Vous pouvez placer des clauses exports dans la section interface ou implementation d'une unité. Toute bibliothèque dont la clause uses inclut une telle unité exporte automatiquement les routines listées dans les clauses exports de l'unité, sans qu'elle ait besoin d'avoir sa propre clause exports.


Une routine est exportée si elle est listée dans une clause exports, de la forme suivante :

exports entry1, ..., entryn;

où chaque entrée est composée du nom d'une procédure, fonction ou variable (qui doit être déclarée avant la clause exports) suivi par une liste de paramètres (seulement lors d'une exportation d'une routine surchargée) et un spécificateur facultatif name. Vous pouvez qualifier le nom de la procédure ou de la fonction avec le nom d'une unité.

Les entrées peuvent aussi inclure la directive resident qui, conservée dans un souci de compatibilité descendante, n'est pas prise en compte par le compilateur.

Sur la plate-forme Win32, un spécificateur index est constitué de la directive index suivie par une constante numérique comprise entre 1 et 2 147 483 647. Pour que les programmes soient plus efficaces, utilisez des valeurs d'index basses. Si une entrée n'a pas de spécificateur index, un numéro est automatiquement affecté à la routine dans la table d'exportation.

Remarque : L'utilisation de spécificateurs index, qui sont supportés uniquement pour des raisons de compatibilité descendante, est déconseillée et peut provoquer des problèmes pour d'autres outils de développement.

Un spécificateur name est constitué de la directive name suivie par une constante chaîne. Si une entrée n'a pas de spécificateur name, la routine est exportée sous son nom de déclaration original, avec la même orthographe et la même casse. Utilisez une clause name quand vous voulez exporter une routine sous un nom différent. Par exemple :

exports
DoSomethingABC name 'DoSomething';

Quand vous exportez une fonction ou une procédure surchargée depuis une bibliothèque à chargement dynamique, vous devez spécifier sa liste de paramètres dans la clause exports. Par exemple :

exports
Divide(X, Y: Integer) name 'Divide_Ints',
Divide(X, Y: Real) name 'Divide_Reals';

Sur Win32, n'incluez pas de spécificateurs index dans les entrées des routines surchargées.

Une clause exports peut apparaître n'importe où et à plusieurs reprises dans la partie déclaration d'un programme ou d'une bibliothèque, ou dans la section interface ou implementation d'une unité. Les programmes contiennent rarement une clause exports.

Code d'initialisation d'une bibliothèque

Les instructions placées dans le bloc d'une bibliothèque constituent le code d'initialisation de la bibliothèque. Ces instructions sont exécutées une seule fois lors du chargement de la bibliothèque. Elles effectuent généralement l'initialisation de variables ou l'enregistrement de classes Windows. Le code d'initialisation d'une bibliothèque peut aussi installer une procédure de point d'entrée en utilisant la variable DllProc. La variable DllProc est semblable à une procédure de sortie, comme décrit dans Procédures de sortie. La procédure de point d'entrée s'exécute quand la bibliothèque est chargée ou déchargée.

Le code d'initialisation d'une bibliothèque peut signaler une erreur en définissant la variable ExitCode sur une valeur non nulle. ExitCode, déclarée dans l'unité System, a la valeur par défaut zéro qui indique la réussite de l'initialisation. Si le code d'initialisation d'une bibliothèque définit ExitCode sur une valeur différente, la bibliothèque est déchargée et l'échec est notifié à l'application appelante. De même, si une exception non gérée se produit durant l'exécution du code d'initialisation, l'application appelante est notifiée de l'échec du chargement de la bibliothèque.

Voici un exemple d'une bibliothèque avec un code d'initialisation et une procédure de point d'entrée :

library Test;
var
  SaveDllProc: Pointer;
procedure LibExit(Reason: Integer);
begin
  if Reason = DLL_PROCESS_DETACH then
  begin
    .
    .   // library exit code
    .
  end;
  SaveDllProc(Reason);	// call saved entry point procedure
end;
begin
    .
    .	  // library initialization code
    .
  SaveDllProc := DllProc; // save exit procedure chain
  DllProc := @LibExit;	  // install LibExit exit procedure
end.

DllProc est appelée la première fois que la bibliothèque est chargée en mémoire, quand un thread démarre ou s'arrête ou quand la bibliothèque est déchargée. La partie initialisation de toutes les unités utilisées par une bibliothèque est exécutée avant le code d'initialisation de la bibliothèque ; la partie finalisation de ces unités est exécutée après la procédure de point d'entrée de la bibliothèque.

Variables globales d'une bibliothèque

Les variables globales déclarées dans une bibliothèque partagée ne peuvent être importées par une application Delphi.

Une bibliothèque peut être utilisée simultanément par plusieurs applications, mais chaque application possède une copie de la bibliothèque dans son propre espace de processus avec son propre jeu de variables globales. Pour que plusieurs bibliothèques, ou plusieurs instances d'une bibliothèque, partagent de la mémoire, elles doivent utiliser des fichiers mappés en mémoire. Pour de plus amples informations, voir votre documentation système.

Bibliothèques et variables système

Plusieurs variables déclarées dans l'unité System sont utiles pour ceux qui programment des bibliothèques. Utilisez IsLibrary pour déterminer si le code est exécuté dans une application ou dans une bibliothèque ; IsLibrary vaut toujours False dans une application et True dans une bibliothèque. Pendant la durée de vie d'une bibliothèque, HInstance contient son handle d'instance. CmdLine vaut toujours nil dans une bibliothèque.

La variable DLLProc permet à une bibliothèque de surveiller les appels effectués par le système d'exploitation au point d'entrée de la bibliothèque. Cette fonctionnalité n'est normalement utilisée que par les bibliothèques qui supportent le fonctionnement multithread. DLLProc est utilisée dans les applications multithreads. Vous devez utiliser les sections de finalisation, plutôt que les procédures de sortie, pour tout comportement de sortie.

Pour surveiller les appels du système d'exploitation, créez une procédure callback ne prenant qu'un seul paramètre entier, par exemple :

procedure DLLHandler(Reason: Integer);

et assignez l'adresse de la procédure à la variable DLLProc. Quand la procédure est appelée, le système lui transmet l'une des valeurs suivantes.

DLL_PROCESS_DETACH

Indique que la bibliothèque se détache de l'espace d'adresse du processus appelant, à la suite d'une sortie normale ou d'un appel à FreeLibrary.

DLL_PROCESS_ATTACH

Indique que la bibliothèque est attachée à l'espace d'adresse du processus appelant, à la suite d'un appel à LoadLibrary.

DLL_THREAD_ATTACH

Indique que le processus en cours est en train de créer un nouveau thread.

DLL_THREAD_DETACH

Indique la sortie normale d'un thread.


Dans le corps de la procédure, vous pouvez spécifier les actions à effectuer en fonction du paramètre transmis à la procédure.

Exceptions et erreurs d'exécution dans les bibliothèques

Quand une exception est déclenchée dans une bibliothèque à chargement dynamique sans être gérée, elle se propage depuis la bibliothèque vers le programme appelant. Si l'application ou la bibliothèque appelante est écrite en Delphi, l'exception peut être gérée dans une instruction try...except normale.

Sur Win32, si l'application appelante ou la bibliothèque est écrite dans un autre langage, l'exception peut être gérée par une exception du système d'exploitation de code d'exception $0EEDFADE. La première entrée du tableau ExceptionInformation de l'enregistrement de l'exception du système d'exploitation contient l'adresse de l'exception et la deuxième entrée contient une référence à l'objet exception de Delphi.

Généralement, vous ne devez pas laisser des exceptions s'échapper de votre bibliothèque. Les exceptions Delphi sont mappées sur le modèle des exceptions du système d'exploitation.

Si une bibliothèque n'utilise pas l'unité SysUtils, le support des exceptions est désactivé. Dans ce cas, quand une erreur d'exécution se produit dans la bibliothèque, l'application appelante se termine. Comme la bibliothèque ne peut pas savoir si elle a été appelée par un programme Delphi, elle ne peut pas invoquer les procédures de sortie de l'application ; l'application est simplement abandonnée et retirée de la mémoire.

Gestionnaire de mémoire partagée

Sur Win32, si une DLL exporte des routines qui transmettent des chaînes longues ou des tableaux dynamiques en tant que paramètres ou résultats de fonctions, que ce soit directement ou à l'intérieur d'enregistrements ou d'objets, la DLL et ses applications client (ou DLLs) doivent toutes utiliser l'unité ShareMem. Cela s'applique aussi si une application ou une DLL alloue avec New ou GetMem de la mémoire qui est désallouée par un appel à Dispose ou FreeMem dans un autre module. ShareMem doit toujours être la première unité listée dans la clause uses d'un programme ou d'une bibliothèque là où elle apparaît.

ShareMem est l'unité d'interface du gestionnaire de mémoire BORLANDMM.DLL qui permet à des modules de partager de la mémoire allouée dynamiquement. BORLANDMM.DLL doit être déployée avec les applications et les DLLs qui utilisent ShareMem. Quand une application ou une DLL utilise ShareMem, son gestionnaire de mémoire est remplacé par le gestionnaire de mémoire de BORLANDMM.DLL.

Voir aussi