Existe-t-il un meilleur moyen de charger une DLL en C ++?

En ce moment, je fais quelque chose comme ça et cela semble désordonné si je finis par avoir beaucoup de fonctions que je veux référencer dans ma DLL. Existe-t-il un moyen plus efficace d’accéder aux fonctions sans avoir à créer un typedef pour chaque définition de fonction afin de comstackr et de charger correctement la fonction. Je veux dire les définitions de fonction sont déjà dans le fichier .h et je ne devrais pas avoir à les redéclarer après avoir chargé la fonction (ou est-ce que je le fais?) Existe-t-il une meilleure solution que d’utiliser LoadLibary? Je n’ai pas nécessairement besoin de cette fonction s’il existe un moyen de faire la même chose dans les parameters du projet Visual Studio 2005.


BHannan_Test_Class.h

#include "stdafx.h" #include  #ifndef BHANNAN_TEST_CLASS_H_ #define BHANNAN_TEST_CLASS_H_ extern "C" { // Returns n! (the factorial of n). For negative n, n! is defined to be 1. int __declspec (dllexport) Factorial(int n); // Returns true iff n is a prime number. bool __declspec (dllexport) IsPrime(int n); } #endif // BHANNAN_TEST_CLASS_H_ 

BHannan_Test_Class.cpp

 #include "stdafx.h" #include "BHannan_Test_Class.h" // Returns n! (the factorial of n). For negative n, n! is defined to be 1. int Factorial(int n) { int result = 1; for (int i = 1; i <= n; i++) { result *= i; } return result; } // Returns true iff n is a prime number. bool IsPrime(int n) { // Trivial case 1: small numbers if (n = 3. // Try to divide n by every odd number i, starting from 3 for (int i = 3; ; i += 2) { // We only have to try i up to the squre root of n if (i > n/i) break; // Now, we have i <= n/i < n. // If n is divisible by i, n is not prime. if (n % i == 0) return false; } // n has no integer factor in the range (1, n), and thus is prime. return true; } 

dll_test.cpp

 #include  typedef int (*FactorialPtr) (int); FactorialPtr myFactorial=NULL; // Tests factorial of negative numbers. TEST(FactorialTest, Negative) { HMODULE myDLL = LoadLibrary("BHannan_Sample_DLL.dll"); if(myDLL) { myFactorial = (FactorialPtr) GetProcAddress(myDLL,"Factorial"); if(myFactorial) { EXPECT_EQ(1, myFactorial(-5)); EXPECT_EQ(1, myFactorial(-1)); EXPECT_TRUE(myFactorial(-10) > 0); } FreeLibrary(myDLL); } } 

Après avoir créé votre fichier .dll, obtenez le fichier .lib à proximité et associez-le à votre application de test. Utilisez les fonctions telles qu’elles sont déclarées dans .h

Il y a un changement mineur à faire dans votre fichier d’en-tête:

 #ifdef EXPORTS_API #define MY_API_EXPORT __declspec (dllexport) #else #define MY_API_EXPORT __declspec (dllimport) #endif extern "C" { int MY_API_EXPORT Factorial(int n); // do the same for other functions } 

De cette façon, lorsque vous construisez votre DLL, vous définissez EXPORTS_API dans vos parameters de projet et les fonctions sont exscopes, dans l’application cliente, plus besoin de définir quoi que ce soit.

Dans le monde Windows, il y a (au moins) 4 façons d’utiliser les DLL:

  1. Liaison dynamic au moment de l’exécution (ce que vous faites maintenant)
  2. Liaison dynamic au moment du chargement (la méthode “typique” d’utilisation des DLL)
  3. Liaison dynamic à charge différée
  4. Transfert de DLL

Je n’ai pas à expliquer le lien dynamic à l’exécution puisque vous le faites déjà. Je choisis de ne pas expliquer le lien dynamic entre la charge différée et la description de ce qu’il est en termes généraux. Le chargement différé est essentiellement identique à la liaison dynamic au moment du chargement, sauf qu’il est effectué juste-à-temps au lieu d’être chargé à l’application. Ce n’est pas aussi utile ou aussi bénéfique que vous pourriez le penser, il est difficile à utiliser et difficile à coder. Alors n’allons pas là-bas, du moins pour le moment. DLL Forwarding est encore plus exotique que Delay-Loading – si exotique, je n’en avais jamais entendu parler jusqu’à ce que @mox le mentionne dans les commentaires. Je vous laisse lire le lien ci-dessus pour en savoir plus, mais il suffit de dire que DLL Forwarding est lorsque vous appelez une fonction exscope dans une DLL, mais cette demande est en fait transmise à une autre fonction dans une DLL différente.

Liaison dynamic au moment du chargement

C’est ce que je considérerais comme une liaison DLL Vanilla .

C’est ce à quoi la plupart des gens se réfèrent lorsqu’ils utilisent des DLL dans leurs applications. Vous venez d’ #include le fichier d’en-tête de la DLL et le lien vers le fichier LIB. Pas besoin de GetProcAddress() ni de créer des typedefs de pointeurs de fonctions. Voici comment cela fonctionne en quelques mots:

1) Vous obtenez généralement 3 fichiers: une DLL avec le code d’exécution, un fichier LIB et un fichier d’en-tête. Le fichier d’en-tête n’est qu’un fichier d’en-tête – il décrit toutes les fonctionnalités de la DLL que vous pouvez utiliser.

2) Vous écrivez votre application, #include le fichier d’en-tête de la DLL et passez des appels à ces fonctions, comme vous utiliseriez n’importe quelle fonction dans un fichier d’en-tête. Le compilateur connaît les noms des fonctions et des objects que vous utilisez car ils se trouvent dans le fichier d’en-tête de la DLL. Mais il ne sait pas encore où ils sont en mémoire. C’est là que le fichier LIB arrive …

3) Vous accédez aux parameters de l’éditeur de liens de votre projet et ajoutez une “dépendance de bibliothèque supplémentaire”, en spécifiant le fichier LIB. Le fichier LIB indique à l’éditeur de liens où les fonctions et les objects que vous utilisez depuis le fichier H résident en mémoire (en termes relatifs, pas en termes absolus, évidemment).

4) Comstackz votre application. Si vous avez tout configuré correctement, il devrait comstackr, lier et exécuter. Lorsque vous obtenez des erreurs d’éditeur de liens “références externes non résolues”, cela est dû au fait que les choses ne sont pas correctement définies. Vous pouvez ne pas avoir spécifié le chemin d’access correct au fichier LIB ou inclure d’autres fichiers LIB.

Les bibliothèques d’importation (.lib) simplifient l’utilisation des DLL dans le code utilisateur, voir par exemple ici pour un didacticiel de base.
Ils empêchent les utilisateurs de charger la DLL, en utilisant GetProcAddress() et les pointeurs de fonctions eux-mêmes – ils établissent un lien statique avec la bibliothèque d’importation au lieu de cela.

Pourquoi n’avez-vous pas VS pour générer une bibliothèque statique shim autour de votre DLL. De cette façon, tout ce que vous avez à faire est d’append une convention d’appel dans le fichier d’en-tête et d’append quelques directives de pré-procesor. La façon la plus simple de comprendre comment faire est de créer un nouveau projet DLL (Visual C ++> Projet Win32, Choose DLL Project, cochez Importer des symboles)

alt text http://img341.imageshack.us/img341/7048/dll.png

Utilisez le fichier d’en-tête principal comme exemple pour décorer vos classes avec la convention d’appel d’importation / exportation. Cette tête est un élément important car elle explique comment utiliser les fonctions et les classes déclarées ici:

 // The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are comstackd with the DLLTEST2_EXPORTS // symbol defined on the command line. this symbol should not be defined on any project // that uses this DLL. This way any other project whose source files include this file see // DLLTEST2_API functions as being imported from a DLL, whereas this DLL sees symbols // defined with this macro as being exported. #ifdef DLLTEST2_EXPORTS #define DLLTEST2_API __declspec(dllexport) #else #define DLLTEST2_API __declspec(dllimport) #endif // This class is exported from the dlltest2.dll class DLLTEST2_API Cdlltest2 { public: Cdlltest2(void); // TODO: add your methods here. }; extern DLLTEST2_API int ndlltest2; DLLTEST2_API int fndlltest2(void); 

Ensuite, dans le projet qui utilise cette DLL, incluez simplement le fichier d’en-tête et le fichier .lib que le projet DLL a généré. De cette façon, il charge automatiquement la DLL et vous pouvez utiliser toutes les fonctions comme si elles étaient liées de manière statique.

Lorsque vous construisez votre dll, vous devriez également obtenir un fichier lib avec lequel vous pouvez créer un lien. Cela va faire le gros du travail en arrière-plan pour vous. Incluez le fichier d’en-tête que vous avez créé, mais remplacez-le par dllimport au lieu de dllexport. Vous pouvez utiliser un define pour cela, de sorte que pour votre projet dll il utilise dllexport et tous les autres utilisateurs de cette définition utilisent dllimport.

Il vous suffit de faire une LoadLibrary manuelle et le typedef si vous souhaitez charger la DLL manuellement. Si vous suivez l’approche ci-dessus, votre exe échouera si la DLL n’est pas présente.

Vous pouvez également créer le projet dll dans une bibliothèque statique et le charger à la place, ce qui éliminera également ce problème mais augmentera la taille de votre exe. Ce n’est bien sûr pas une option si vous voulez vraiment que ce soit une DLL.

Vous pouvez GetProcAddress() lien vers les symboles de la DLL directement au lieu d’utiliser GetProcAddress() , qui obtient l’adresse d’une fonction lors de l’exécution.

Exemple d’extrait de fichier d’en-tête:

 #if defined(MY_LIB_STATIC) #define MY_LIB_EXPORT #elif defined(MY_LIB_EXPORTS) #define MY_LIB_EXPORT __declspec(dllexport) #else #define MY_LIB_EXPORT __declspec(dllimport) #endif #ifdef __cplusplus extern "C" { #endif int MY_LIB_EXPORT Factorial(int n); #ifdef __cplusplus } #endif 

Ensuite, dans BHannan_Test_Class.cpp , vous #define MY_LIB_EXPORTS avant d’inclure l’en-tête.

Dans dll_test.cpp vous incluez l’en-tête et utilisez simplement Factorial() comme vous utiliseriez une fonction normale. Lors de la liaison, vous souhaitez créer un lien vers la bibliothèque d’importation générant votre DLL. Cela rend les symboles de la DLL disponibles pour le code qui y est lié.

Bien sûr, vous n’avez pas besoin du typedef

 int (* myFactorial)(int) = 0; HMODULE myDLL = LoadLibrary("BHannan_Sample_DLL.dll"); if(myDLL) { myFactorial = reinterpret_cast( GetProcAddress(myDLL,"Factorial")); ... } 

ou, en exploitant l’initialisation de C ++ et l’idiome de test

 if ( HMODULE myDLL = LoadLibrary("BHannan_Sample_DLL.dll") ) { if ( int (* myFactorial)(int) = GetProcAddress ( myFactorial, myDLL, "Factorial" ) ) { ... } } 

compte tenu de cela pour ranger la répétition du type

 template  T GetProcAddress ( const T&, HMODULE mod, const char* name) { return reinterpret_cast (GetProcAddress(mod,name)); } 

Mais ne pas utiliser un typedef est généralement plus mauvais que mieux, car les types de pointeurs de fonctions de C sont un peu difficiles à obtenir à moins que vous les utilisiez régulièrement. (Si vous les utilisez régulièrement, votre logiciel peut être quelque peu inhabituel).

Les extensions Microsoft dllimport et le compilateur créent une bibliothèque statique qui effectue le chargement pour vous et fournit des trampolines ou des thunks, comme d’autres l’ont publié. Sauf si vous créez un système de plug-in qui ne sait pas quelle DLL il va charger, utilisez-le plutôt.