27 votes

Partage d'une variable globale/statique entre un processus et une DLL

J'aimerais partager une variable statique/globale uniquement entre un processus et une dll qui est invoquée par le processus. L'exe et la dll sont dans le même espace d'adressage mémoire. Je ne veux pas que la variable soit partagée entre d'autres processus.


Élaboration du problème :

Disons qu'il existe une variable statique/globale x en a.cpp . Les deux exe foo.exe et la dll bar.dll ont a.cpp donc la variable x est dans les deux images.

Ahora, foo.exe charge dynamiquement (ou statiquement) bar.dll . Le problème est alors de savoir si la variable x est partagé par l'exe et la dll, ou non.

Dans Windows, ces deux gars jamais partager le x : l'exe et la dll auront une copie séparée de x . Cependant, sous Linux, l'exe et la dll partagent la variable x .

Malheureusement, je veux le comportement de Linux. J'ai d'abord envisagé d'utiliser pragma data_seg sur Windows. Cependant, même si je configure correctement le segment de données partagé, foo.exe y bar.dll ne partage jamais le x . Rappelons que bar.dll est chargé dans l'espace d'adressage de foo.exe . Cependant, si je lance une autre instance de foo.exe entonces x est partagé. Mais, je ne veux pas x pour être partagé par différents processus. Ainsi, en utilisant data_seg a échoué.

Je peux utiliser un fichier mappé en mémoire en créant un nom unique entre exe et dll, ce que j'essaie maintenant.


Deux questions :

  1. Pourquoi le comportement de Linux et de Windows est-il différent ? Quelqu'un peut-il nous en dire plus à ce sujet ?
  2. Quel serait le moyen le plus simple de résoudre ce problème sous Windows ?

12voto

James Caccese Points 730

Pour obtenir le comportement de linux où le programme principal et une dll partagent le même x vous pouvez exporter cette variable soit à partir de la dll, soit à partir du programme principal. L'autre module doit importer cette variable.

Pour ce faire, vous utilisez les fichiers DEF ( voir la documentation de microsoft ), ou en marquant les utilisations avec la variable avec __declspec(dllexport) où il est défini, et __declspec(dllimport) dans tout autre module, il est utilisé ( voir la documentation de microsoft ). C'est la même chose que la façon dont toute fonction, tout objet ou toute variable est partagé entre les modules dans Windows.

Dans le cas où vous souhaitez qu'un programme charge une bibliothèque au moment de l'exécution, mais que le programme principal doive utiliser la variable avant que la bibliothèque ne soit chargée, le programme doit exporter la variable et la dll doit l'importer. Il y a ici un petit problème de poule et d'œuf, car la dll dépend du programme principal, et le programme principal dépend de la dll. Voir http://www.lurklurk.org/linkers/linkers.html#wincircular

J'ai écrit un exemple de la façon dont vous pouvez le faire en utilisant le compilateur de Microsoft et mingw (gcc sous Windows), y compris toutes les différentes façons dont un programme et une bibliothèque peuvent se lier l'un à l'autre (statiquement, dll chargée au démarrage du programme, dll chargée pendant l'exécution).

main.h

#ifndef MAIN_H
#define MAIN_H

// something that includes this
// would #include "linkage_importing.h"
// or #include "linkage_exporting.h"
// as appropriate

#ifndef EXPLICIT_MAIN
LINKAGE int x;
#endif // EXPLICIT_MAIN

#endif // MAIN_H

main.c

#ifdef EXPLICIT_DLL
#include "dyn_link.h"
#endif // EXPLICIT_DLL
#include <stdio.h>
#include "linkage_exporting.h"
#include "main.h"
#include "linkage_importing.h"
#include "dll.h"

FNCALL_DLL get_call_dll(void);

int main(int argc, char* argv[])
{
   FNCALL_DLL fncall_dll;
   fncall_dll = get_call_dll();
   if (fncall_dll)
   {
      x = 42;
      printf("Address of x as seen from main() in main.c: %p\n", &x);
      printf("x is set to %i in main()\n", x);
      fncall_dll();
      // could also be called as (*fncall_dll)();
      // if you want to be explicit that fncall_dll is a function pointer
      printf("Value of x as seen from main() after call to call_dll(): %i\n", x);
   }
   return 0;
}

FNCALL_DLL get_call_dll(void)
{
#ifdef EXPLICIT_DLL
   return get_ptr("dll.dll", "call_dll");
#else
   return call_dll;
#endif // EXPLICIT_DLL
}

dll.h

#ifndef DLL_H
#define DLL_H

// something that includes this
// would #include "linkage_importing.h"
// or #include "linkage_exporting.h"
// as appropriate

// declaration of type to hold a
// pointer to the function
typedef void(*FNCALL_DLL)(void);

#ifndef EXPLICIT_DLL
LINKAGE void call_dll(void);
#endif // EXPLICIT_DLL

#endif // DLL_H

dll.c

#ifdef EXPLICIT_MAIN
#include "dyn_link.h"
#endif // EXPLICIT_MAIN
#include <stdio.h>
#include "linkage_importing.h"
#include "main.h"
#include "linkage_exporting.h"
#include "dll.h"

int* get_x_ptr(void);

LINKAGE void call_dll(void)
{
   int* x_ptr;
   x_ptr = get_x_ptr();
   if (x_ptr)
   {
      printf("Address of x as seen from call_dll() in dll.c: %p\n", x_ptr);
      printf("Value of x as seen in call_dll: %i()\n", *x_ptr);
      *x_ptr = 31415;
      printf("x is set to %i in call_dll()\n", *x_ptr);
   }
}

int* get_x_ptr(void)
{
#ifdef EXPLICIT_MAIN
   return get_ptr("main.exe", "x");   // see note in dyn_link.c about using the main program as a library
#else
   return &x;
#endif //EXPLICIT_MAIN
}

dyn_link.h

#ifndef DYN_LINK_H
#define DYN_LINK_H

// even though this function is used by both, we link it
// into both main.exe and dll.dll as necessary.
// It's not shared in a dll, because it helps us load dlls :)
void* get_ptr(const char* library, const char* object);

#endif // DYN_LINK_H

dyn_link.c

#include "dyn_link.h"
#include <windows.h>
#include <stdio.h>

void* get_ptr(const char* library, const char* object)
{
   HINSTANCE hdll;
   FARPROC ptr;
   hdll = 0;
   ptr = 0;

   hdll = LoadLibrary(library);
   // in a better dynamic linking library, there would be a
   // function that would call FreeLibrary(hdll) to cleanup
   //
   // in the case where you want to load an object in the main
   // program, you can use
   // hdll = GetModuleHandle(NULL);
   // because there's no need to call LoadLibrary on the
   // executable if you can get its handle by some other means.

   if (hdll)
   {
      printf("Loaded library %s\n", library);
      ptr = GetProcAddress(hdll, object);
      if (ptr)
      {
         printf("Found %s in %s\n", object, library);
      } else {
         printf("Could not find %s in %s\n", object, library);
      }
   } else {
      printf("Could not load library %s\n", library);
   }
   return ptr;
}

linkage_importing.h

// sets up some macros to handle when to use "__declspec(dllexport)",
// "__declspec(dllimport)", "extern", or nothing.

// when using the LINKAGE macro (or including a header that does):
//    use "#include <linkage_importing.h>" to make the LINKAGE macro
//    do the right thing for importing (when using functions,
//    variables, etc...)
//
//    use "#include <linkage_exporting.h>" to make the LINKAGE macro
//    do the right thing for exporting (when declaring functions,
//    variables, etc).
//
//    You can include either file at any time to change the meaning of
//    LINKAGE.

// if you declare NO_DLL these macros do not use __declspec(...), only
// "extern" as appropriate

#ifdef LINKAGE
#undef LINKAGE
#endif
#ifdef NO_DLL
   #define LINKAGE extern
#else
   #define LINKAGE extern __declspec(dllimport)
#endif

linkage_exporting.h

// See linkage_importing.h to learn how this is used
#ifdef LINKAGE
#undef LINKAGE
#endif
#ifdef NO_DLL
   #define LINKAGE
#else
   #define LINKAGE __declspec(dllexport)
#endif

construire mingw explicit both.sh

#! /bin/bash
echo Building configuration where both main
echo and dll link explicitly to each other
rm -rf mingw_explicit_both
mkdir -p mingw_explicit_both/obj
cd mingw_explicit_both/obj

# compile the source code (dll created with position independent code)
gcc -c -fPIC -DEXPLICIT_MAIN ../../dll.c
gcc -c -DEXPLICIT_DLL ../../main.c
gcc -c ../../dyn_link.c

#create the dll from its object code the normal way
gcc -shared -odll.dll dll.o dyn_link.o -Wl,--out-implib,libdll.a

# create the executable
gcc -o main.exe main.o dyn_link.o

mv dll.dll ..
mv main.exe ..
cd ..

construire mingw explicit dll.sh

#! /bin/bash
echo Building configuration where main explicitly
echo links to dll, but dll implicitly links to main
rm -rf mingw_explicit_dll
mkdir -p mingw_explicit_dll/obj
cd mingw_explicit_dll/obj

# compile the source code (dll created with position independent code)
gcc -c -fPIC ../../dll.c
gcc -c -DEXPLICIT_DLL ../../main.c
gcc -c ../../dyn_link.c

# normally when linking a dll, you just use gcc
# to create the dll and its linking library (--out-implib...)
# But, this dll needs to import from main, and main's linking library doesn't exist yet
# so we create the linking library for main.o
# make sure that linking library knows to look for symbols in main.exe (the default would be a.out)
gcc -omain.exe -shared main.o -Wl,--out-implib,main.a  #note this reports failure, but it's only a failure to create main.exe, not a failure to create main.a

#create the dll from its object code the normal way (dll needs to know about main's exports)
gcc -shared -odll.dll dll.o dyn_link.o main.a -Wl,--out-implib,libdll.a

# create the executable
gcc -o main.exe main.o dyn_link.o

mv dll.dll ..
mv main.exe ..
cd ..

construire mingw explicit main.sh

#! /bin/bash
echo Building configuration where dll explicitly
echo links to main, but main implicitly links to dll
rm -rf mingw_explicit_main
mkdir -p mingw_explicit_main/obj
cd mingw_explicit_main/obj

# compile the source code (dll created with position independent code)
gcc -c -fPIC -DEXPLICIT_MAIN ../../dll.c
gcc -c ../../main.c
gcc -c ../../dyn_link.c

# since the dll will link dynamically and explicitly with main, there is no need
# to create a linking library for main, and the dll can be built the regular way
gcc -shared -odll.dll dll.o dyn_link.o -Wl,--out-implib,libdll.a

# create the executable (main still links with dll implicitly)
gcc -o main.exe main.o -L. -ldll

mv dll.dll ..
mv main.exe ..
cd ..

construire mingw implicit.sh

#! /bin/bash
echo Building configuration where main and
echo dll implicitly link to each other
rm -rf mingw_implicit
mkdir -p mingw_implicit/obj
cd mingw_implicit/obj

# compile the source code (dll created with position independent code)
gcc -c -fPIC ../../dll.c
gcc -c ../../main.c

# normally when linking a dll, you just use gcc
# to create the dll and its linking library (--out-implib...)
# But, this dll needs to import from main, and main's linking library doesn't exist yet
# so we create the linking library for main.o
# make sure that linking library knows to look for symbols in main.exe (the default would be a.out)
gcc -omain.exe -shared main.o -Wl,--out-implib,main.a  #note this reports failure, but it's only a failure to create main.exe, not a failure to create main.a

# create the dll from its object code the normal way (dll needs to know about main's exports)
gcc -shared -odll.dll dll.o main.a -Wl,--out-implib,libdll.a

# create the executable (exe needs to know about dll's exports)
gcc -o main.exe main.o -L. -ldll

mv dll.dll ..
mv main.exe ..
cd ..

construire mingw static.sh

#! /bin/bash
echo Building configuration where main and dll
echo statically link to each other
rm -rf mingw_static
mkdir -p mingw_static/obj
cd mingw_static/obj

# compile the source code
gcc -c -DNO_DLL ../../dll.c
gcc -c -DNO_DLL ../../main.c

# create the static library
ar -rcs dll.a dll.o

# link the executable
gcc -o main.exe main.o dll.a

mv main.exe ../
cd ..

construire msvc explicit both.bat

@echo off
echo Building configuration where both main
echo and dll link explicitly to each other
rd /s /q win_explicit_both
md win_explicit_both\obj
cd win_explicit_both\obj

rem compile the source code
cl /nologo /c /DEXPLICIT_MAIN ..\..\dll.c
cl /nologo /c /DEXPLICIT_DLL ..\..\main.c
cl /nologo /c ..\..\dyn_link.c

rem create the dll from its object code the normal way
link /nologo /dll dll.obj dyn_link.obj

rem create the executable
link /nologo main.obj dyn_link.obj

move dll.dll ..\
move main.exe ..\
cd ..

construire msvc explicit dll.bat

@echo off
echo Building configuration where main explicitly
echo links to dll, but dll implicitly links to main
rd /s /q win_explicit_dll
md win_explicit_dll\obj
cd win_explicit_dll\obj

rem compile the source code
cl /nologo /c ..\..\dll.c
cl /nologo /c /DEXPLICIT_DLL ..\..\main.c
cl /nologo /c ..\..\dyn_link.c

rem normally when linking a dll, you just use the link command
rem that creates the dll and its linking library.
rem But, this dll needs to import from main, and main's linking library doesn't exist yet
rem so we create the linking library for main.obj
rem make sure that linking library knows to look for symbols in main.exe (the default would be main.dll)
lib /nologo /def /name:main.exe main.obj

rem create the dll from its object code the normal way (dll needs to know about main's exports)
link /nologo /dll dll.obj main.lib

rem create the executable
link /nologo main.obj dyn_link.obj

move dll.dll ..\
move main.exe ..\
cd ..

construire msvc explicit main.bat

@echo off
echo Building configuration where dll explicitly
echo links to main, but main implicitly links to dll
rd /s /q win_explicit_main
md win_explicit_main\obj
cd win_explicit_main\obj

rem compile the source code
cl /nologo /c /DEXPLICIT_MAIN ..\..\dll.c
cl /nologo /c ..\..\main.c
cl /nologo /c ..\..\dyn_link.c

rem since the dll will link dynamically and explicitly with main, there is no need
rem to create a linking library for main, and the dll can be built the regular way
link /nologo /dll dll.obj dyn_link.obj

rem create the executable (main still links with dll implicitly)
link /nologo main.obj dll.lib

move dll.dll ..\
move main.exe ..\
cd ..

construire msvc implicite.bat

@echo off
echo Building configuration where main and
echo dll implicitly link to each other
rd /s /q win_implicit
md win_implicit\obj
cd win_implicit\obj

rem compile the source code
cl /nologo /c ..\..\dll.c
cl /nologo /c ..\..\main.c

rem normally when linking a dll, you just use the link command
rem that creates the dll and its linking library.
rem But, this dll needs to import from main, and main's linking library doesn't exist yet
rem so we create the linking library for main.obj
rem make sure that linking library knows to look for symbols in main.exe (the default would be main.dll)
lib /nologo /def /name:main.exe main.obj

rem create the dll from its object code the normal way (dll needs to know about main's exports)
link /nologo /dll dll.obj main.lib

rem create the executable (exe needs to know about dll's exports)
link /nologo main.obj dll.lib

move dll.dll ..\
move main.exe ..\
cd ..

construire msvc static.bat

@echo off
echo Building configuration where main and dll
echo statically link to each other
rd /s /q win_static
md win_static\obj
cd win_static\obj

rem compile the source code
cl /nologo /DNO_DLL /c ..\..\dll.c
cl /nologo /DNO_DLL /c ..\..\main.c

rem create the static library
lib /nologo dll.obj

rem link the executable
link /nologo main.obj dll.lib

move main.exe ..\
cd ..

10voto

Mikael Persson Points 7174

D'abord, j'ai trouvé que cet article était une lecture très intéressante et concise sur les bibliothèques de liens dynamiques (l'article est seulement spécifique à Linux, mais les concepts s'appliquent sûrement à Windows aussi et vous pourriez avoir un aperçu du comportement différent que vous voyez). En particulier, la différence fondamentale entre le chargement statique et le chargement dynamique.

Je pense que ce que vous voulez ou essayez de mettre en œuvre est un modèle de "singleton cross-module". Si vous lisez les réponses à ce fil Je ne sais pas comment je pourrais répondre à votre question mieux que Ben Voigt n'a répondu à ce message. J'ai déjà implémenté un singleton inter-modules (plusieurs fois en fait) en utilisant la méthode qu'il décrit, et cela fonctionne à merveille.

Bien sûr, vous ne serez pas en mesure de conserver la propreté de la variable globale dans le fichier cpp. Vous devrez utiliser un pointeur statique, des fonctions d'accès et un comptage de références. Mais cela peut fonctionner. Je ne suis pas sûr de savoir comment il serait possible d'éviter que foo.exe et foo.exe partagent la même instance de données globales un bar.dll, je n'ai jamais eu à le faire et je ne peux pas vraiment penser à un moyen de le faire, désolé.

7voto

Jeremiah Points 101

J'ai trouvé cette question tellement intéressante que j'ai pris le temps d'écrire un tutoriel complet sur la façon d'utiliser les DLL pour partager des données entre plusieurs DLL (soit implicitement, soit explicitement liées) mais aussi pour s'assurer que les données ne sont pas partagées entre des processus distincts du même fichier exécutable.

Vous pouvez trouver l'article complet ici : http://3dgep.com/?p=1759


Une solution à ce problème qui fonctionne très bien est de créer une DLL "commune" ou "partagée" qui définit toutes les données et méthodes que vous voulez partager entre plusieurs DLL (mais pas entre les processus).

Supposons que vous souhaitiez définir une classe singleton à laquelle vous pouvez accéder à partir du code de l'application principale (l'EXE), mais que vous souhaitiez également accéder à l'instance singleton en mode partagé (DLL liée implicitement ou explicitement). Tout d'abord, vous devez déclarer la classe singleton dans la DLL "commune" :

// Export the class when compiling the DLL, 
// otherwise import the class when using the DLL.
class __declspec(dllexport) MySingleton 
{
public:
    static MySingleton& Instance();
};

Lors de la compilation du projet CommonDLL, vous devez exporter la déclaration de classe en décorant la classe avec __declspec(dllexport) et lorsque vous utilisez la DLL (dans l'application par exemple), la définition de la classe doit être importée en décorant la classe avec __declspec(dllimport) .

Lors de l'exportation d'une classe en décorant la classe avec l'attribut __declspec(dllexport) toutes les méthodes et données de la classe (même les données privées) sont exportées de la DLL et utilisables par toute DLL ou EXE qui se lie implicitement à la DLL commune.

La définition de la classe MySingleton pourrait ressembler à ceci :

MySingleton& MySingleton::Instance()
{
    static MySingleton instance;
    return instance;
}

Lors de la compilation de la dll commune, deux fichiers seront produits :

  1. En Common.DLL qui est la bibliothèque partagée qui définit les méthodes et les données exportées utilisées par la DLL.
  2. En Common.LIB qui déclare les stubs pour les méthodes et les membres exportés par la DLL.

Si vous liez votre application au fichier LIB exporté, alors le fichier DLL sera implicitement lié au moment de l'exécution (tant que le fichier DLL est trouvé dans les chemins de recherche de DLL) et vous aurez accès au singleton défini dans le fichier CommonDLL.DLL.

De même, toute bibliothèque partagée (plug-ins par exemple) qui se lie également au fichier CommonDLL.LIB aura accès aux mêmes instances singleton lorsqu'elle sera chargée dynamiquement par l'application.

Pour une explication complète de cette solution, y compris un exemple de code source, consultez l'article suivant que j'ai publié, intitulé "Using Dynamic Link Libraries (DLL) to Create Plug-Ins" :

http://3dgep.com/?p=1759

5voto

Ciaran Keating Points 1876

Si foo.exe charge toujours bar.dll, vous pouvez implémenter la variable dans bar.dll et l'exporter. Par exemple, un fichier b.cpp n'est compilé que dans bar.dll, pas dans foo.exe :

__declspec(dllexport) int x;

Puis l'importer dans un fichier source c.cpp compilé dans foo.exe :

__declspec(dllimport) int x;

Cependant, si parfois foo.exe ne charge pas bar.dll, cela ne fonctionnera pas. De plus, j'écris ceci de mémoire et il se peut qu'il y ait quelques erreurs de syntaxe, mais j'espère que c'est suffisant pour vous mettre dans la bonne direction.

Je ne peux pas répondre pourquoi c'est différent pour Linux.

3voto

Reed Hedges Points 995

La différence entre GCC et Visual Studio est que sous Linux, il permet implicitement au code de voir les symboles d'autres bibliothèques liées dynamiquement (partagées), sans que le programmeur ait à faire quoi que ce soit de spécial. Tous les symboles sont disponibles dans la bibliothèque partagée (liée dynamiquement) pour que l'éditeur de liens dynamiques les résolve lors de l'exécution du programme. Sous Windows, vous devez exporter spécifiquement le symbole de la DLL et l'importer explicitement dans le programme ou la bibliothèque qui l'utilise. (En général, cela se fait par le biais d'une macro (#define) qui se développe pour avoir la déclaration dllexport dans un fichier d'en-tête lors de la construction de la DLL elle-même, mais lorsque le fichier d'en-tête est inclus par un autre programme utilisant la DLL, il se développe pour avoir la déclaration dllimport à la place. À mon avis, c'est une douleur dans le cou, et le comportement de GCC est plus facile, puisque vous n'avez pas à faire quelque chose de spécial pour obtenir le comportement que vous voulez habituellement.

Sur les versions plus récentes de GCC, vous pouvez, si vous le souhaitez, définir la valeur par défaut pour masquer les symboles lors de la construction d'une bibliothèque dynamique (partagée).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X