2011-02-06 13 views
20

Me gustaría compartir una variable estática/global solo entre un proceso y una DLL que es invocada por el proceso. El exe y dll están en el mismo espacio de direcciones de memoria. No quiero que la variable se comparta entre otros procesos.Compartiendo una variable global/estática entre un proceso y una DLL


Elaboración del problema:

decir que hay un/variable global estática x en a.cpp. Tanto el exe foo.exe como el dll bar.dll tienen a.cpp, por lo que la variable x está en ambas imágenes.

Ahora, foo.exe carga dinámicamente (o estáticamente) bar.dll. Entonces, el problema es si la variable x es compartida por el exe y dll, o no.

En Windows, estos dos chicos no compartir el x: el exe y dll tendrán una copia separada de x. Sin embargo, en Linux, el exe y dll comparten la variable x.

Desafortunadamente, quiero el comportamiento de Linux. Primero consideré usar pragma data_seg en Windows. Sin embargo, incluso si configuro correctamente el segmento de datos compartidos, foo.exe y bar.dll nunca comparten el x. Recuerde que bar.dll se carga en el espacio de direcciones de foo.exe. Sin embargo, si ejecuto otra instancia de foo.exe, se comparte x. Pero no quiero que x sea compartido por diferentes procesos. Por lo tanto, se produjo un error al usar data_seg.

Puedo usar un archivo mapeado en memoria haciendo un nombre único entre exe y dll, que estoy intentando ahora.


Dos preguntas:

  1. Por qué el comportamiento de Linux y Windows es diferente? ¿Alguien puede explicar más sobre esto?
  2. ¿Cuál sería la forma más fácil de resolver este problema en Windows?

Respuesta

8

Primero, descubrí que this article era una lectura muy interesante y concisa de las bibliotecas de enlaces dinámicos (el artículo es solo específico de Linux, pero los conceptos seguramente también se aplican a Windows y puede obtener alguna información sobre los diferentes comportamiento que estás viendo). Especialmente la diferencia fundamental entre la carga estática y dinámica.

Creo que lo que quiere o está tratando de implementar es un patrón de "singleton de módulos cruzados". Si lees las respuestas al this thread, no sé cómo podría responder tu pregunta mejor de lo que Ben Voigt respondió esa publicación. He implementado un singleton de módulos cruzados antes (algunas veces en realidad) usando el método que describe, y funciona como un amuleto.

Por supuesto, no podrá conservar la limpieza de solo tener la variable global en el archivo cpp. Deberá usar un puntero estático y algunas funciones de acceso y recuento de referencias. Pero puede funcionar No estoy tan seguro de cómo sería posible evitar que foo.exe y foo.exe compartan la misma instancia de datos globales de un bar.dll, nunca tuve que hacer eso y realmente no puedo pensar en una forma de hacerlo lo siento

+0

Gracias por buena información. Pude escribir un singleton de módulo cruzado similar utilizando un objeto mapeado en memoria (= memoria compartida). ¡Gracias! – minjang

5

Si foo.exe siempre carga bar.dll, entonces puede implementar la variable en bar.dll y exportarla. Por ejemplo, algunos archivos b.cpp compilado solamente en bar.dll, no en foo.exe:

__declspec(dllexport) int x; 

importarla a continuación en un archivo de origen c.cpp compilado en foo.exe:

__declspec(dllimport) int x; 

Sin embargo, si a veces foo.exe no carga bar.dll entonces esto no funcionará. Además, estoy escribiendo esto desde la memoria y puede haber algunos errores sintácticos, pero espero que sea suficiente para apuntar en la dirección correcta.

No puedo responder por qué es Linux diferente.

+0

Gracias por la respuesta. Desafortunadamente, foo.exe no siempre está llamando a bar.dll. Pero, 'x siempre es utilizado por foo.exe. – minjang

+0

Tal vez es solo una cuestión de terminología, pero ... no es necesario llamar a ninguna función en la DLL, solo para cargarla. –

+0

su solución se aplica únicamente a la carga estática (es decir, el dll se carga al mismo tiempo que el exe y, por lo tanto, las variables globales se vinculan antes de que se inicie la ejecución). Pero el OP necesita una solución que funcione con carga dinámica (usando LoadLibrary() o dlopen() para cargar la biblioteca en cualquier momento), en este caso, las variables globales simplemente no pueden vincularse tan fácilmente porque el proceso se está ejecutando (básicamente , Linux puede hacerlo hasta cierto punto, Windows no puede hacerlo en absoluto). –

0

Si entiendo su pregunta correctamente, está conectando estáticamente a.cpp en foo.exe y bar.dll, por lo que obtiene 2 instancias de x.

Si ha creado una tercera DLL (por ejemplo A.dll), y se vincula dinámicamente foo.exe y bar.dll a A.dll, obtendrá el comportamiento que desee:

  1. foo.exe carga a.dll y crea x.
  2. bar.dll se carga y ve que a.dll está cargado y no lo carga nuevamente, comparten x.
  3. Otro proceso carga a.dll, obtiene su propia x.
4

Me pareció una pregunta tan interesante que me tomé el tiempo de escribir un extenso tutorial sobre cómo usar DLL para compartir datos entre múltiples DLL (implícita o explícitamente) pero también me aseguro de que los datos no se comparte entre procesos separados del mismo archivo ejecutable.

Puede encontrar el artículo completo aquí: http://3dgep.com/?p=1759


Una solución a este problema que he encontrado que funciona bastante bien es crear una o DLL "común" "compartida" que define todos los datos y métodos que desea compartir en múltiples DLL (pero no compartidos entre procesos).

Supongamos que desea definir una clase singleton a la que se pueda acceder desde el código de la aplicación principal (el EXE) pero también desea acceder a la instancia singleton en DLL compartido (ya sea implícita o explícitamente vinculado). En primer lugar, lo que se necesita para declarar la clase Singleton en la DLL "común":

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

Al compilar proyecto CommonDLL, tiene que exportar el declaratoin clase mediante la decoración de la clase con __declspec(dllexport) y cuando se utiliza la DLL (en la aplicación, por ejemplo), la definición de clase debe importarse decorando la clase con __declspec(dllimport).

Al exportar una clase decorando la clase con el especificador __declspec(dllexport), todos los métodos y datos de la clase (incluso datos privados) se exportan de la DLL y pueden utilizarse con cualquier DLL o EXE que implícitamente enlace a la DLL común.

La definición de la clase MySingleton podría ser algo como esto:

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

Al compilar el archivo DLL común, se producirán dos archivos:

  1. El archivo Common.DLL cual es la biblioteca compartida que define los métodos y los datos exportados utilizados por el archivo DLL.
  2. El archivo Common.LIB que declara apéndices de los métodos y miembros exportados desde la DLL.

Si crea un enlace que demanda contra el archivo LIB exportado, el archivo DLL se implícitamente ligado en tiempo de ejecución (siempre y cuando el archivo DLL se encuentra en las rutas de búsqueda DLL) y tendrá acceso a la singleton definido en el archivo CommonDLL.DLL.

Además, cualquier biblioteca compartida (plug-ins, por ejemplo) que también se vincule con el archivo CommonDLL.LIB tendrá acceso a las mismas instancias singleton cuando se cargue dinámicamente por la aplicación.

Para una explicación completa de esta solución que incluye un ejemplo de código fuente y la salida hasta el siguiente artículo que he publicado titulado "Uso de bibliotecas de vínculos dinámicos (DLL) para crear plug-ins":

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

10

Para obtener el comportamiento de Linux donde tanto el programa principal como un dll comparten el mismo x, puede exportar esa variable desde el dll o desde el programa principal. El otro módulo debe importar esa variable.

Esto se hace mediante el uso de archivos DEF (see microsoft's documentation), o mediante el marcado de los usos con la variable con __declspec(dllexport) donde se define y __declspec(dllimport) en cualquier otro módulo se utiliza (see microsoft's documentation). Esto es lo mismo que cualquier función, objeto o variable que se comparte entre los módulos en Windows.

En el caso en que desee que un programa cargue una biblioteca en tiempo de ejecución, pero el programa principal puede tener que usar la variable antes de cargar la biblioteca, el programa debería exportar la variable y la dll debería importarla . Aquí hay un pequeño problema de huevo y gallina porque el dll depende del programa principal y el programa principal depende del dll. Ver http://www.lurklurk.org/linkers/linkers.html#wincircular

He escrito un ejemplo de cómo se puede hacer utilizando el compilador de Microsoft y mingw (gcc en Windows), incluidas las diferentes formas en que un programa y una biblioteca pueden enlazarse entre sí (estáticamente cargada dll al inicio del programa, DLL cargado en tiempo de ejecución)

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 

acumulación MinGW both.sh explícita

#! /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 .. 

acumulación MinGW dll.sh explícita

#! /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 .. 

acumulación MinGW main.sh explícita

#! /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 .. 

acumulación 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 .. 

acumulación 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 .. 

acumulación msvc both.bat explícita

@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 .. 

acumulación msvc dll.bat explícita

@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 .. 

acumulación msvc main.bat explícita

@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 .. 

acumulación 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 .. 

acumulación 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 .. 
+0

Si alguien puede decirme una mejor forma de hacer un volcado de código para un ejemplo completo, me encantaría saberlo. –

3

La diferencia entre el CCG y Visual Studio y es que en Linux, implícitamente permite que el código vea símbolos de otras bibliotecas dinámicamente vinculadas (compartidas), sin que el programador tenga que hacer algo especial. Todos los símbolos están disponibles en la biblioteca compartida (vinculada dinámicamente) para que el enlazador dinámico se resuelva cuando se ejecuta el programa. En Windows, debe exportar específicamente el símbolo de la DLL y también importarlo explícitamente en el programa o la biblioteca que lo está utilizando. (Normalmente esto se realiza a través de una macro (#define) que se expande para tener la declaración dllexport en un archivo de encabezado cuando se construye el dll, pero cuando el archivo de encabezado está incluido en algún otro programa que usa el dll, se expande para tener el dllimport declaración en su lugar. En mi opinión, este es un dolor en el cuello, y el comportamiento de GCC es más fácil, ya que no tiene que hacer nada especial para obtener el comportamiento que generalmente desea

En la versión más nueva de GCC, puede establezca el valor predeterminado para ocultar símbolos al compilar una biblioteca dinámica (compartida), si así lo desea.

1

Gracias por proporcionar varias soluciones al respecto. Miré estas opciones y decidí implementar el singleton de módulo cruzado usando la memoria compartida y también funcionó bien para mí. he utilizado Qt QSharedMemory para alcanzar mi tarea, pero el prototipo i escribió usando el Win32 CreateFileMapping & etc.

0

he visto muchas respuestas a esta pregunta y ya que es un poco complicado y poco claro me gustaría traer lo siguiente guión. Queremos compartir una variable global entre un archivo DLL y un programa principal, y también permitir el acceso a esta variable desde diferentes módulos en el archivo DLL y en el programa principal.

La variable es un BOOL que indica si el programa debe continuar ejecutándose o detenerse. El nombre de la variable es ShouldRun;

En el programa principal tenemos que poner:

__declspec(dllexport) bool ShouldRun; 

En el módulo principal de la DLL tenemos que poner:

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

En cualquier otro módulo dentro del proyecto DLL vamos a utilizar:

extern "C" BOOL ShouldRun; 
Cuestiones relacionadas