Charger dynamiquement une fonction à partir d'une 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 qui contient une fonction qui renvoie un entier nommé funci ()
En utilisant ce code, je (pense) j'ai importé le .fichier dll dans le projet (il n'y a 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 quand j'essaie de compiler ceci .fichier cpp que je pense a importé le.dll, j'ai l'erreur suivante:
C:Documents and SettingsUserDesktopfgfdgonemore.cpp||In function 'int main()':|
C:Documents and SettingsUserDesktopfgfdgonemore.cpp|16|error: 'funci' was not declared in this scope|
||=== Build finished: 1 errors, 0 warnings ===|
Je connais un .dll est différent d'un fichier d'en-tête, donc je sais que je ne peux pas importer une fonction comme celle-ci, mais c'est le meilleur que je puisse trouver pour montrer que j'ai essayé.
Ma question Est, Comment puis-je utiliser le pointeur "hGetProcIDDLL" pour accéder à la fonction dans le .DLL.
J'espère que cette question a du sens et que je n'aboie pas encore un mauvais arbre.
2 réponses
LoadLibrary
ne fait pas ce que vous pensez qu'il fait. Il charge la DLL dans la mémoire du processus en cours, mais n'importe pas comme par magie les fonctions qui y sont définies! Cela ne serait pas possible, car les appels de fonction sont résolus par l'éditeur de liens au moment de la compilation alors que LoadLibrary
est appelé au moment de l'exécution (rappelez-vous que C++ est un langage typé statiquement).
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;
}
Aussi, vous devriez exporter votre fonction de la DLL correctement. Cela peut être fait comme ceci:
int __declspec(dllexport) __stdcall funci() {
// ...
}
Comme le note Lundin, il est recommandé de libérer le handle de la bibliothèque Si vous n'en avez pas besoin plus longtemps. Cela le fera décharger si aucun autre processus ne détient toujours un handle sur la même DLL.
En plus de la réponse déjà affichée, j'ai pensé que je devrais partager une astuce pratique que j'utilise pour charger toutes les fonctions DLL dans le programme via des pointeurs de fonction, sans écrire un appel GetProcAddress séparé pour chaque fonction. J'aime aussi appeler les fonctions directement comme tenté dans L'OP.
Commencez par définir un type de pointeur de fonction générique:
typedef int (__stdcall* func_ptr_t)();
Quels types sont utilisés ne sont pas vraiment importants. Maintenant, créez un tableau de ce type, qui correspond à la quantité 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 fonctions sous forme de chaînes. Créez donc un tableau similaire composé des noms de fonctions 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 a réussi, 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;
Maintenant, vous pouvez charger toutes les fonctions de la DLL avec la boucle pratique, mais appelez-les via le membre de l'union by_type
.
Mais bien sûr, c'est un peu lourd à taper quelque chose comme
functions.by_type.dll_add_ptr(1, 1);
chaque fois que vous voulez l'appeler une fonction.
Il s'avère que c'est la raison pour laquelle j'ai ajouté le postfix "ptr" aux noms: je voulais les garder différents des noms de fonctions réels. Nous pouvons maintenant lisser la syntaxe icky struct et obtenir les noms souhaités, en utilisant quelques 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, dont le type et les paramètres, comme s'ils étaient liés statiquement à votre projet:
int result = dll_add(1, 1);
Avertissement: strictement parlant, les conversions entre différents pointeurs de fonction ne sont pas définies par la norme C et ne sont pas sûres. Donc formellement, ce que je fais ici est un comportement indéfini. Cependant, dans le monde Windows, les pointeurs de fonction sont toujours de la même taille quel que soit leur type et les conversions entre eux sont prévisibles sur n'importe quelle version de Windows que j'ai utilisée.
En outre, il pourrait en théorie Y avoir un remplissage inséré dans l'Union / struct, ce qui faire tout échouer. Cependant, les pointeurs sont de la même taille que L'exigence d'alignement dans Windows. A static_assert
pour s'assurer que la structure / union n'a pas de remplissage peut être encore en ordre.