LA CLOCHE

Il y a ceux qui ont lu cette nouvelle avant vous.
Abonnez-vous pour recevoir les derniers articles.
E-mail
Nom
Nom de famille
Comment voulez-vous lire La cloche
Pas de spam

Comme vous le savez probablement, les bibliothèques de liens dynamiques (DLL) utilisent les conventions du langage C lors de la déclaration des objets exportés, tandis que C ++ utilise un système de génération de noms de compilation légèrement différent, vous ne pouvez donc pas simplement exporter des fonctions - méthodes de la classe C ++ puis les utiliser dans le code de l'application cliente (ci-après, le client désigne l'application utilisant la DLL). Cependant, cela peut être fait en utilisant les interfaces disponibles à la fois pour la DLL et l'application cliente. Cette méthode est à la fois très puissante et élégante. le client ne voit que l'interface abstraite, et la classe réelle qui implémente toutes les fonctions peut être n'importe quoi. La technologie COM de Microsoft (Component Modèle d'objet) est construit sur une idée similaire (plus des fonctionnalités supplémentaires, bien sûr). Cet article vous montrera comment utiliser une approche "classe" avec une interface de type COM pour une liaison précoce (à la compilation) et tardive (à l'exécution).

Si vous avez déjà travaillé avec une DLL, vous savez déjà qu'une DLL a une fonction spéciale DllMain(). Cette fonction est similaire à WinMain, ou main() dans le sens où il s'agit d'une sorte de point d'entrée de DLL. Le système d'exploitation appelle automatiquement cette fonction si la DLL est chargée et déchargée. Habituellement, cette fonction n'est utilisée pour rien d'autre.

Il existe deux méthodes pour lier une DLL à un projet : la liaison précoce (au moment de la compilation) et la liaison tardive (au moment de l'exécution). Les méthodes diffèrent par la manière dont la DLL est chargée et la manière dont les fonctions implémentées et exportées à partir de la DLL sont appelées.

Liaison anticipée (au moment de la compilation)

Avec cette méthode de liaison, le système d'exploitation charge automatiquement la DLL lors du démarrage du programme. Cependant, il est nécessaire que le projet en cours de développement comprenne un fichier .lib (fichier bibliothèque) correspondant à cette DLL. Ce fichier définit tous les objets DLL exportés. Les déclarations peuvent contenir des fonctions ou des classes C standard. Tout ce que le client doit faire est d'utiliser ce fichier .lib et d'inclure le fichier d'en-tête DLL - et le système d'exploitation chargera automatiquement cette DLL. Comme vous pouvez le voir, cette méthode semble très facile à utiliser, car tout est transparent. Cependant, vous devez avoir remarqué que le code client doit être recompilé chaque fois que le code DLL change et qu'un nouveau fichier .lib est généré en conséquence. C'est à vous de décider si cela convient à votre application. Une DLL peut déclarer les fonctions qu'elle souhaite exporter avec deux méthodes. La méthode standard consiste à utiliser des fichiers .def. Un tel fichier .def n'est qu'une liste des fonctions exportées à partir de la DLL.

// ================================================ ============= // fichier .def BIBLIOTHÈQUE myfirstdll.dll DESCRIPTION "Ma première DLL" EXPORTE MyFunction // ================== = ========================================= // En-tête DLL qui sera inclus dans le code client bool MyFunction (int parms); // ================================================ ============ // implémentation de la fonction dans DLL bool MyFunction (int parms) (// faire tout ce qui est nécessaire ............)

Je pense qu'il est possible de ne pas dire qu'en cet exemple une seule MyFunction est exportée. La deuxième méthode de déclaration des objets exportés est spécifique, mais beaucoup plus puissante : vous pouvez exporter non seulement des fonctions, mais aussi des classes et des variables. Jetons un coup d'œil à l'extrait de code généré lors de la création de VisualC ++ DLL AppWizard. Les commentaires inclus dans la liste sont suffisants pour comprendre comment tout cela fonctionne.

// ================================================ ============= // En-tête DLL à inclure dans le code client / * Bloc ifdef suivant - méthode standard créer une macro qui facilite l'exportation des DLL. Tous les fichiers de cette DLL sont compilés avec une clé spécifique MYFIRSTDLL_EXPORTS. Cette clé n'est définie pour aucun des projets utilisant cette DLL. Ainsi, tout projet qui inclut ce fichier voit les fonctions MYFIRSTDLL_API comme importées d'une DLL, tandis que la DLL elle-même voit ces fonctions comme exportées. * / #ifdef MYFIRSTDLL_EXPORTS #define MYFIRSTDLL_API __declspec (dllexport) #else #define MYFIRSTDLL_API __declspec (dllimport) #endif // La classe est exportée à partir des méthodes de la classe test2.dll MYFIRSTDLL_Fll_API (CMyFid).); extern MYFIRSTDLL_API int nMyFirstDll; MYFIRSTDLL_API int fnMyFunction (void);

Lors de la compilation de la DLL, la clé MYFIRSTDLL_EXPORTS est définie, donc le mot clé __declspec (dllexport) est substitué avant les déclarations des objets exportés. Et lorsque le code client est compilé, cette clé n'est pas définie et les objets sont préfixés par __declspec (dllimport) afin que le client sache quels objets sont importés de la DLL.

Dans les deux cas, il suffit au client d'ajouter le fichier myfirstdll.lib au projet et d'inclure un fichier d'en-tête qui déclare les objets importés de la DLL, puis d'utiliser ces objets (fonctions, classes et variables) exactement comme s'ils ont été définies et mises en œuvre localement dans le projet. Voyons maintenant une autre méthode d'utilisation des DLL, qui est souvent plus pratique et plus puissante.

Liaison tardive (pendant l'exécution du programme)

Lorsque la liaison tardive est utilisée, la DLL n'est pas chargée automatiquement au démarrage du programme, mais directement dans le code où elle est nécessaire. Il n'est pas nécessaire d'utiliser des fichiers .lib, donc l'application cliente n'a pas besoin d'être recompilée lorsque la DLL change. Cette liaison est puissante précisément parce que VOUS décidez quand et quelle DLL charger. Par exemple, vous écrivez un jeu qui utilise DirectX et OpenGL. Vous pouvez simplement inclure tout le code nécessaire dans le fichier exécutable, mais il sera alors tout simplement impossible de comprendre quelque chose. Ou vous pouvez mettre du code DirectX dans une DLL et du code OpenGL dans une autre et les lier statiquement à votre projet. Mais maintenant, tout le code est interdépendant, donc si vous écrivez une nouvelle DLL contenant du code DirectX, vous devrez également recompiler l'exécutable. La seule commodité est que vous n'avez pas à vous soucier du chargement (bien que l'on ne sache pas si c'est une commodité si vous chargez les deux DLL, occupant de la mémoire, et en réalité vous n'en avez besoin que d'une seule). Et enfin, à mon avis, meilleure idée est de laisser l'exécutable décider quelle DLL charger au démarrage. Par exemple, si le programme détermine que le système ne prend pas en charge l'accélération OpenGL, il est préférable de charger la DLL avec le code DirectX, sinon de charger OpenGL. Par conséquent, la liaison tardive économise de la mémoire et réduit la dépendance entre la DLL et l'exécutable. Cependant, dans ce cas, une restriction est imposée sur les objets exportés - seules les fonctions de style C peuvent être exportées. Les classes et les variables ne peuvent pas être chargées si le programme utilise la liaison tardive. Voyons comment contourner cette limitation à l'aide d'interfaces.

Une DLL conçue pour une liaison tardive utilise généralement un fichier .def pour déterminer les objets qu'elle souhaite exporter. Si vous ne souhaitez pas utiliser de fichier .def, vous pouvez simplement utiliser le préfixe __declspec (dllexport) devant les fonctions exportées. Les deux méthodes font la même chose. Le client charge la DLL en passant le nom du fichier DLL à la fonction Win32 LoadLibrary(). Cette fonction retourne le handle HINSTANCE, qui est utilisé pour travailler avec la DLL et qui est nécessaire pour décharger la DLL de la mémoire lorsqu'elle n'est pas plus nécessaire. Après avoir chargé la DLL, le client peut obtenir un pointeur vers n'importe quelle fonction à l'aide de la fonction GetProcAddress(), en utilisant le nom de la fonction requise comme paramètre.

// ================================================ ============= // fichier .def BIBLIOTHÈQUE myfirstdll.dll DESCRIPTION "Ma première DLL" EXPORTE MyFunction // ================== = =========================================/ * Implémentation de la fonction dans la DLL * / bool MyFunction (int parms) (// faire quelque chose ............) // ====================== == ==================================== // Code client / * La déclaration de fonction n'est vraiment nécessaire que pour afin de définir les paramètres. Les déclarations de fonction sont généralement contenues dans un fichier d'en-tête fourni avec la DLL. Le mot-clé extern C dans la déclaration de fonction indique au compilateur d'utiliser les conventions de nommage des variables C * / extern "C" bool MyFunction (int parms); typedef bool (* MYFUNCTION) (int parms); MAFONCTION pfnMyFunc = 0; // pointeur vers MyFunction HINSTANCE hMyDll = :: LoadLibrary ("myfirstdll.dll"); if (hMyDll! = NULL) (// Déterminer l'adresse de la fonction pfnMyFunc = (MYFUNCTION) :: GetProcAddress (hMyDll, "MyFunction"); // En cas d'échec, décharger la DLL if (pfnMyFunc == 0) (:: FreeLibrary (hMyDll) ; return;) // Appel de la fonction bool result = pfnMyFunc (parms); // Décharge la DLL si on n'en a plus besoin :: FreeLibrary (hMyDll);)

Comme vous pouvez le voir, le code est assez simple. Voyons maintenant comment travailler avec des "classes" peut être implémenté. Comme indiqué précédemment, si la liaison tardive est utilisée, il n'y a aucun moyen direct d'importer des classes à partir d'une DLL, nous devons donc implémenter la "fonctionnalité" de la classe à l'aide d'une interface qui contient toutes les fonctions publiques à l'exception du constructeur et du destructeur. L'interface sera une structure C/C++ régulière contenant uniquement des fonctions membres abstraites virtuelles. La classe réelle dans la DLL héritera de cette structure et implémentera toutes les fonctionnalités définies dans l'interface. Maintenant, pour accéder à cette classe depuis l'application cliente, il suffit d'exporter les fonctions de style C correspondant à l'instance de la classe et de les associer à l'interface que nous avons définie pour que le client puisse les utiliser. Pour implémenter une telle méthode, deux fonctions supplémentaires sont nécessaires, dont l'une créera l'interface et la seconde supprimera l'interface une fois que vous aurez fini de l'utiliser. Un exemple de mise en œuvre de cette idée est donné ci-dessous.

// ================================================ ============ // Fichier .def BIBLIOTHÈQUE myinterface.dll DESCRIPTION "implémente l'interface I_MyInterface EXPORTE GetMyInterface FreeMyInterface // ================== = ========================================== // Fichier d'en-tête utilisé dans Dll et client, // qui déclare l'interface // I_MyInterface.h struct I_MyInterface (virtual bool Init (int parms) = 0; virtual bool Release () = 0; virtual void DoStuff () = 0;); / * Déclarations des fonctions exportées Dll et définitions de type pour les pointeurs de fonction pour un chargement et une manipulation faciles des fonctions Notez le préfixe extern "C", qui indique au compilateur d'utiliser des fonctions de style C * / extern "C" (HRESULT GetMyInterface (I_MyInterface ** pInterface); typedef HRESULT (* GETINTERFACE) (I_MyInterface ** pInterface); HRESULT FreeMyInterface (I_MyInterface ** pInterface); typedef HRESULT (* FREEINTERFACE) (I_MyInterface ** pInterface);) // ============ === ================================ ============== // Implémentation de l'interface en Dll // MyInterface.h class CMyClass : public I_MyInterface (public : bool Init (int parms); bool Libération (); void DoStuff (); CMyClass (); ~ CMyClass (); // tous les autres membres de la classe ............ private: // tous les membres de la classe ............); // ================================================ ============ // Fonctions exportées qui créent et détruisent une interface // Dllmain.h HRESULT GetMyInterface (I_MyInterface ** pInterface) (if (! * PInterface) (* pInterface = new CMyClass; return S_OK;) return E_FAIL;) HRESULT FreeMyInterface (I_MyInterface ** pInterface) (if (! * PInterface) return E_FAIL; delete * pInterface; * pInterface = 0; return S_OK;) // ========== ================================================== // Code client // Déclarations d'interface et appels de fonction GETINTERFACE pfnInterface = 0; // pointeur vers la fonction GetMyInterface I_MyInterface * pInterface = 0; // pointeur vers la structure MyInterface HINSTANCE hMyDll = :: LoadLibrary ("myinterface.dll"); if (hMyDll! = NULL) (// Détermine l'adresse de la fonction pfnInterface = (GETINTERFACE) :: GetProcAddress (hMyDll, "GetMyInterface"); // Décharge la DLL si l'opération précédente a échoué if (pfnInterface == 0) ( :: FreeLibrary (hMyDll); return;) // Appel de la fonction HRESULT hr = pfnInterface (& pInterface); // Déchargement en cas d'échec if (FAILED (hr)) (:: FreeLibrary (hMyDll); return;) // Le interface est chargée, vous pouvez appeler les fonctions pInterface-> Init (1); pInterface-> DoStuff (); pInterface-> Release (); // Libérez l'interface FREEINTERFACE pfnFree = (FREEINTERFACE) :: GetProcAddress (hMyDll, "FreeMyInterface" ); si (pfnGratuit! = 0) pfnFree (& hMyDll); // Décharge la DLL :: FreeLibrary (hMyDll); )

Ces informations suffisent à vous faire ressentir tout le confort d'utilisation des interfaces. Bonne programmation !

Dès la naissance (ou un peu plus tard), le système d'exploitation Windows utilisait des DLL (Dynamic Link Library), qui contenaient des implémentations des fonctions les plus couramment utilisées. Les héritiers de Windows - NT et Windows 95, ainsi que OS/2 - dépendent également des DLL pour fournir une grande partie de leurs fonctionnalités.

Examinons un certain nombre d'aspects de la création et de l'utilisation de DLL :

  • Comment lier statiquement des DLL
  • comment charger les DLL de manière dynamique ;
  • comment créer des DLL ;
  • Comment créer des extensions de DLL MFC.

Utilisation des DLL

Presque impossible à créer Application Windows qui n'utilise pas de DLL. Une DLL contient toutes les fonctions de l'API Win32 et d'innombrables autres fonctions des systèmes d'exploitation Win32.

D'une manière générale, les DLL sont simplement des collections de fonctions assemblées en bibliothèques. Cependant, contrairement à leurs cousines statiques (fichiers .lib), les DLL ne sont pas directement liées aux exécutables à l'aide de l'éditeur de liens. Le fichier exécutable ne contient que des informations sur leur emplacement. Au moment de l'exécution du programme, toute la bibliothèque est chargée. Cela permet à différents processus de partager les mêmes bibliothèques en mémoire. Cette approche vous permet de réduire la quantité de mémoire requise pour plusieurs applications qui utilisent de nombreuses bibliothèques partagées, ainsi que de contrôler la taille des fichiers EXE.

Cependant, si la bibliothèque n'est utilisée que par une seule application, il est préférable d'en faire une bibliothèque régulière et statique. Bien entendu, si les fonctions qu'il contient seront utilisées dans un seul programme, vous pouvez simplement y insérer le fichier source correspondant.

Le plus souvent, un projet est lié à une DLL de manière statique, ou implicite, lors de l'étape de liaison. Le système d'exploitation contrôle le chargement de la DLL pendant l'exécution du programme. Cependant, une DLL peut être chargée explicitement ou dynamiquement au cours d'une application.

Importer des bibliothèques

Lorsqu'une DLL est liée statiquement, le nom du fichier .lib est déterminé parmi d'autres paramètres de l'éditeur de liens dans ligne de commande ou sous l'onglet Lien de la boîte de dialogue Paramètres du projet dans Developer Studio. Cependant le fichier .lib utilisé par lors de la liaison implicite d'une DLL, n'est pas une bibliothèque statique régulière. Ces fichiers .lib sont appelés importer des bibliothèques(importer des bibliothèques). Ils ne contiennent pas le code de la bibliothèque lui-même, mais uniquement des références à toutes les fonctions exportées à partir du fichier DLL, dans lequel tout est stocké. Par conséquent, les bibliothèques d'importation ont tendance à être plus petites que les fichiers DLL. Nous reviendrons plus tard sur les modalités de leur création. Examinons maintenant d'autres problèmes liés à la liaison implicite des bibliothèques dynamiques.

Négociation d'interface

Lorsque vous utilisez vos propres bibliothèques ou des bibliothèques tierces, vous devrez faire attention à faire correspondre l'appel de fonction à son prototype.

Si le monde était parfait, les programmeurs n'auraient pas à se soucier de faire correspondre les interfaces de fonction lors de la liaison des bibliothèques - elles seraient toutes identiques. Cependant, le monde est loin d'être parfait, et de nombreux grands programmes sont écrits à l'aide de diverses bibliothèques sans C++.

Par défaut, dans Visual C++, les interfaces de fonction s'accordent selon les règles C++. Cela signifie que les paramètres sont poussés sur la pile de droite à gauche, l'appelant est responsable de les supprimer de la pile lorsque la fonction se termine et développe son nom. La modification des noms permet à l'éditeur de liens de faire la distinction entre les fonctions surchargées, c'est-à-dire fonctions avec le même nom mais des listes d'arguments différentes. Cependant, dans l'ancienne bibliothèque C, il n'y a pas de fonctions à nom étendu.

Bien que toutes les autres règles d'appel d'une fonction en C soient identiques à celles d'appel d'une fonction en C++, les noms de fonction ne sont pas développés dans les bibliothèques C. Ils sont uniquement ajoutés avec un trait de soulignement (_) devant.

Si vous devez connecter une bibliothèque C à une application C++, toutes les fonctions de cette bibliothèque devront être déclarées comme externes au format C :

Extern "С" int MyOldCFunction (int myParam);

Les déclarations de fonction de bibliothèque sont généralement placées dans le fichier d'en-tête de la bibliothèque, bien que les en-têtes de la plupart des bibliothèques C ne soient pas destinés à être utilisés dans des projets C++. Dans ce cas, vous devez créer une copie du fichier d'en-tête et y inclure le modificateur extern "C" dans la déclaration de toutes les fonctions de bibliothèque utilisées. Le modificateur externe "C" peut également être appliqué à l'ensemble du bloc, auquel l'ancien fichier d'en-tête C est connecté à l'aide de la directive #tinclude. Ainsi, au lieu de modifier chaque fonction séparément, vous pouvez vous débrouiller avec seulement trois lignes :

Externe "C" (#include "MyCLib.h")

Les programmes pour les anciennes versions de Windows utilisaient également des conventions d'appel de fonction Langue PASCAL pour les fonctions API Windows... Les programmes plus récents devraient utiliser le modificateur winapi converti en _stdcall. Bien qu'il ne s'agisse pas d'une interface de fonction C ou C++ standard, elle est utilisée pour appeler des fonctions API Windows. Cependant, tout cela est généralement déjà pris en compte dans les en-têtes Windows standard.

Chargement d'une DLL de lien implicite

Au démarrage, l'application essaie de trouver tous les fichiers DLL implicitement connectés à l'application et de les placer dans la zone mémoire occupée par ce processus. Le système d'exploitation recherche les fichiers DLL dans l'ordre suivant.

  • Le répertoire où se trouve le fichier EXE.
  • Le répertoire courant du processus.
  • Répertoire système Windows.

Si la DLL n'est pas trouvée, l'application affiche une boîte de dialogue avec un message sur son absence et les chemins qui ont été recherchés. Ensuite, le processus est arrêté.

Si la bibliothèque requise est trouvée, elle est placée dans la RAM du processus, où elle reste jusqu'à sa fin. L'application peut désormais accéder aux fonctions contenues dans la DLL.

Chargement et déchargement dynamique des DLL

Au lieu de faire en sorte que Windows se connecte dynamiquement à la DLL lorsque l'application est chargée pour la première fois dans la RAM, vous pouvez lier le programme au module de bibliothèque au moment de l'exécution (de cette façon, vous n'avez pas besoin d'utiliser la bibliothèque d'importation pendant le processus de création de l'application). Plus précisément, vous pouvez déterminer quelle DLL est disponible pour l'utilisateur ou laisser l'utilisateur choisir la DLL à charger. Ainsi, vous pouvez utiliser différentes DLL qui implémentent les mêmes fonctions et effectuent des actions différentes. Par exemple, une application conçue pour transférer des données de manière indépendante peut décider au moment de l'exécution de charger la DLL pour TCP/IP ou un autre protocole.

Chargement d'une DLL normale

La première chose à faire lors du chargement dynamique d'une DLL est de placer le module de bibliothèque dans la mémoire de processus. Cette opération s'effectue à l'aide de la fonction :: LoadLibrary qui a un seul argument - le nom du module à charger. Le fragment correspondant du programme devrait ressembler à ceci :

HINSTANCE hMyDll; :: if ((hMyDll = :: LoadLibrary ("MyDLL")) == NULL) (/ * La DLL n'a pas pu être chargée * /) else (/ * l'application peut utiliser les fonctions DLL via hMyDll * /)

Windows considère dll comme l'extension de fichier standard pour une bibliothèque, sauf si vous spécifiez une extension différente. Si le chemin est également spécifié dans le nom du fichier, alors seul il sera utilisé pour rechercher le fichier. Sinon, Windows recherchera le fichier de la même manière que dans le cas des DLL connectées implicitement, en commençant par le répertoire à partir duquel le fichier exe est chargé et en continuant selon la valeur PATH.

Lorsque Windows trouve le fichier, son chemin complet sera comparé au chemin des DLL déjà chargées par ce processus. Si une identité est trouvée, au lieu de charger une copie de l'application, un handle vers la bibliothèque déjà liée est renvoyé.

Si le fichier est trouvé et que la bibliothèque a été chargée avec succès, la fonction :: LoadLibrary renvoie son handle, qui est utilisé pour accéder aux fonctions de la bibliothèque.

Avant d'utiliser les fonctions de la bibliothèque, vous devez obtenir leur adresse. Pour ce faire, utilisez d'abord la directive typedef pour définir le type d'un pointeur de fonction et définir une variable de ce nouveau type, par exemple :

// le type PFN_MyFunction déclarera un pointeur vers une fonction // acceptant un pointeur vers un tampon de caractères et renvoyant une valeur int typedef int (WINAPI * PFN_MyFunction) (char *); :: PFN_MaFonction pfnMaFonction;

Ensuite, vous devez obtenir le descripteur de bibliothèque, avec lequel vous pouvez déterminer les adresses des fonctions, par exemple, l'adresse d'une fonction nommée MyFunction :

HMyDll = :: LoadLibrary ("MyDLL"); pfnMaFonction = (PFN_MaFonction) :: GetProcAddress (hMyDll, "MaFonction"); :: int iCode = (* pfnMaFonction) ("Bonjour");

L'adresse de la fonction est déterminée à l'aide de la fonction :: GetProcAddress, il faut lui passer le nom de la bibliothèque et le nom de la fonction. Ce dernier doit être transféré sous la forme sous laquelle il est exporté depuis la DLL.

Vous pouvez également faire référence à la fonction par l'ordinal auquel elle est exportée (dans ce cas, un fichier def doit être utilisé pour créer la bibliothèque, cela sera discuté plus tard) :

PfnMyFunction = (PFN_MyFunction) :: GetProcAddress (hMyDll, MAKEINTRESOURCE (1));

Après avoir terminé le travail avec la bibliothèque de liens dynamiques, elle peut être déchargée de la mémoire de processus à l'aide de la fonction :: Bibliothèque gratuite:

:: FreeLibrary (hMyDll);

Chargement des extensions DLL MFC

Lors du chargement des extensions MFC pour les DLL (qui sont discutées plus en détail ci-dessous) au lieu des fonctions Charger la bibliothèque et Bibliothèque gratuite les fonctions sont utilisées AfxLoadLibrary et AfxFreeLibrary... Ces dernières sont quasiment identiques aux fonctions de l'API Win32. Ils garantissent uniquement en outre que les structures MFC initialisées par l'extension DLL ne sont pas entachées par d'autres threads.

Ressources DLL

Le chargement dynamique s'applique également aux ressources DLL que MFC utilise pour charger les ressources d'application standard. Pour ce faire, vous devez d'abord appeler la fonction Charger la bibliothèque et placez la DLL en mémoire. Puis en utilisant la fonction AfxSetResourceHandle vous devez préparer la fenêtre du programme pour recevoir des ressources de la bibliothèque nouvellement chargée. Sinon, les ressources seront chargées à partir des fichiers connectés au fichier exécutable du processus. Cette approche est pratique lorsque vous devez utiliser différents ensembles de ressources, par exemple, pour différentes langues.

Commenter. Utilisation de la fonction Charger la bibliothèque vous pouvez également charger des fichiers exécutables en mémoire (ne les exécutez pas pour l'exécution !). Le descripteur d'exécutable peut alors être utilisé lors de l'appel de fonctions RechercherRessource et ChargerRessource pour rechercher et charger des ressources d'application. Décharger les modules de la mémoire en utilisant également la fonction Bibliothèque gratuite.

Un exemple de DLL standard et de méthodes de chargement

Voici le code source d'une bibliothèque de liens dynamiques appelée MyDLL qui contient une fonction, MyFunction, qui affiche simplement un message.

Tout d'abord, une macroconstante est définie dans le fichier d'en-tête EXPORTATION... En utilisant ce mot-clé en définissant une fonction, la DLL vous permet d'indiquer à l'éditeur de liens que la fonction est disponible pour une utilisation par d'autres programmes, ce qui l'amène à l'ajouter à la bibliothèque d'importation. De plus, une telle fonction, tout comme une procédure de fenêtre, doit être définie à l'aide de la constante RAPPELER:

MaDLL.h#define EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction (char * str);

Le fichier de bibliothèque est également légèrement différent des fichiers C classiques pour Windows. Au lieu d'une fonction WinMain il y a une fonction DllMain... Cette fonction est utilisée pour effectuer l'initialisation, qui sera discutée plus tard. Pour que la bibliothèque reste en mémoire après son chargement et que ses fonctions puissent être appelées, sa valeur de retour doit être VRAI :

MaDLL.c #include #include "MyDLL.h" int WINAPI DllMain (HINSTANCE hInstance, DWORD fdReason, PVOID pvReserved) (retourne TRUE;) EXPORT int CALLBACK MyFunction (char * str) (MessageBox (NULL, str, "Function from DLL", MB_OK); retour 1 ;)

Après avoir traduit et lié ces fichiers, deux fichiers apparaissent - MyDLL.dll (la bibliothèque de liens dynamiques elle-même) et MyDLL.lib (sa bibliothèque d'importation).

Un exemple de connexion DLL implicite par une application

Donnons maintenant le code source d'une application simple qui utilise la fonction MyFunction de la bibliothèque MyDLL.dll :

#comprendre #include "MyDLL.h" int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) (int iCode = MyFunction ("Bonjour"); return 0;)

Ce programme ressemble à programmes réguliers pour Windows, ce qu'il est vraiment. Néanmoins, il convient de noter que son texte source, en plus d'appeler la fonction MyFunction depuis la bibliothèque DLL, comprend également le fichier d'en-tête de cette bibliothèque, MyDLL.h. Il est également nécessaire au stade de la construction de l'application de s'y connecter importer la bibliothèque MyDLL.lib (le processus de connexion implicite d'une DLL à un module exécutable).

Il est extrêmement important de comprendre que le code de MyFunction lui-même n'est pas inclus dans le fichier MyApp.exe. Au lieu de cela, il a simplement un lien vers le fichier MyDLL.dll et un lien vers la MyFunction, qui se trouve dans ce fichier. Le fichier MyApp.exe nécessite l'exécution du fichier MyDLL.dll.

Le fichier d'en-tête MyDLL.h est inclus dans le fichier source du programme MyApp.c de la même manière que le fichier windows.h y est inclus. L'inclusion de la bibliothèque d'importation MyDLL.lib pour la liaison est similaire à l'inclusion de toutes les bibliothèques d'importation Windows. Lorsque MyApp.exe est en cours d'exécution, il est lié à MyDLL.dll comme toutes les DLL Windows standard.

Un exemple de chargement dynamique d'une DLL par une application

Donnons maintenant le code source complet d'une application simple qui utilise la fonction MyFunction de la bibliothèque MyDLL.dll en utilisant le chargement dynamique de la bibliothèque :

#comprendre typedef int (WINAPI * PFN_MyFunction) (char *); int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) (HINSTANCE hMyDll; if ((hMyDll = LoadLibrary ("MyDLL")) == NULL) return 1; pFN_MyFunctionFunction , "MyFunction ="); * pfnMyFunction) ("Bonjour"); FreeLibrary (hMyDll); return 0;)

Création de DLL

Maintenant que vous savez comment fonctionnent les DLL dans les applications, voyons comment les créer. Lors du développement d'une application, il est conseillé de placer dans une DLL des fonctions accessibles par plusieurs processus. Cela permet une utilisation plus efficace de la mémoire dans Windows.

Le moyen le plus simple de créer un nouveau projet DLL consiste à utiliser AppWizard, qui effectue automatiquement la plupart des opérations. Pour les DLL simples telles que celles décrites dans ce chapitre, vous devez sélectionner le type de projet Win32 Dynamic-Link Library. Le nouveau projet se verra attribuer tous les paramètres nécessaires pour créer la DLL. Les fichiers sources devront être ajoutés au projet manuellement.

Si vous prévoyez d'utiliser pleinement Fonctionnalité MFC, tels que des documents et des vues, ou envisagez de créer un serveur OLE Automation, il est préférable de choisir le type de projet MFC AppWizard (dll). Dans ce cas, en plus d'affecter des paramètres au projet pour connecter des bibliothèques dynamiques, l'assistant effectuera un travail supplémentaire. Le projet ajoutera les références nécessaires aux bibliothèques MFC et aux fichiers source contenant la description et l'implémentation dans la DLL de l'objet de classe d'application dérivé de CWinApp.

Parfois, il est pratique de créer d'abord un projet MFC AppWizard (dll) en tant qu'application de test, puis de créer une DLL en tant que composant. En conséquence, la DLL sera créée automatiquement si nécessaire.

Dll Fonction principale

La plupart des DLL sont simplement des collections de fonctions virtuellement indépendantes les unes des autres qui sont exportées et utilisées dans les applications. En plus des fonctions destinées à l'export, chaque DLL possède une fonction DllMain... Cette fonction est conçue pour initialiser et nettoyer les DLL. Elle a remplacé des fonctions LibMain et WEP utilisé dans les versions précédentes de Windows. La structure de la fonction la plus simple DllMain pourrait ressembler à ceci :

BOOL WINAPI DllMain (HANDLE hInst, DWORD dwReason, LPVOID IpReserved) (BOOL bAllWentWell = TRUE ; switch (dwReason) (case DLL_PROCESS_ATTACH : // Initialisation du processus. Break ; case DLL_THREAD_ATTACH : // Initialiser DLL_ATTACH_ATTACH : // Initialiser DLL_ATTACH_ATTACH : // Initialiser DLLATTACH : DLL_ATTACH : // Initialisation du thread. Break flow structures.break ; case DLL_PROCESS_DETACH : // Effacement du processus structures.break ;) if (bAllWentWell) renvoie TRUE ; sinon renvoie FALSE ;)

Fonction DllMain appelé dans plusieurs cas. La raison de l'appel est déterminée par le paramètre dwReason, qui peut être l'une des valeurs suivantes.

La première fois que la DLL est chargée par le processus, la fonction est appelée DllMain avec dwReason égal à DLL_PROCESS_ATTACH. Chaque fois qu'un processus crée un nouveau thread, DllMainO est appelé avec dwReason égal à DLL_THREAD_ATTACH (sauf pour le premier thread, car dans ce cas dwReason est égal à DLL_PROCESS_ATTACH).

A la fin du processus avec la DLL, la fonction DllMain appelé avec dwReason égal à DLL_PROCESS_DETACH. Lorsqu'un thread (autre que le premier) est détruit, dwReason sera égal à DLL_THREAD_DETACH.

Toutes les opérations d'initialisation et de nettoyage pour les processus et les threads dont la DLL a besoin doivent être effectuées en fonction de la valeur dwReason, comme illustré dans l'exemple précédent. L'initialisation du processus est généralement limitée à l'allocation de ressources partagées par thread, comme le chargement de fichiers partagés et l'initialisation de bibliothèques. L'initialisation des threads permet de configurer des modes propres à un thread donné, par exemple pour initialiser la mémoire locale.

Une DLL peut inclure des ressources qui n'appartiennent pas à l'application appelante. Si les fonctions DLL opèrent sur des ressources DLL, il serait évidemment utile de stocker le descripteur hInst quelque part dans un endroit caché et de l'utiliser lors du chargement des ressources à partir de la DLL. Le pointeur IpReserved est réservé à l'interne utiliser des fenêtres... Par conséquent, la demande ne doit pas le réclamer. Vous pouvez seulement vérifier sa signification. Si la DLL a été chargée dynamiquement, elle sera NULL. Lorsqu'il est chargé statiquement, ce pointeur sera non nul.

En cas de succès, la fonction DllMain doit retourner VRAI. Si une erreur se produit, FALSE est renvoyé et toute autre action est terminée.

Commenter. Si vous n'écrivez pas votre propre fonction DllMain(), le compilateur inclura la version standard, qui renvoie simplement TRUE.

Exportation de fonctions à partir de DLL

Pour qu'une application accède aux fonctions de la DLL, chacune d'entre elles doit occuper une ligne dans la table des fonctions exportées de la DLL. Il existe deux manières d'ajouter une fonction à cette table au moment de la compilation.

Méthode __Declspec (dllexport)

Vous pouvez exporter une fonction à partir d'une DLL en plaçant le modificateur __declspec (dllexport) au début de sa description. En outre, MFC inclut plusieurs macros qui définissent __declspec (dllexport), notamment AFX_CLASS_EXPORT, AFX_DATA_EXPORT et AFX_API_EXPORT.

La méthode __declspec est utilisée moins fréquemment que la seconde méthode, qui fonctionne avec les fichiers de définition de module (.def) et permet un meilleur contrôle du processus d'exportation.

Fichiers de définition de module

La syntaxe des fichiers .def dans Visual C ++ est assez simple, principalement parce que les paramètres complexes utilisés dans versions précédentes Windows dans Win32 ne s'applique plus. Comme il ressort de l'exemple simple suivant, le fichier .def contient le nom et la description de la bibliothèque, ainsi que la liste des fonctions exportées :

MaDLL.def BIBLIOTHÈQUE "MyDLL" DESCRIPTION "MyDLL - sample DLL" EXPORTATIONS MyFunction @ 1

Dans la ligne d'exportation d'une fonction, vous pouvez spécifier son numéro ordinal en le faisant précéder du symbole @. Ce numéro sera ensuite utilisé pour se référer à ObtenirAdresseProc(). En fait, le compilateur attribue des numéros séquentiels à tous les objets exportés. Cependant, la façon dont il le fait est quelque peu imprévisible à moins que ces numéros ne soient explicitement attribués.

Vous pouvez utiliser le paramètre NONAME dans la ligne d'exportation. Cela empêche le compilateur d'inclure le nom de la fonction dans la table d'exportation DLL :

MaFonction @ 1 NONAME

Cela peut parfois économiser beaucoup d'espace dans le fichier DLL. Les applications qui utilisent la bibliothèque d'importation pour lier implicitement la DLL ne « remarqueront » pas la différence car la liaison implicite utilise automatiquement des numéros de séquence. Les applications qui chargent dynamiquement les DLL devront passer ObtenirAdresseProc un numéro de séquence, pas un nom de fonction.

Lors de l'utilisation de ce qui précède, le fichier def décrivant les fonctions exportées de la bibliothèque DLL peut, par exemple, ne pas ressembler à ceci :

#define EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction (char * str); un comme ceci : extern "C" int CALLBACK MyFunction (char * str);

Exporter des cours

Créer un fichier .def pour exporter même des classes simples à partir d'une bibliothèque dynamique peut être assez délicat. Vous devrez explicitement exporter toutes les fonctions pouvant être utilisées par une application externe.

Si vous regardez le fichier d'allocation de mémoire implémenté dans la classe, vous y remarquerez des fonctions très inhabituelles. Il s'avère qu'il existe des constructeurs et destructeurs implicites, des fonctions déclarées dans les macros MFC, notamment _DECLARE_MESSAGE_MAP, ainsi que des fonctions écrites par le programmeur.

Bien qu'il soit possible d'exporter chacune de ces fonctions individuellement, il existe un moyen plus simple. Si vous utilisez le modificateur de macro AFX_CLASS_EXPORT dans la déclaration de classe, le compilateur se chargera de l'export lui-même. fonctions nécessaires permettant à l'application d'utiliser la classe contenue dans la DLL.

mémoire DLL

Contrairement aux bibliothèques statiques, qui font essentiellement partie du code de l'application, les bibliothèques de liens dynamiques dans les versions 16 bits de Windows géraient la mémoire d'une manière légèrement différente. Sous Win 16, la mémoire DLL était située en dehors de l'espace d'adressage de la tâche. Le placement de bibliothèques dynamiques en mémoire globale a permis de les partager avec diverses tâches.

Dans Win32, une DLL se trouve dans la zone mémoire du processus qui la charge. Chaque processus reçoit une copie distincte de la mémoire "globale" de la DLL, qui est réinitialisée à chaque fois qu'un nouveau processus la charge. Cela signifie qu'une bibliothèque de liens dynamiques ne peut pas être partagée dans la mémoire partagée comme c'était le cas dans Winl6.

Et pourtant, en effectuant des manipulations complexes sur le segment de données de la DLL, vous pouvez créer une zone de mémoire partagée pour tous les processus utilisant cette bibliothèque.

Supposons que vous ayez un tableau d'entiers qui devrait être utilisé par tous les processus qui chargent cette DLL. Celui-ci peut être programmé comme suit :

#pragma data_seg (". myseg") int sharedlnts ; // autres variables usage commun#pragma data_seg () #pragma comment (lib, "msvcrt" "-SECTION: .myseg, rws");

Toutes les variables déclarées entre les directives #pragma data_seg() sont placées dans le segment .myseg. La directive #pragma comment () n'est pas un commentaire normal. Il demande à la bibliothèque runtime C de marquer la nouvelle partition comme lecture/écriture/partage.

Compilation complète de DLL

Si le projet DLL est créé à l'aide d'AppWizard et que le fichier .def est modifié en conséquence, cela suffit. Si vous créez des fichiers de projet manuellement ou par d'autres moyens sans utiliser AppWizard, vous devez inclure l'option / DLL sur la ligne de commande de l'éditeur de liens. Cela crée une DLL au lieu d'un fichier exécutable autonome.

S'il existe une ligne LIBRART dans le fichier .def, vous n'avez pas besoin de spécifier explicitement l'option / DLL sur la ligne de commande de l'éditeur de liens.

Il existe un certain nombre de modes spéciaux pour MFC concernant l'utilisation de la DLL MFC. La section suivante est consacrée à cette question.

DLL et MFC

Le programmeur n'est pas obligé d'utiliser MFC lors de la création de bibliothèques de liens dynamiques. Cependant, l'utilisation de MFC ouvre un certain nombre de possibilités très importantes.

Il existe deux niveaux d'utilisation de la structure MFC dans une DLL. La première est une bibliothèque de liens dynamiques basée sur MFC, DLL MFC(DLL MFC régulière). Il peut utiliser MFC, mais il ne peut pas transmettre de pointeurs d'objet MFC entre les DLL et les applications. Le deuxième niveau est mis en œuvre dans extensions MFC dynamiques(DLL d'extensions MFC). L'utilisation de ce type de bibliothèque de liens dynamiques nécessite un travail de configuration supplémentaire, mais vous permet d'échanger librement des pointeurs vers des objets MFC entre la DLL et l'application.

DLL MFC courantes

Les DLL MFC régulières vous permettent d'utiliser MFC dans des bibliothèques de liens dynamiques. Cependant, les applications qui accèdent à ces bibliothèques n'ont pas besoin d'être construites sur MFC. Dans les DLL classiques, vous pouvez utiliser MFC comme vous le souhaitez, notamment en créant de nouvelles classes basées sur MFC dans la DLL et en les exportant vers des applications.

Toutefois, les DLL régulières ne peuvent pas échanger des pointeurs vers des classes dérivées de MFC avec des applications.

Si votre application doit échanger des pointeurs vers des objets de classes MFC ou leurs dérivés avec des DLL, vous devez utiliser l'extension DLL décrite dans la section suivante.

L'architecture DLL commune est conçue pour être utilisée par d'autres environnements de programmation tels que Visual Basic et PowerBuilder.

Lors de la création d'une DLL MFC régulière à l'aide d'AppWizard, un nouveau projet de type Assistant d'application MFC (dll)... Dans la première boîte de dialogue de l'assistant d'application, vous devez sélectionner l'un des modes pour les bibliothèques de liens dynamiques classiques : « DLL régulière avec MFC statistiquement lié » ou « DLL régulière utilisant la DLL MFC partagée ». Le premier prévoit la statique et le second la liaison dynamique des bibliothèques MFC. Vous pouvez ultérieurement modifier le mode de connexion MFC à la DLL à l'aide de la zone de liste déroulante sous l'onglet Général de la boîte de dialogue Paramètres du projet.

Gestion des informations d'état du MFC

Chaque module de processus MFC contient des informations sur son état. Ainsi, les informations d'état d'une DLL sont différentes des informations d'état de l'application appelante. Par conséquent, toutes les fonctions exportées à partir de la bibliothèque accessibles directement à partir des applications doivent indiquer à MFC les informations d'état à utiliser. Dans une DLL MFC standard qui utilise des DLL MFC, placez la ligne suivante au début de la fonction exportée avant d'appeler un sous-programme MFC :

AFX_MANAGE_STATE (AfxGetStaticModuleState ());

Cette instruction spécifie l'utilisation des informations d'état appropriées lors de l'exécution de la fonction appelant le sous-programme.

Extensions MFC dynamiques

MFC vous permet de créer des DLL que vos applications ne considèrent pas comme un ensemble de fonctions distinctes, mais comme des extensions de MFC. Avec ce type de DLL, vous pouvez créer de nouvelles classes qui dérivent des classes MFC et les utiliser dans vos applications.

Pour permettre aux pointeurs d'objet MFC d'être librement échangés entre l'application et la DLL, vous devez créer une extension MFC dynamique. Les DLL de ce type se connectent aux DLL MFC de la même manière que toute application qui utilise l'extension dynamique MFC.

Le moyen le plus simple de créer une nouvelle extension MFC dynamique consiste à utiliser l'assistant d'application pour définir le type de projet Assistant d'application MFC (dll) et à l'étape 1 activez le mode "MFC Extension DLL". Cela affectera tous les attributs requis de l'extension dynamique MFC au nouveau projet. De plus, la fonction sera créée DllMain pour une DLL qui effectue un certain nombre d'opérations spécifiques pour initialiser une extension de DLL. Veuillez noter que les bibliothèques dynamiques de ce genre ne contiennent pas et ne doivent pas contenir d'objets dérivés de CWinApp.

Initialisation des extensions dynamiques

Pour "s'intégrer" dans le framework MFC, les extensions MFC dynamiques nécessitent des la configuration initiale... Les opérations correspondantes sont effectuées par la fonction DllMain... Regardons un exemple de cette fonction créée par l'AppWizard.

Statique AFX_EXTENSION_MODULE MyExtDLL = (NULL, NULL); extern "C" int APIENTRY DllMain (HINSTANCE hinstance, DWORD dwReason, LPVOID IpReserved) (if (dwReason == DLL_PROCESS_ATTACH) (TRACED ("MYEXT.DLL Initializing! \ n"); // Initialisation unique de la DLL AfxInitExtensionModule ( , hinstance); // Insérez cette DLL dans la chaîne de ressources new CDynLinkLibrary (MyExtDLL);) else if (dwReason == DLL_PROCESS_DETACH) (TRACED ("MYEXT.DLL Terncing! \ n");) return 1; // ok)

La partie la plus importante de cette fonction est l'appel AfxInitExtensionModule... Il s'agit de l'initialisation de la bibliothèque de liens dynamiques, lui permettant de fonctionner correctement dans le cadre d'un framework MFC. Les arguments de cette fonction sont le handle de DLL passé à DllMain et la structure AFX_EXTENSION_MODULE, qui contient des informations sur la DLL MFC.

Il n'est pas nécessaire d'initialiser explicitement la structure AFX_EXTENSION_MODULE. Cependant, il doit être déclaré. Le constructeur s'occupera de l'initialisation. CDynLinkLibrary... Dans DLL, vous devez créer une classe CDynLinkLibrary... Son constructeur initialisera non seulement la structure AFX_EXTENSION_MODULE, mais ajoutera également la nouvelle bibliothèque à la liste des DLL avec lesquelles MFC peut fonctionner.

Chargement des extensions MFC dynamiques

À partir de la version 4.0, MFC permet de charger et de décharger dynamiquement les DLL, y compris les extensions. Pour effectuer correctement ces opérations sur la DLL en cours de création, dans sa fonction DllMain au moment de la déconnexion du processus, vous devez ajouter un appel AfxTermExtensionModule... La structure AFX_EXTENSION_MODULE déjà utilisée ci-dessus est passée à la dernière fonction en paramètre. Pour ce faire, dans le texte DllMain vous devez ajouter les lignes suivantes.

Si (dwReason == DLL_PROCESS_DETACH) (AfxTermExtensionModule (MyExtDLL);)

N'oubliez pas non plus que la nouvelle DLL est une extension dynamique et doit être chargée et déchargée dynamiquement à l'aide des fonctions AfxLoadLibrary et AfxFreeLibrary,mais non Charger la bibliothèque et Bibliothèque gratuite.

Exportation de fonctions à partir d'extensions dynamiques

Voyons maintenant comment exporter des fonctions et des classes d'une extension dynamique vers une application. Bien que vous puissiez ajouter manuellement tous les noms étendus au fichier DEF, il est préférable d'utiliser des modificateurs pour les déclarations de classes et de fonctions exportées, tels que AFX_EXT_CLASS et AFX_EXT_API, par exemple :

Classe AFX_EXT_CLASS CMyClass : public CObject (// Votre déclaration de classe) void AFX_EXT_API MyFunc ();

Il existe deux manières de charger une DLL : la liaison explicite et la liaison implicite.

La liaison implicite ajoute les fonctions de la DLL chargée à la section d'importation du fichier appelant. Lors du lancement d'un tel fichier, le chargeur système opérateur analyse la section d'importation et connecte toutes les bibliothèques spécifiées. En raison de sa simplicité, cette méthode est très populaire ; mais la simplicité est la simplicité, et la liaison implicite présente certains inconvénients et limites :

toutes les DLL connectées sont toujours chargées, même si pendant toute la session le programme n'appelle jamais aucune d'entre elles ;

si au moins une des DLL requises est manquante (ou si la DLL n'exporte pas au moins une fonction requise) - le chargement du fichier exécutable est interrompu par le message "La bibliothèque de liens dynamiques n'a pas pu être trouvée" (ou quelque chose comme ça) - même si l'absence de cette DLL n'est pas critique pour exécuter le programme. Par exemple, éditeur de texte pourrait très bien fonctionner dans une configuration minimale - sans module d'impression, sans sortie de tableaux, de graphiques, de formules et d'autres composants mineurs, mais si ces DLL sont chargées avec un lien implicite - que cela vous plaise ou non, vous devrez les "tirer" .

Les recherches de DLL sont effectuées dans l'ordre suivant : dans le répertoire contenant le fichier appelant ; dans le répertoire courant du processus ; v répertoire système% Windows% système % ; dans le répertoire principal% Windows% ; dans les répertoires spécifiés dans la variable PATH. Il est impossible de spécifier un chemin de recherche différent (ou plutôt, peut-être, mais cela nécessitera d'apporter des modifications au registre du système, et ces modifications affecteront tous les processus en cours d'exécution dans le système - ce qui n'est pas bon).

La liaison explicite supprime tous ces inconvénients - au prix d'une certaine complexité de code. Le programmeur lui-même devra se charger de charger la DLL et de connecter les fonctions exportées (sans oublier le contrôle des erreurs, sinon un jour le système finira par geler). D'un autre côté, la liaison explicite permet de charger les DLL selon les besoins et donne au programmeur la possibilité de gérer indépendamment les situations où les DLL sont manquantes. Vous pouvez aller plus loin - ne définissez pas explicitement le nom de la DLL dans le programme, mais scannez tel ou tel répertoire pour la présence de bibliothèques dynamiques et connectez tous ceux trouvés à l'application. C'est ainsi que fonctionne le mécanisme de prise en charge des plug-ins dans le célèbre gestionnaire de fichiers FAR (et pas seulement dans celui-ci).

Il existe trois façons de charger une DLL :

a) implicite ;

c) différé.

Envisagez le chargement implicite de DLL. Pour créer une application qui repose sur le chargement implicite de DLL, vous devez avoir :

Fichier d'inclusion de la bibliothèque avec les descriptions des objets DLL utilisés (prototypes de fonction, déclarations de classe et de type). Ce fichier est utilisé par le compilateur.

Fichier LIB avec une liste d'identifiants importés. Ce fichier doit être ajouté aux paramètres du projet (à la liste des bibliothèques utilisées par l'éditeur de liens).

Le projet est compilé de la manière habituelle. A partir des modules objets et du fichier LIB, ainsi qu'en tenant compte des liens vers les identifiants importés, l'éditeur de liens (linker, linker) génère un module EXE chargeable. Dans ce module, l'éditeur de liens place également une section d'importation qui répertorie les noms de tous les modules DLL requis. Pour chaque DLL, la section d'importation spécifie les noms de fonction et de variable référencés dans le code du fichier exécutable. Le chargeur du système d'exploitation utilisera ces informations.

Que se passe-t-il pendant la phase d'exécution de l'application cliente ? Après le démarrage du module EXE, le chargeur du système d'exploitation effectue les opérations suivantes :

1. Crée un espace d'adressage virtuel pour un nouveau processus et projette un module exécutable dessus ;

2. Analyse la section d'importation, identifie tous les modules DLL nécessaires et les projette également sur l'espace d'adressage du processus.

Une DLL peut importer des fonctions et des variables d'une autre DLL. Cela signifie qu'il peut avoir sa propre section d'importation, pour laquelle vous devez répéter les mêmes étapes. Par conséquent, l'initialisation du processus peut prendre un certain temps.

Une fois que le module EXE et toutes les DLL sont mappés à l'espace d'adressage du processus, son thread principal est prêt à être exécuté et l'application démarre.

Les inconvénients du chargement implicite sont le chargement obligatoire de la bibliothèque, même si ses fonctions ne seront pas appelées, et, par conséquent, l'exigence obligatoire de la présence de la bibliothèque lors de la liaison.

Le chargement explicite de DLL élimine les inconvénients mentionnés ci-dessus au prix d'une certaine complexité de code. Le programmeur doit se charger lui-même de charger la DLL et de connecter les fonctions exportées. D'un autre côté, le chargement explicite permet à la DLL d'être chargée selon les besoins et permet au programme de gérer les situations qui surviennent en l'absence d'une DLL.

Dans le cas d'un chargement explicite, le processus de travail avec DLL se déroule en trois étapes :

1. Chargement de la DLL à l'aide d'une fonction Charger la bibliothèque(ou son équivalent étendu LoadLibraryEx). En cas de chargement réussi, la fonction retourne un descripteur hLib de type HMODULE, qui permet un accès ultérieur à cette DLL.

2. Appels de fonction ObtenirAdresseProc pour obtenir des pointeurs vers les fonctions requises ou d'autres objets. Comme premier paramètre, la fonction ObtenirAdresseProc reçoit un descripteur hLib, comme deuxième paramètre - l'adresse de la chaîne avec le nom de la fonction importée. Le pointeur résultant est ensuite utilisé par le client. Par exemple, s'il s'agit d'un pointeur de fonction, la fonction requise est appelée.

3. Lorsque la bibliothèque dynamique chargée n'est plus nécessaire, il est recommandé de la libérer en appelant la fonction Bibliothèque libre. Libérer une bibliothèque ne signifie pas que le système d'exploitation la supprimera immédiatement de la mémoire. Le délai de déchargement est prévu pour le cas où la même DLL après un certain temps est à nouveau requise par un processus. Mais s'il y a des problèmes avec la RAM, Windows supprime d'abord les bibliothèques libérées de la mémoire.

Envisagez de charger paresseux une DLL. Une DLL à chargement différé est une DLL liée implicitement qui n'est pas chargée tant que le code ne fait pas référence à un identificateur exporté à partir de celle-ci. De telles DLL peuvent être utiles dans les situations suivantes :

Si une application utilise plusieurs DLL, son initialisation peut prendre un certain temps, ce qui est requis par le chargeur pour projeter toutes les DLL dans l'espace d'adressage du processus. Les DLL à chargement différé résolvent ce problème en répartissant la charge de la DLL lors de l'exécution de l'application.

Si l'application est conçue pour fonctionner dans différentes versions du système d'exploitation, certaines fonctions peuvent apparaître uniquement dans les versions ultérieures du système d'exploitation et ne pas être utilisées dans la version actuelle. Mais si le programme n'appelle pas une fonction spécifique, la DLL n'est pas nécessaire pour cela et elle peut continuer à fonctionner en toute sécurité. En se référant à un inexistant

la fonction peut prévoir l'émission d'un avertissement approprié à l'utilisateur.

Pour implémenter la méthode de chargement paresseux, il est en outre nécessaire d'ajouter à la liste des bibliothèques d'éditeurs de liens non seulement la bibliothèque d'importation nécessaire MyLib.lib, mais également la bibliothèque d'importation système delayimp.lib. De plus, vous devez ajouter le drapeau / delayload: MyLib.dll dans les options de l'éditeur de liens.

Ces paramètres obligent l'éditeur de liens à effectuer les opérations suivantes :

Implémenter une fonction spéciale dans le module EXE
delayLoadHelper;

Supprimez MyLib.dll de la section d'importation du module exécutable afin que le chargeur du système d'exploitation ne tente pas de charger implicitement cette bibliothèque au stade du chargement de l'application ;

Ajoutez une nouvelle section d'importation différée au fichier EXE avec une liste de fonctions importées de MyLib.dll ;

Convertir les appels de fonction de DLL en appels
delayLoadhelper.

Au stade de l'exécution de l'application, un appel à une fonction depuis une DLL est implémenté en appelant delayLoadHelper. Cette fonction, utilisant les informations de la section d'importation différée, appelle d'abord LoadLibrary, puis GetprocAddress. Après avoir reçu l'adresse de la fonction DLL, delayLoadHelper fait en sorte qu'à l'avenir cette fonction DLL soit appelée directement. Notez que chaque fonction d'une DLL est configurée individuellement la première fois qu'elle est appelée.

Usage DLL(bibliothèque de liens dynamiques) est répandu dans la programmation Windows. DLL en fait, une partie du code du fichier exécutable avec l'extension DLL... N'importe quel programme peut appeler DLL.

Avantage DLL est comme suit:

  • Réutilisation du code.
  • Partage de code entre les applications.
  • Code de fractionnement.
  • Améliorer la consommation des ressources dans Windows.

Création de DLL

au menu Déposer sélectionnez Nouveau -> Autre. Dans la boîte de dialogue de l'onglet Nouveau choisir Assistant DLL... Un module sera automatiquement créé - un modèle vide pour le futur DLL.

Syntaxe DLL

Construire DLL, sélectionnez Projet -> Construire Nom du projet .

Visibilité des fonctions et des procédures

Les fonctions et procédures peuvent être locales et exportées depuis DLL.

Local

Les fonctions et procédures locales peuvent être utilisées en interne DLL... Ils ne sont visibles qu'à l'intérieur de la bibliothèque et aucun programme ne peut les appeler de l'extérieur.

Exporté

Les fonctions et procédures exportées peuvent être utilisées en dehors de DLL... D'autres programmes peuvent appeler de telles fonctions et procédures.

Le code source ci-dessus utilise une fonction exportée. Le nom de la fonction suit un mot réservé Exportations.

Chargement de la DLL

Il existe deux types de chargement dans Delphi DLL:

Chargement statique

Lorsque l'application est lancée, elle est chargée automatiquement. Il reste en mémoire pendant toute l'exécution du programme. Très facile à utiliser. Ajoutez juste un mot externe après une déclaration de fonction ou de procédure.

Fonction SummaTotal (facteur : entier) : entier ; S'inscrire; externe "Example.dll" ;

Si DLL n'est pas trouvé, le programme continuera à s'exécuter.

chargé en mémoire selon les besoins. Sa mise en œuvre est plus compliquée car vous devez vous-même le charger et le décharger de la mémoire. La mémoire est utilisée de manière plus économique, l'application s'exécute donc plus rapidement. Le programmeur lui-même doit s'assurer que tout fonctionne correctement. Pour cela il vous faut :

  • Déclarez le type de la fonction ou de la procédure décrite.
  • Chargez la bibliothèque en mémoire.
  • Obtenir l'adresse d'une fonction ou d'une procédure en mémoire.
  • Appeler une fonction ou une procédure.
  • Déchargez la bibliothèque de la mémoire.

Déclaration de type qui décrit une fonction

type TSumaTotal = fonction (facteur : entier) : entier ;

Chargement de la bibliothèque

dll_instance : = LoadLibrary ("Example_dll.dll");

Obtenir un pointeur vers une fonction

@SummaTotal : = GetProcAddress (dll_instance, "SummaTotal");

Appel de fonction

Label1.Caption : = IntToStr (SummaTotal (5));

Décharger une bibliothèque de la mémoire

FreeLibrary (dll_instance);

Appel dynamique DLL nécessite plus de travail, mais plus facile à gérer les ressources en mémoire. Si vous devez utiliser DLL dans le programme, alors le chargement statique est préférable. N'oubliez pas d'utiliser le bloc essayez ... sauf et essayez ... enfin pour éviter les erreurs lors de l'exécution du programme.

Exportation de chaînes

Créé par DLL avec en utilisant Delphi, peut être utilisé dans des programmes écrits dans d'autres langages de programmation. Pour cette raison, nous ne pouvons utiliser aucun type de données. Les types qui existent dans Delphi peuvent ne pas exister dans d'autres langages. Il est conseillé d'utiliser des types de données natifs de Linux ou Windows. Notre DLL peut être utilisé par un autre programmeur qui utilise un langage de programmation différent.

Peut être utilisé chaînes et tableaux dynamiques v DLLécrit en Delphi, mais pour cela, vous devez connecter le module PartagerMem sectionner les usages v DLL et le programme qui l'utilisera. De plus, cette annonce doit être la première de la section les usages chaque dossier de projet.

Les types chaîne de caractères, comme nous le savons, C, C++ et d'autres langages n'existent pas, il est donc conseillé d'utiliser à leur place PChar.

Exemple de DLL

bibliothèque Example_dll; utilise SysUtils, Classes; var (déclarer les variables) k1 : entier ; k2 : entier ; (Nous déclarons la fonction) function SummaTotal (facteur : entier) : entier ; S'inscrire; facteur de début : = facteur * k1 + facteur ; facteur : = facteur * k2 + facteur ; résultat : = facteur ; finir; (Nous exportons la fonction pour une utilisation ultérieure par le programme) exporte SummaTotal; (Initialisation des variables) début k1 : = 2 ; k2 : = 5 ; finir.

Un exemple d'appel d'une fonction à partir d'une DLL

unité Unité1; l'interface utilise Windows, Messages, SysUtils, Variantes, Classes, Graphiques, Contrôles, Formulaires, Boîtes de dialogue, StdCtrls ; type TForm1 = classe (TForm) Button1 : TButton ; procédure Button1Click (Expéditeur : TObject) ; private (Déclarations privées) public (Déclarations publiques) end; tapez TSummaTotal = fonction (facteur : entier) : entier ; var Form1 : TForm1 ; implémentation ($ R * .dfm) procédure TForm1.Button1Click (Sender: TObject); var dll_instance : Manhandle ; SummaTotal : TSummaTotal ; begin instance_dll : = LoadLibrary ("Example_dll.dll"); @SummaTotal : = GetProcAddress (dll_instance, "SummaTotal"); Label1.Caption : = IntToStr (SummaTotal (5)); FreeLibrary (dll_instance); finir; finir.

LA CLOCHE

Il y a ceux qui ont lu cette nouvelle avant vous.
Abonnez-vous pour recevoir les derniers articles.
E-mail
Nom
Nom de famille
Comment voulez-vous lire La cloche
Pas de spam