2010-06-09 11 views
9

Como observé un comportamiento extraño de las variables globales en las bibliotecas cargadas dinámicamente, escribí la siguiente prueba.Bibliotecas cargadas dinámicamente y símbolos globales compartidos

Al principio necesitamos una biblioteca vinculada estáticamente: La cabecera test.hpp

#ifndef __BASE_HPP 
#define __BASE_HPP 

#include <iostream> 

class test { 
private: 
    int value; 
public: 
    test(int value) : value(value) { 
    std::cout << "test::test(int) : value = " << value << std::endl; 
    } 

    ~test() { 
    std::cout << "test::~test() : value = " << value << std::endl; 
    } 

    int get_value() const { return value; } 
    void set_value(int new_value) { value = new_value; } 
}; 

extern test global_test; 

#endif // __BASE_HPP 

y la fuente test.cpp

#include "base.hpp" 

test global_test = test(1); 

Entonces me escribió una biblioteca cargada dinámicamente: library.cpp

#include "base.hpp" 

extern "C" { 
    test* get_global_test() { return &global_test; } 
} 

y un programa de cliente cargando g esta biblioteca: client.cpp

#include <iostream> 
#include <dlfcn.h> 
#include "base.hpp" 

typedef test* get_global_test_t(); 

int main() { 
    global_test.set_value(2); // global_test from libbase.a 
    std::cout << "client:  " << global_test.get_value() << std::endl; 

    void* handle = dlopen("./liblibrary.so", RTLD_LAZY); 
    if (handle == NULL) { 
    std::cout << dlerror() << std::endl; 
    return 1; 
    } 

    get_global_test_t* get_global_test = NULL; 
    void* func = dlsym(handle, "get_global_test"); 
    if (func == NULL) { 
    std::cout << dlerror() << std::endl; 
    return 1; 
    } else get_global_test = reinterpret_cast<get_global_test_t*>(func); 

    test* t = get_global_test(); // global_test from liblibrary.so 
    std::cout << "liblibrary.so: " << t->get_value() << std::endl; 
    std::cout << "client:  " << global_test.get_value() << std::endl; 

    dlclose(handle); 
    return 0; 
} 

Ahora compilo la biblioteca estáticamente cargado con

g++ -Wall -g -c base.cpp 
ar rcs libbase.a base.o 

la biblioteca cargada dinámicamente

g++ -Wall -g -fPIC -shared library.cpp libbase.a -o liblibrary.so 

y el cliente

g++ -Wall -g -ldl client.cpp libbase.a -o client 

Ahora observo: el cliente y la biblioteca cargada dinámicamente poseen una versión diferente de la variable global_test. Pero en mi proyecto estoy usando cmake. La escritura de la estructura se parece a esto:

CMAKE_MINIMUM_REQUIRED(VERSION 2.6) 
PROJECT(globaltest) 

ADD_LIBRARY(base STATIC base.cpp) 

ADD_LIBRARY(library MODULE library.cpp) 
TARGET_LINK_LIBRARIES(library base) 

ADD_EXECUTABLE(client client.cpp) 
TARGET_LINK_LIBRARIES(client base dl) 

analizar los creados makefile s me encontré con que cmake construye el cliente con

g++ -Wall -g -ldl -rdynamic client.cpp libbase.a -o client 

Esto termina en un comportamiento ligeramente diferente, pero fatal: la global_test del cliente y la biblioteca cargada dinámicamente son las mismas, pero se destruirán dos veces al final del programa.

¿Estoy usando cmake de manera incorrecta? ¿Es posible que el cliente y la biblioteca cargada dinámicamente usen el mismo global_test pero sin este doble problema de destrucción?

+0

Mi primera reacción sería cuestionar la necesidad de esta variable global. –

+0

Ok, en mi programa original, esta variable global es una constante proporcionada por una biblioteca enlazada estáticamente. Sin embargo, se destruirá dos veces en la versión de cmake – phlipsy

+0

. Se aplicaría el mismo problema para cualquier patrón único, así que no veo ningún problema con el – Elemental

Respuesta

1

Mi primera pregunta es si hay alguna razón particular por la que tanto estática como dinámicamente (a través de dlopen) se vincule el mismo código?

Para su problema: -rdynamic exportará los símbolos de su programa y lo que probablemente esté sucediendo es que el enlazador dinámico resuelve todas las referencias a su variable global al primer símbolo que encuentra en las tablas de símbolos. Cuál es que yo no sé

EDIT: dada su propósito me gustaría enlazar su programa de esa manera:

g++ -Wall -g -ldl client.cpp -llibrary -L. -o client 

Puede que tenga que fijar el orden.

+0

global. Entre algunas funciones definí algunas constantes voluminosas en la biblioteca enlazada estáticamente. La biblioteca cargada dinámicamente es un complemento para el cliente que utiliza estas funciones proporcionadas por la biblioteca enlazada estáticamente. – phlipsy

+0

@phlipsy: la forma en que probablemente debería usar esas constantes es vincular la biblioteca estática al ejecutable principal, vincular el ejecutable principal con -rdinámico (como lo hace cmake) y no vincular los complementos con la biblioteca estática. No obstante, no sé cómo vincular tu complemento al programa. Puede tratar de factorizar el código común (biblioteca estática) a la biblioteca dinámica y vincular el programa y el complemento con eso. – Tomek

+0

¿Luego, en tiempo de ejecución, el complemento usará las definiciones de los símbolos usados ​​ubicados en el ejecutable del cliente? Es eso legal? – phlipsy

2

Si utiliza bibliotecas compartidas, debe definir las cosas que desea exportar con macro como here. Ver definición de macro DLL_PUBLIC allí.

+1

¡Solo en Windows! – bmargulies

+0

Es una macro general. Yo trabajo tanto en GNU/Linux y Windows. Ver el #ifdef en la declaración. – INS

2

De forma predeterminada, el vinculador no combinará una variable global (una 'D') en el ejecutable base con una en una biblioteca compartida. El ejecutable base es especial. Puede haber una forma oscura de hacer esto con uno de esos oscuros archivos de control que lee, pero lo dudo.

--export-dynamic hará que los símbolos a.out 'D' estén disponibles para las bibliotecas compartidas.

Sin embargo, considere el proceso. Paso 1: crea un DSO desde un .o con una 'U' y una .a con una 'D'. Entonces, el enlazador incorpora el símbolo en el DSO. Paso 2, crea el ejecutable con una 'U' en uno de los archivos .o, y 'D' en aa y en el DSO. Tratará de resolver usando la regla de izquierda a derecha.

Las variables, a diferencia de las funciones, presentan ciertas dificultades para el enlazador a través de los módulos en cualquier caso. Una práctica mejor es evitar las referencias var globales a través de los límites del módulo, y usar llamadas a funciones. Sin embargo, aún fallaría si pusiera la misma función tanto en el ejecutable base como en una lib compartida.

+0

¿Quiere decir que vincular el cliente * y * la biblioteca con 'libbase.a' es una mala idea? Ok, ¿sugieres no vincular la biblioteca con 'libbase.a' porque en tiempo de ejecución los símbolos del cliente son tomados para las llamadas en la biblioteca? Funciona ... pero ¿es legal? – phlipsy

+0

He editado para simplificar, ya que no estoy del todo seguro de su pregunta sobre qué archivos están terminando. – bmargulies

+0

Hay una biblioteca enlazada estáticamente usada (llamada 'libbase.a'), una biblioteca cargada dinámicamente (un complemento) que usa partes de' libbase.a' y un cliente que carga el complemento y usa partes de 'libbase.a' también. – phlipsy

3
g++ -Wall -g -ldl -rdynamic client.cpp libbase.a -o client 

CMake añade -rdynamic opción que permite a la biblioteca cargada para resolver los símbolos en el ejecutable de carga ... Así se puede ver que esto es lo que no quiere. Sin esta opción, simplemente pierde este símbolo por accidente.

Pero ... No deberías hacer algo así allí. Sus bibliotecas y ejecutables deben no compartir símbolos a menos que realmente se deben compartir.

Siempre piense en enlaces dinámicos como enlaces estáticos.

0

Aconsejaría utilizar un dlopen(... RTLD_LAZY|RTLD_GLOBAL); para unir tablas de símbolos globales.

0

yo propondría para compilar cualquier biblioteca estática .a la que va a vincular a una biblioteca dinámica, con -fvisibility = parámetro oculto, por lo que:

g ++ -Wall -fvisibility = -g ocultos de base -c. cpp

+0

¿Puedes explicar por qué? – Charles

+0

Lo siento, solía ser un largo tiempo cuando estaba involucrado en este problema y ya no veo el contexto. Sin embargo, aprecio que mi sugerencia fue útil. ¿No? ;-) – smrt28

Cuestiones relacionadas