LA CLOCHE

Il y a ceux qui lisent cette actualité avant vous.
Abonnez-vous pour recevoir les derniers articles.
Email
Nom
Nom de famille
Comment voulez-vous lire The Bell
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 nom de compilation légèrement différent, vous ne pouvez donc pas simplement exporter des fonctions - méthodes de la classe C ++ puis utilisez-les dans le code de l'application client (ci-après, le client désigne l'application utilisant la DLL). Cependant, cela peut être fait à l'aide des interfaces disponibles à la fois pour la DLL et l'application cliente. Cette méthode est très puissante et élégante à la fois. 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 (Component Object Model) de Microsoft repose sur une idée similaire (plus des fonctionnalités supplémentaires, bien sûr). Cet article vous montrera comment utiliser l'approche "classe" à l'aide d'une interface de type COM au début (au moment de la compilation) et liaison tardive (pendant l'exécution du programme).

Si vous avez déjà travaillé avec une DLL, vous savez déjà que DLL a une fonction spéciale DllMain (). Cette fonction est similaire à WinMain ou main () en ce sens qu'il s'agit d'une sorte de point d'entrée DLL. Le système d'exploitation appelle automatiquement cette fonction lorsque 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 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 depuis 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 au démarrage du programme. Cependant, il est nécessaire qu'un fichier .lib (fichier bibliothèque) correspondant à cette DLL soit inclus dans le projet en cours de développement. Ce fichier définit tous les objets DLL exportés. Les déclarations peuvent contenir des fonctions ou des classes C régulières. 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 simple à utiliser, car tout est transparent. Cependant, vous devez avoir remarqué que le code client doit être recompilé chaque fois que le code DLL est modifié 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 de fonctions exportées à partir d'une DLL.

// \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // fichier .def BIBLIOTHEQUE myfirstdll.dll DESCRIPTION "Ma première DLL" EXPORTE MyFunction // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // En-tête DLL qui sera inclus dans code client bool MyFunction (int parms); // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // implémentation de fonction dans DLL bool MyFunction (int parms) (// fait tout ce qui est nécessaire ............)

Je pense qu'il est possible de ne pas dire que dans cet exemple une seule fonction 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 la DLL VisualC ++ AppWizard. Les commentaires inclus dans la liste suffisent amplement à comprendre comment tout cela fonctionne.

// \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // En-tête DLL à inclure dans le code client / * Bloc ifdef suivant - méthode standard création d'une macro qui facilite l'exportation de 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 dans lequel ce fichier est inclus voit les fonctions MYFIRSTDLL_API comme importées à partir d'une DLL, tandis que la DLL elle-même voit ces mêmes fonctions comme exportées. * / #ifdef MYFIRSTDLL_EXPORTS #define MYFIRSTDLL_API __declspec (dllexport) #else #define MYFIRSTDLL_API __declspec (dllimport) #endif // La classe est exportée depuis la classe test2.dll MYFIRSTDLL_Fll_API méthodes.); extern MYFIRSTDLL_API int nMyFirstDll; MYFIRSTDLL_API int fnMyFunction (void);

La clé MYFIRSTDLL_EXPORTS est définie lors de la compilation DLL, de sorte que le mot clé __declspec (dllexport) est remplacé 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 à partir de la DLL.

Dans les deux cas, tout ce que le client a à faire est 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 étaient définis et mis en œuvre localement dans le projet. Examinons 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, de sorte que 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, disons que 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 quoi que ce soit. Vous pouvez également placer le code DirectX dans une DLL et le 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, en prenant de la mémoire, et en réalité vous n'en avez besoin que d'une seule). Et enfin, à mon avis, meilleure idée consiste à 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 une 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éfinir 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 transmettant le nom de fichier DLL à la fonction Win32 LoadLibrary (). Cette fonction renvoie 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 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.

// \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // fichier .def BIBLIOTHEQUE myfirstdll.dll DESCRIPTION "Ma première DLL" EXPORTE MyFunction // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d / * Implémentation de fonction dans DLL * / bool MyFunction (int parms) (// faire quelque chose ............) // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // 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 dénomination C * / extern "C" bool MyFunction (int parms); typedef bool (* MYFUNCTION) (int parms); MYFUNCTION pfnMyFunc \u003d 0; // pointeur vers MyFunction HINSTANCE hMyDll \u003d :: LoadLibrary ("myfirstdll.dll"); if (hMyDll! \u003d NULL) (// Détermine l'adresse de la fonction pfnMyFunc \u003d (MYFUNCTION) :: GetProcAddress (hMyDll, "MyFunction"); // En cas d'échec, décharge la DLL if (pfnMyFunc \u003d\u003d 0) (:: FreeLibrary (hMyDll) ; return;) // Appel de la fonction bool result \u003d pfnMyFunc (parms); // Décharge la DLL si nous n'en avons 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 une liaison tardive est utilisée, il n'y a pas de moyen direct d'importer des classes à partir d'une DLL, nous devons donc implémenter la «fonctionnalité» de la classe en utilisant une interface qui contient toutes les fonctions publiques sauf le constructeur et le 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. Désormais, 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 autres fonctions 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.

// \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // .def file LIBRARY myinterface.dll DESCRIPTION "implémente l'interface I_MyInterface EXPORTS GetMyInterface FreeMyInterface // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // 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) \u003d 0; virtual bool Release () \u003d 0; virtual void DoStuff () \u003d 0;); / * Déclarations des fonctions exportées Dll et définitions de type des pointeurs de fonction pour faciliter le chargement et la manipulation des fonctions Notez le préfixe extern "C", qui indique au compilateur d'utiliser les 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);) // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Implémentation de l'interface en Dll // MyInterface.h class CMyClass: public I_MyInterface (public: bool Init (int parms); bool Release (); void DoStuff (); CMyClass (); ~ CMyClass (); // tous les autres membres de la classe ............ private: // tous les membres de la classe ............); // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Fonctions exportées qui créent et détruisent l'interface // Dllmain.h HRESULT GetMyInterface (I_MyInterface ** pInterface) (if (! * PInterface) (* pInterface \u003d new CMyClass; return S_OK;) return E_FAIL;) HRESULT FreeMyInterface (I_MyInterface ** pInterface) (if (! * PInterface) return E_FAIL; delete * pInterface; * pInterface \u003d 0; return S_OK;) // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Code client // Déclarations d'interface et appels de fonction GETINTERFACE pfnInterface \u003d 0; // pointeur vers la fonction GetMyInterface I_MyInterface * pInterface \u003d 0; // pointeur vers la structure MyInterface HINSTANCE hMyDll \u003d :: LoadLibrary ("myinterface.dll"); if (hMyDll! \u003d NULL) (// Détermine l'adresse de la fonction pfnInterface \u003d (GETINTERFACE) :: GetProcAddress (hMyDll, "GetMyInterface"); // Décharge la DLL si l'opération précédente a échoué if (pfnInterface \u003d\u003d 0) (:: FreeLibrary (hMyDll); return;) // Appel de la fonction HRESULT hr \u003d pfnInterface (& pInterface); // Décharge en cas d'échec if (FAILED (hr)) (:: FreeLibrary (hMyDll); return;) // L'interface est chargée, vous pouvez appeler fonctions pInterface-\u003e Init (1); pInterface-\u003e DoStuff (); pInterface-\u003e Release (); // Libère l'interface FREEINTERFACE pfnFree \u003d (FREEINTERFACE) :: GetProcAddress (hMyDll, "FreeMyInterface"); if (pfnFree! \u003d 0) pfnFree (& hMyDll); // Décharger DLL :: FreeLibrary (hMyDll); )

Ces informations suffisent pour que vous ressentiez toute la convivialité 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, et OS / 2 - dépendent également des DLL pour fournir une grande partie de leurs fonctionnalités.

Jetons un coup d'œil à un certain nombre d'aspects de la création et de l'utilisation des DLL:

  • comment lier statiquement des DLL
  • comment charger dynamiquement des DLL;
  • comment créer des DLL;
  • comment créer des extensions DLL MFC.

Utilisation de la DLL

Presque impossible à créer application Windowsqui 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 dans des bibliothèques. Cependant, contrairement à leurs cousins \u200b\u200bstatiques (fichiers .lib), les DLL ne sont pas directement liées aux exécutables à l'aide de l'éditeur de liens. Le fichier exécutable contient uniquement des informations sur leur emplacement. Au moment de l'exécution du programme, la bibliothèque entière 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 et également 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 régulière et statique. Bien sûr, si les fonctions qu'il contient ne seront utilisées que 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 pendant la phase de liaison. Le système d'exploitation contrôle le chargement de la DLL pendant l'exécution du programme. Cependant, la DLL peut être chargée explicitement ou dynamiquement au cours de l'application.

Importer des bibliothèques

Lorsqu'une DLL est liée statiquement, le nom du fichier .lib est défini parmi les autres paramètres de l'éditeur de liens dans ligne de commande ou dans l'onglet Lien de la boîte de dialogue Paramètres du projet dans Developer Studio. Cependant le fichier .lib utilisé lors de la connexion implicite d'une DLL, n'est pas une bibliothèque statique standard. 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 sur les méthodes de leur création plus tard. 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, alors les programmeurs n'auraient pas à se soucier de la correspondance des 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 écrit à l'aide de diverses bibliothèques sans C ++.

Par défaut, dans Visual C ++, les interfaces de fonction sont conformes aux 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. Le changement de nom permet à l'éditeur de liens de différencier les fonctions surchargées, c'est-à-dire fonctions avec le même nom mais des listes d'arguments différentes. Cependant, l'ancienne bibliothèque C manque de fonctions nommées étendues.

Bien que toutes les autres règles pour appeler une fonction en C soient identiques à celles pour appeler 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 les 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 extern "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 en tirer avec seulement trois lignes:

Extern "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 doivent 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, généralement tout cela est 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 qui sont 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 dans lequel se trouve le fichier EXE.
  • Le répertoire actuel 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'à ce qu'elle se termine. L'application peut désormais accéder aux fonctions contenues dans la DLL.

Chargement et déchargement dynamiques de DLL

Au lieu d'avoir Windows lié 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 lors de la création de l'application). Plus précisément, vous pouvez définir la DLL 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 exécutent différentes actions. Par exemple, une application conçue pour transférer des données indépendamment peut décider au moment de l'exécution de charger la DLL pour TCP / IP ou un autre protocole.

Chargement d'une DLL standard

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 est effectuée à l'aide de la fonction :: LoadLibraryqui a un seul argument - le nom du module à charger. Le fragment correspondant du programme devrait ressembler à ceci:

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

Windows considère que dll est 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 trouver le fichier. Sinon, Windows recherchera le fichier de la même manière que dans le cas des DLL lié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 est 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 :: LoadLibraryrenvoie 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 produisant une valeur int typedef int (WINAPI * PFN_MyFunction) (char *); :: PFN_MyFunction pfnMyFunction;

Ensuite, vous devriez obtenir un 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 \u003d :: LoadLibrary ("MaDLL"); pfnMyFunction \u003d (PFN_MyFunction) :: GetProcAddress (hMyDll, "MyFunction"); :: int iCode \u003d (* pfnMyFunction) ("Bonjour");

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

Vous pouvez également faire référence à la fonction par l'ordinal par lequel 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 \u003d (PFN_MyFunction) :: GetProcAddress (hMyDll, MAKEINTRESOURCE (1));

Après avoir fini de travailler avec la bibliothèque de liens dynamiques, elle peut être déchargée de la mémoire de processus à l'aide de la fonction :: FreeLibrary:

:: FreeLibrary (hMyDll);

Chargement des extensions DLL MFC

Lors du chargement d'extensions MFC pour les DLL (qui sont détaillées ci-dessous) au lieu de fonctions LoadLibraryet FreeLibraryles fonctions sont utilisées AfxLoadLibrary et AfxFreeLibrary... Ces derniers sont presque 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 altéré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 LoadLibraryet placez la DLL en mémoire. Puis en utilisant la fonction AfxSetResourceHandle vous devez préparer la fenêtre du programme pour recevoir les 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.

Commentaire. Utilisation de la fonction LoadLibrary vous pouvez également charger des fichiers exécutables en mémoire (ne les exécutez pas pour exécution!). Le descripteur exécutable peut ensuite être utilisé lors de l'appel de fonctions TrouverResourceet LoadResource pour rechercher et charger des ressources d'application. Déchargez les modules de la mémoire en utilisant également la fonction FreeLibrary.

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 macro constante est définie dans le fichier d'en-tête EXPORTATION... L'utilisation de ce mot-clé lors de la définition d'une fonction dans une bibliothèque de liens dynamiques permet à l'éditeur de liens d'indiquer à l'éditeur de liens que la fonction est disponible pour une utilisation par d'autres programmes, l'amenant à 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:

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

Le fichier de bibliothèque est également légèrement différent des fichiers C normaux 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 pour pouvoir appeler ses fonctions, sa valeur de retour doit être TRUE:

MyDLL.c #include #include "MyDLL.h" int WINAPI DllMain (HINSTANCE hInstance, DWORD fdReason, PVOID pvReserved) (return 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 \u003d MyFunction ("Hello"); return 0;)

Ce programme ressemble à programmes réguliers pour Windows, ce qu'il est vraiment. Cependant, il convient de noter qu'en plus d'appeler la fonction MyFunction à partir de la bibliothèque DLL, son texte source inclut é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 bibliothèque d'importation MyDLL.lib (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 fonction MyFunction, qui se trouve dans ce fichier. Le fichier MyApp.exe nécessite le fichier MyDLL.dll pour s'exécuter.

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é à la bibliothèque MyDLL.dll de la même manière qu'à 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 \u003d LoadLibrary ("MyDLL")) \u003d\u003d NULL) return 1; PFN_MyFunction_function; pf; , "MaFonction"); int iCode \u003d (* pfnMyFunction) ("Bonjour"); FreeLibrary (hMyDll); return 0;)

Création de DLL

Maintenant que vous savez comment les DLL fonctionnent dans les applications, voyons comment les créer. Lors du développement d'une application, les fonctions auxquelles accèdent plusieurs processus doivent être placées dans une DLL. Cela permet une utilisation plus efficace de la mémoire sous 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 source devront être ajoutés au projet manuellement.

Si vous prévoyez d'utiliser pleinement fonctionnalité MFC tels que des documents et des vues, ou l'intention 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'attribuer 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.

Fonction DllMain

La plupart des DLL sont simplement des collections de fonctions pratiquement indépendantes les unes des autres, 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 permet d'initialiser et de nettoyer les DLL. Elle a remplacé des fonctions LibMain et WEPutilisé 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 \u003d TRUE; switch (dwReason) (case DLL_PROCESS_ATTACH: // Initialisation du processus. Break; case DLL_THREAD_ATTACH: // Initialiser DLL_ATTACH_DLL: // Initialiser_ATTACH: // Initialiser DLL_ATTACH: // Initialiser_attach flow structures.break; case DLL_PROCESS_DETACH: // Effacement du processus structures.break;) if (bAllWentWell) return TRUE; sinon return FALSE;)

Fonction DllMainappelé dans plusieurs cas. La raison de son 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).

À la fin du processus avec la fonction DLL 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 des processus et des threads dont la DLL a besoin doivent être effectuées en fonction de la valeur dwReason, comme indiqué 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 des bibliothèques. L'initialisation de thread est utilisée pour configurer des modes spécifiques à ce thread uniquement, 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 fonctionnent avec des ressources DLL, il serait évidemment utile de conserver le descripteur hInst quelque part dans un endroit secret et de l'utiliser lors du chargement de ressources depuis la DLL. Le pointeur IpReserved est réservé aux en utilisant Windows... Par conséquent, la demande ne doit pas le revendiquer. Vous ne pouvez 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 TRUE. Si une erreur se produit, FALSE est renvoyé et toute autre action est terminée.

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

Exportation de fonctions depuis DLL

Pour qu'une application accède aux fonctions DLL, chaque fonction doit occuper une ligne dans la table des fonctions exportées de la DLL. Il existe deux façons 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 deuxième méthode, qui fonctionne avec les fichiers de définition de module (.def) et vous donne un meilleur contrôle sur le 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 premières versions Windows dans Win32 ne s'applique plus. Comme le montre 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:

MyDLL.def BIBLIOTHEQUE "MyDLL" DESCRIPTION "MyDLL - exemple de DLL" EXPORTE MyFunction @ 1

Dans la ligne d'exportation d'une fonction, vous pouvez spécifier son numéro ordinal en le précédant du symbole @. Ce numéro sera alors utilisé pour désigner GetProcAddress (). 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 sur 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 les numéros de séquence. Les applications qui chargent dynamiquement des DLL devront passer GetProcAddressun 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, être différent:

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

Exporter des classes

Créer un fichier .def pour exporter même des classes simples à partir d'une bibliothèque dynamique peut être délicat. Vous devrez exporter explicitement 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, en particulier _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'exportation lui-même. fonctions nécessairespermettant à 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 traitaient 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 des tâches. Le placement de bibliothèques dynamiques dans la 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 le charge. Cela signifie qu'une bibliothèque de liens dynamiques ne peut pas être partagée en mémoire partagée comme elle l'était dans Winl6.

Cependant, 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 la bibliothèque.

Supposons que vous ayez un tableau d'entiers qui devraient être utilisés par tous les processus chargeant cette DLL. Cela 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 C d'exécution de marquer la nouvelle partition comme lecture / écriture / partage.

Compilation DLL complète

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 le commutateur / 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 l'option / DLL explicitement sur la ligne de commande de l'éditeur de liens.

Il existe un certain nombre de modes spéciaux pour MFC liés à l'utilisation de la bibliothèque de liens dynamiques MFC. La section suivante est consacrée à ce numéro.

DLL et MFC

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

Il existe deux niveaux d'utilisation de la structure MFC dans une DLL. Le premier est une bibliothèque de liens dynamiques basée sur MFC standard, DLL MFC (DLL MFC standard). Il peut utiliser MFC, mais il ne peut pas transmettre de pointeurs vers des objets MFC entre les DLL et les applications. Le deuxième niveau est implémenté dans extensions MFC dynamiques (DLL d'extensions MFC). L'utilisation de ce type de bibliothèque de liens dynamiques nécessite un effort de configuration supplémentaire, mais vous permet d'échanger librement des pointeurs vers des objets MFC entre la DLL et l'application.

DLL MFC communes

Les DLL MFC standard 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 créées au-dessus de MFC. Vous pouvez utiliser MFC dans les DLL standard comme vous le souhaitez, notamment en créant de nouvelles classes dans la DLL en fonction des classes MFC et en les exportant vers des applications.

Toutefois, les DLL standard ne peuvent pas échanger de 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 standard à l'aide de AppWizard, un nouveau projet de type Assistant d'application MFC (dll)... Dans la première boîte de dialogue de l'Assistant Application, vous devez sélectionner l'un des modes pour les bibliothèques dynamiques communes: «DLL régulière avec MFC lié statistiquement» 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 par la bibliothèque auxquelles on accède directement à partir d'applications doivent indiquer à MFC les informations d'état à utiliser. Dans une DLL MFC normale 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 pendant 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 une collection 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 l'échange gratuit de pointeurs d'objet MFC entre votre 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 Application pour taper Assistant d'application MFC (dll) et à l'étape 1, activez le mode «DLL d'extension MFC». Cela attribuera 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 DLL. Veuillez noter que les bibliothèques dynamiques de ce type ne contiennent pas et ne doivent pas contenir d'objets dérivés de CWinApp.

Initialisation des extensions dynamiques

Pour «s'intégrer» dans la structure 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 générée par l'AppWizard.

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

La partie la plus importante de cette fonction est l'appel AfxInitExtensionModule... Il s'agit de l'initialisation d'une bibliothèque de liens dynamiques, ce qui lui permet de fonctionner correctement dans le cadre d'une infrastructure MFC. Les arguments de cette fonction sont le descripteur 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 travailler.

Chargement d'extensions MFC dynamiques

À partir de la version 4.0, MFC permet le chargement et le déchargement dynamiques des 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 tant que paramètre. Pour cela dans le texte DllMain vous devez ajouter les lignes suivantes.

Si (dwReason \u003d\u003d DLL_PROCESS_DETACH) (AfxTermExtensionModule (MyExtDLL);)

Rappelez-vous également que la nouvelle DLL est une extension dynamique et doit être chargée et déchargée dynamiquement, en utilisant les fonctions AfxLoadLibrary et AfxFreeLibrary,mais non LoadLibrary et FreeLibrary.

Exporter des fonctions à partir d'extensions dynamiques

Voyons maintenant comment les fonctions et les classes d'une extension dynamique sont exportées 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, telles 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 façons de charger une DLL: la liaison explicite et la liaison implicite.

Avec la liaison implicite, les fonctions de la DLL chargée sont ajoutées à 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 inclut 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 limitations:

toutes les DLL connectées sont toujours chargées, même si pendant toute la session le programme n'accède à 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 est introuvable" (ou quelque chose du genre) - 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, sortie de tableaux, graphiques, formules et autres composants mineurs, mais si ces DLL sont chargées avec un lien implicite - que vous le vouliez ou non, vous devez 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; dans le répertoire système% Windows% System%; 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é du code. Le programmeur lui-même devra s'occuper du chargement de la DLL et de la connexion des fonctions exportées (sans oublier le contrôle des erreurs, sinon un jour le système se fige). D'autre part, la liaison explicite permet aux DLL d'être chargées 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 spécifiez 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 toutes celles trouvées à 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 de DLL implicite. Pour créer une application qui repose sur le chargement DLL implicite, vous devez avoir:

La bibliothèque comprend un fichier avec des 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. En utilisant les modules objet et le fichier LIB, ainsi que la prise en compte des liens vers les identifiants importés, l'éditeur de liens (linker, linker) forme 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 y projette un module exécutable;

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

Une DLL peut importer des fonctions et des variables à partir 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. En conséquence, l'initialisation du processus peut prendre beaucoup de temps.

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

Les inconvénients du chargement implicite sont que la bibliothèque doit être chargée même si ses fonctions ne seront pas appelées et, par conséquent, la bibliothèque doit être requise lors de la liaison.

Le chargement explicite de DLL élimine les inconvénients mentionnés ci-dessus au prix d'une certaine complexité du code. Le programmeur lui-même doit s'occuper du chargement de la DLL et de la connexion des fonctions exportées. D'autre part, 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 DLL à l'aide d'une fonction LoadLibrary(ou son équivalent étendu LoadLibraryEx).En cas de chargement réussi, la fonction renvoie un descripteur hLib de type HMODULE, qui permet un accès supplémentaire à cette DLL.

2. Appels de fonction GetProcAddresspour obtenir des pointeurs vers les fonctions requises ou d'autres objets. En tant que premier paramètre, la fonction GetProcAddressreç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 FreeLibrary.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 fourni pour le cas où la même DLL après un certain temps est à nouveau nécessaire par un processus. Mais s'il y a des problèmes avec la RAM, Windows supprime tout d'abord les bibliothèques libérées de la mémoire.

Envisagez de charger paresseusement une DLL. Une DLL à chargement différé est une DLL implicitement liée qui n'est pas chargée tant que le code ne fait pas référence à un identificateur exporté à partir de celle-ci. Ces 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 distribuant la charge DLL pendant 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é. Quand on se réfère à un inexistant

la fonction peut être fournie avec un avertissement à l'utilisateur.

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

Ces paramètres forcent 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 à partir d'une DLL est implémenté en appelant delayLoadHelper. Cette fonction, en 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.

En utilisant DLL (bibliothèque de liens dynamiques) est répandue 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élioration de la consommation des ressources dans Windows.

Création de DLL

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

Syntaxe DLL

Construire DLL, sélectionnez Projet -\u003e 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 DLL

Il existe deux types de chargement dans Delphi DLL:

Chargement statique

Il se charge automatiquement au démarrage de l'application. Il reste en mémoire tout au long de l'exécution du programme. Utilisation très simple. 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 un DLL est introuvable, le programme continuera à s'exécuter.

chargé en mémoire au besoin. Son implémentation 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, de sorte que l'application s'exécute plus rapidement. Le programmeur lui-même doit s'assurer que tout fonctionne correctement. Pour cela, vous avez besoin de:

  • Déclarez le type de la fonction ou de la procédure décrite.
  • Chargez la bibliothèque en mémoire.
  • Obtenez l'adresse d'une fonction ou d'une procédure en mémoire.
  • Appelez 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 \u003d fonction (facteur: entier): entier;

Chargement de la bibliothèque

dll_instance: \u003d LoadLibrary ("Example_dll.dll");

Obtenir un pointeur vers une fonction

@SummaTotal: \u003d GetProcAddress (dll_instance, "SummaTotal");

Appel de fonction

Label1.Caption: \u003d 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 ... enfinpour éviter les erreurs lors de l'exécution du programme.

Exporter des chaînes

Créé par DLL de 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é cordes et tableaux dynamiques dans DLLécrit en Delphi, mais pour cela, vous devez connecter le module PartagerMem à la section les usages dans DLL et le programme qui l'utilisera. De plus, cette annonce doit être la première de la section les usages chaque fichier de projet.

Les types chaîne, comme nous le savons, C, C ++ et d'autres langages n'existent pas, il est donc conseillé de les 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 (factor: integer): integer; S'inscrire; facteur de début: \u003d facteur * k1 + facteur; facteur: \u003d facteur * k2 + facteur; résultat: \u003d facteur; fin; (Nous exportons la fonction pour une utilisation ultérieure par le programme) exporte SummaTotal; (Initialisation des variables) begin k1: \u003d 2; k2: \u003d 5; fin.

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

unit Unit1; l'interface utilise Windows, Messages, SysUtils, Variantes, Classes, Graphiques, Contrôles, Formulaires, Dialogues, StdCtrls; type TForm1 \u003d classe (TForm) Button1: TButton; procédure Button1Click (Sender: TObject); privé (déclarations privées) public (déclarations publiques) fin; type TSummaTotal \u003d fonction (facteur: entier): entier; var Form1: TForm1; procédure d'implémentation ($ R * .dfm) TForm1.Button1Click (Sender: TObject); var dll_instance: Thandle; SummaTotal: TSummaTotal; begin dll_instance: \u003d LoadLibrary ("Example_dll.dll"); @SummaTotal: \u003d GetProcAddress (dll_instance, "SummaTotal"); Label1.Caption: \u003d IntToStr (SummaTotal (5)); FreeLibrary (dll_instance); fin; fin.

LA CLOCHE

Il y a ceux qui lisent cette actualité avant vous.
Abonnez-vous pour recevoir les derniers articles.
Email
Nom
Nom de famille
Comment voulez-vous lire The Bell
Pas de spam