web-dev-qa-db-fra.com

Charger dynamiquement une fonction depuis un DLL

Je regarde un peu les fichiers .dll, je comprends leur utilisation et j'essaie de comprendre comment les utiliser.

J'ai créé un fichier .dll contenant une fonction qui renvoie un entier nommé funci ()

en utilisant ce code, j’ai (pense) avoir importé le fichier .dll dans le projet (il n’ya pas de plaintes):

#include <windows.h>
#include <iostream>

int main() {
  HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Documents and Settings\\User\\Desktop  \\fgfdg\\dgdg\\test.dll");

  if (hGetProcIDDLL == NULL) {
    std::cout << "cannot locate the .dll file" << std::endl;
  } else {
    std::cout << "it has been called" << std::endl;
    return -1;
  }

  int a = funci();

  return a;
}

# funci function 

int funci() {
  return 40;
}

Cependant, lorsque j'essaie de compiler ce fichier .cpp qui, à mon avis, a importé le fichier .dll, l'erreur suivante apparaît:

C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp||In function 'int main()':|
C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp|16|error: 'funci' was not     declared in this scope|
||=== Build finished: 1 errors, 0 warnings ===|

Je sais qu’un fichier .dll est différent d’un fichier d’en-tête, je sais donc que je ne peux pas; importer une fonction comme celle-ci, mais c’est le meilleur choix que je puisse faire pour montrer que j’ai essayé.

Ma question est, comment puis-je utiliser le pointeur "hGetProcIDDLL" pour accéder à la fonction dans le fichier .dll.

J'espère que cette question a du sens et que je n'aboie pas encore du mauvais arbre.

75
user969416

LoadLibrary ne fait pas ce que vous pensez. Il charge la DLL) dans la mémoire du processus en cours, mais not ​​importe magiquement les fonctions qui y sont définies! Cela ne serait pas possible, car les appels de fonction le sont résolu par l'éditeur de liens au moment de la compilation, tandis que LoadLibrary est appelé à l'exécution (rappelez-vous que C++ est un langage statiquement typé ).

Vous avez besoin d’une fonction WinAPI distincte pour obtenir l’adresse des fonctions chargées dynamiquement: GetProcAddress .

Exemple

#include <windows.h>
#include <iostream>

/* Define a function pointer for our imported
 * function.
 * This reads as "introduce the new type f_funci as the type: 
 *                pointer to a function returning an int and 
 *                taking no arguments.
 *
 * Make sure to use matching calling convention (__cdecl, __stdcall, ...)
 * with the exported function. __stdcall is the convention used by the WinAPI
 */
typedef int (__stdcall *f_funci)();

int main()
{
  HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Documents and Settings\\User\\Desktop\\test.dll");

  if (!hGetProcIDDLL) {
    std::cout << "could not load the dynamic library" << std::endl;
    return EXIT_FAILURE;
  }

  // resolve function address here
  f_funci funci = (f_funci)GetProcAddress(hGetProcIDDLL, "funci");
  if (!funci) {
    std::cout << "could not locate the function" << std::endl;
    return EXIT_FAILURE;
  }

  std::cout << "funci() returned " << funci() << std::endl;

  return EXIT_SUCCESS;
}

En outre, vous devriez exporter votre fonction à partir du DLL correctement. Ceci peut être fait comme ceci:

int __declspec(dllexport) __stdcall funci() {
   // ...
}

Comme le note Lundin, il est bon d’utiliser libérez la poignée de la bibliothèque si vous n’en avez pas besoin plus longtemps. Cela entraînera son déchargement si aucun autre processus ne contient toujours un descripteur sur la même DLL.

135
Niklas B.

En plus de la réponse déjà publiée, j’ai pensé partager une astuce pratique pour charger toutes les fonctions DLL dans le programme via des pointeurs de fonction, sans écrire d’appel GetProcAddress séparé pour chaque fonction). J'aime aussi appeler les fonctions directement comme dans l’opération.

Commencez par définir un type de pointeur de fonction générique:

typedef int (__stdcall* func_ptr_t)();

Les types utilisés ne sont pas vraiment importants. Créez maintenant un tableau de ce type, qui correspond au nombre de fonctions que vous avez dans la DLL:

func_ptr_t func_ptr [DLL_FUNCTIONS_N];

Dans ce tableau, nous pouvons stocker les pointeurs de fonction réels qui pointent dans l'espace mémoire DLL.

Le problème suivant est que GetProcAddress attend les noms de fonction sous forme de chaînes. Créez donc un tableau similaire comprenant les noms de fonction dans la DLL:

const char* DLL_FUNCTION_NAMES [DLL_FUNCTIONS_N] = 
{
  "dll_add",
  "dll_subtract",
  "dll_do_stuff",
  ...
};

Maintenant, nous pouvons facilement appeler GetProcAddress () dans une boucle et stocker chaque fonction dans ce tableau:

for(int i=0; i<DLL_FUNCTIONS_N; i++)
{
  func_ptr[i] = GetProcAddress(hinst_mydll, DLL_FUNCTION_NAMES[i]);

  if(func_ptr[i] == NULL)
  {
    // error handling, most likely you have to terminate the program here
  }
}

Si la boucle réussit, le seul problème que nous avons maintenant appelle les fonctions. Le pointeur de fonction typedef de plus tôt n'est pas utile, car chaque fonction aura sa propre signature. Cela peut être résolu en créant une structure avec tous les types de fonctions:

typedef struct
{
  int  (__stdcall* dll_add_ptr)(int, int);
  int  (__stdcall* dll_subtract_ptr)(int, int);
  void (__stdcall* dll_do_stuff_ptr)(something);
  ...
} functions_struct;

Et enfin, pour les connecter au tableau d’avant, créez une union:

typedef union
{
  functions_struct  by_type;
  func_ptr_t        func_ptr [DLL_FUNCTIONS_N];
} functions_union;

Vous pouvez maintenant charger toutes les fonctions de la DLL avec la boucle commode, mais appelez-les par l'intermédiaire du membre de l'union by_type.

Mais bien sûr, il est un peu lourd de taper quelque chose comme

functions.by_type.dll_add_ptr(1, 1); chaque fois que vous souhaitez appeler une fonction.

Il s’est avéré que c’est la raison pour laquelle j’ai ajouté le suffixe "ptr" aux noms: je voulais les garder différents des noms de fonctions réels. Nous pouvons maintenant affiner la syntaxe icky struct et obtenir les noms souhaités, en utilisant des macros:

#define dll_add (functions.by_type.dll_add_ptr)
#define dll_subtract (functions.by_type.dll_subtract_ptr)
#define dll_do_stuff (functions.by_type.dll_do_stuff_ptr)

Et voilà, vous pouvez maintenant utiliser les noms de fonction, avec le type et les paramètres corrects, comme s'ils étaient liés statiquement à votre projet:

int result = dll_add(1, 1);

Avertissement: À proprement parler, les conversions entre différents pointeurs de fonction ne sont pas définies par le standard C et ne sont pas sécuritaires. Donc formellement, ce que je fais ici est un comportement indéfini. Cependant, dans le monde Windows, les pointeurs de fonction ont toujours la même taille, quel que soit leur type, et les conversions entre eux sont prévisibles sur toutes les versions de Windows que j'ai utilisées.

En outre, il pourrait en théorie y avoir un remplissage inséré dans l'union/struct, ce qui entraînerait l'échec de tout Cependant, les pointeurs ont la même taille que l'exigence d'alignement de Windows. Un static_assert Pour s'assurer que la structure/union n'a pas de remplissage pourrait être en ordre.

29
Lundin