web-dev-qa-db-fra.com

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

J'aimerais partager une variable statique/globale uniquement entre un processus et une dll invoquée par le processus. Les exe et 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.


Elaboration du problème:

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

Maintenant, foo.exe charge dynamiquement (ou de manière statique) bar.dll. Ensuite, le problème est de savoir si la variable x est partagée par les fichiers exe et dll, ou non.

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

Malheureusement, je veux le comportement de Linux. J'ai d'abord envisagé d'utiliser pragma data_seg sous Windows. Cependant, même si je configure correctement le segment de données partagé, foo.exe et bar.dll ne partagent jamais la x. Rappelez-vous que bar.dll est chargé dans l'espace d'adressage de foo.exe. Cependant, si j'exécute une autre instance de foo.exe, alors x est partagé. Mais je ne veux pas que x soit partagé par différents processus. Donc, l'utilisation de data_seg a échoué.

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


Deux questions:

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

Tout d’abord, j’ai trouvé que cet article était une lecture très intéressante et concise sur les bibliothèques de liens dynamiques (cet article n’est spécifique qu’à Linux, mais les concepts s’appliquent également aux fenêtres et vous pourrez peut-être obtenir un aperçu comportement différent que vous voyez). Surtout la différence fondamentale entre le chargement statique et dynamique.

Je pense que ce que vous voulez ou essayez d’implémenter est un motif de "singleton inter-modules". Si vous lisez les réponses à ce fil de discussion , je ne sais pas comment je pourrais éventuellement mieux répondre à votre question que Ben Voigt n'a répondu à ce message. J'ai déjà implémenté un singleton inter-modules (à quelques reprises en fait) en utilisant la méthode qu'il décrit, et cela fonctionne à merveille.

Bien sûr, vous ne pourrez pas conserver la propreté de simplement avoir la variable globale dans le fichier cpp. Vous devrez utiliser un pointeur statique ainsi que certaines fonctions d’accesseur et de comptage de références. Mais ça peut marcher. Je ne sais pas trop 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. ça, désolé.

8
Mikael Persson

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

Pour ce faire, vous utilisez des 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 utilisé ( voir la documentation de Microsoft ). Cela correspond à la manière dont une fonction, un objet ou une variable est partagé entre les modules dans Windows.

Si 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 un petit problème de poule et d'oeuf ici parce que 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 à la fois le compilateur de Microsoft et mingw (gcc dans Windows), y compris les différentes manières dont un programme et une bibliothèque peuvent être liés (statiquement, dll chargée au début 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

principal 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 explicite 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 explicite 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 explicite 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 explicite 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 explicite 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 implicit.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 ..
11
James Caccese

Si foo.exe charge toujours bar.dll, vous pouvez alors implémenter la variable dans bar.dll et l'exporter. Par exemple, certains fichiers b.cpp compilés uniquement dans bar.dll, pas dans foo.exe:

__declspec(dllexport) int x;

Puis importez-le 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 pourrait donc y avoir des erreurs de syntaxe, mais j'espère que cela suffira pour vous indiquer la bonne direction.

Je ne peux pas dire pourquoi c'est différent de Linux.

5
Ciaran Keating

J'ai trouvé cette question tellement intéressante que j'ai pris le temps d'écrire un didacticiel complet sur l'utilisation des DLL pour partager des données entre plusieurs DLL (de manière implicite ou explicite), tout en veillant à ce que les données ne soient pas partagées par plusieurs processus. du même fichier exécutable.

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


Une solution à ce problème qui a très bien fonctionné consiste à créer une DLL "commune" ou "partagée" définissant toutes les données et méthodes à partager entre plusieurs DLL (mais non entre processus).

Supposons que vous souhaitiez définir une classe singleton accessible à partir du code de l'application principale (le fichier EXE), mais que vous souhaitiez également accéder à l'instance du singleton en DLL partagée (DLL liée de manière implicite ou explicite). 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 la décorant 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 la décorant avec le spécificateur __declspec(dllexport), toutes les méthodes et données de la classe (même les données privées) sont exportées à partir de DLL et utilisables par tout DLL ou EXE lié de manière implicite à la commande. 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. Le fichier Common.DLL qui est la bibliothèque partagée qui définit les mehthods et les données exportées utilisés par la DLL.
  2. Le fichier Common.LIB qui déclare des stubs pour les méthodes et les membres exportés à partir de la DLL.

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

En outre, toute bibliothèque partagée (plug-ins par exemple) qui est également liée au fichier CommonDLL.LIB aura accès aux mêmes instances de singleton lors du chargement dynamique de l'application.

Pour une explication complète de cette solution, y compris un exemple de code source, consultez l'article suivant intitulé "Utilisation de bibliothèques de liens dynamiques (DLL) pour créer des plug-ins":

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

4
Jeremiah

La différence entre GCC et Visual Studio réside dans le fait que sous Linux, cela permet implicitement au code de voir les symboles d'autres bibliothèques (partagées) liées dynamiquement, sans que le programmeur ait à faire quelque chose de spécial. Tous les symboles sont disponibles dans la bibliothèque partagée (reliée dynamiquement) pour que l'éditeur de liens dynamique puisse les résoudre lors de l'exécution du programme. Sous Windows, vous devez spécifiquement exporter le symbole de la DLL et également l'importer explicitement dans le programme ou la bibliothèque qui l'utilise. (Habituellement, cela se fait via une macro (#define) qui étend la déclaration dllexport à 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 également avec la dllimport À mon avis, c’est une douleur au cou, et le comportement de GCC est plus facile, car vous n’avez rien à faire de spécial pour obtenir le comportement que vous désirez habituellement.

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

3
Reed Hedges

Merci de fournir diverses solutions à ce sujet. J'ai examiné ces options et décidé d'implémenter le module singleton cross module à l'aide de la mémoire partagée et cela a également bien fonctionné pour moi . .

1
Zeeshan

Si je comprends bien votre question, vous liez statiquement a.cpp à foo.exe et bar.dll, vous obtenez donc 2 instances de x.

Si vous avez créé une troisième DLL (disons a.dll) et que vous avez lié dynamiquement foo.exe et bar.dll à a.dll, vous obtiendrez le comportement souhaité:

  1. foo.exe charge a.dll et crée x.
  2. bar.dll est chargé et voit que a.dll est chargé et ne le charge pas à nouveau, ils partagent x.
  3. Un autre processus charge a.dll, il obtient Son propre x.
0
zdan

J'ai vu beaucoup de réponses à cette question et comme c'est un peu compliqué et peu clair, j'aimerais apporter le scénario suivant: .. Nous voulons partager une variable globale entre une DLL et un programme principal, autoriser l'accès à cette variable à partir de différents modules dans la DLL et dans le programme principal.

La variable est un BOOL indiquant si le programme doit continuer à s'exécuter ou à s'arrêter. Le nom de la variable est ShouldRun ;

Dans le programme principal, nous devons mettre:

__declspec(dllexport)  bool ShouldRun;

Dans le module principal de la DLL, nous devons mettre:

extern "C" BOOL __declspec(dllexport) ShouldRun = TRUE;

Dans tout autre module du projet DLL, nous utiliserons:

extern  "C" BOOL ShouldRun;
0
Michael Haephrati