2010-06-09 15 views
9

Poiché ho osservato un comportamento strano delle variabili globali nelle mie librerie caricate dinamicamente, ho scritto il seguente test.Librerie caricate dinamiche e simboli globali condivisi

In un primo momento abbiamo bisogno di una libreria collegata in modo statico: L'intestazione 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 

e la fonte test.cpp

#include "base.hpp" 

test global_test = test(1); 

Poi ho scritto una libreria caricata dinamicamente: library.cpp

#include "base.hpp" 

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

e un programma client caricato g questa libreria: 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; 
} 

Ora compilare la libreria caricata staticamente con

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

la libreria caricata dinamicamente

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

e il client

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

Ora osservo: il client e la libreria caricata dinamicamente possiedono una versione diversa della variabile global_test. Ma nel mio progetto sto usando cmake. Lo script di build è simile al seguente:

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) 

analizzando le creati makefile s ho scoperto che CMake costruisce il cliente con

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

Questo finisce in un comportamento leggermente diverso, ma fatale: La global_test del cliente e la libreria caricata dinamicamente è la stessa, ma verrà distrutta due volte alla fine del programma.

Sto utilizzando cmake in modo errato? È possibile che il client e la libreria caricata dinamicamente utilizzino lo stesso global_test ma senza questo doppio problema di distruzione?

+0

La mia prima reazione sarebbe quella di mettere in discussione la necessità di questa variabile globale. –

+0

Ok, nel mio programma originale questa variabile globale è una costante fornita da una libreria collegata staticamente. Tuttavia, verrà eliminato due volte nella versione cmake – phlipsy

+0

Lo stesso problema si applicherebbe a qualsiasi modello singleton, quindi non vedo un problema con il globale – Elemental

risposta

1

La mia prima domanda è se esiste una ragione particolare per cui sia staticamente che dinamicamente (tramite dlopen) collegano lo stesso codice?

Per il tuo problema: -rdynamic esporterà i simboli dal tuo programma e ciò che probabilmente sta accadendo è che il linker dinamico risolve tutti i riferimenti alla tua variabile globale al primo simbolo che incontra nelle tabelle dei simboli. Qual è quello che non so.

EDIT: dato il vostro scopo vorrei collegare il vostro programma in questo modo:

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

Potrebbe essere necessario correggere l'ordine.

+0

Tra alcune funzioni ho definito alcune costanti voluminose nella libreria collegata staticamente. La libreria caricata dinamicamente è un plug-in per il client che utilizza queste funzionalità fornite dalla libreria collegata staticamente. – phlipsy

+0

@phlipsy: il modo in cui probabilmente si dovrebbero utilizzare tali costanti è di collegare la libreria statica all'eseguibile principale, collegare l'eseguibile principale con -rdynamic (come fa cmake) e non collegare i plugin con la libreria statica. Non so però come collegare il tuo plugin al programma. Puoi provare a calcolare il codice comune (libreria statica) alla libreria dinamica e collegare sia il programma che il plugin. – Tomek

+0

Quindi in fase di esecuzione il plugin utilizzerà le definizioni dei simboli utilizzati che si trovano nell'eseguibile del client? È legale? – phlipsy

2

Se si utilizzano librerie condivise, è necessario definire le cose che si desidera esportare con una macro come here. Vedi la definizione della macro DLL_PUBLIC qui.

+1

Solo su Windows! – bmargulies

+0

È una macro generale. Io lavoro sia su GNU/Linux che su Windows. Vedi il #ifdef nella dichiarazione. – INS

2

Per impostazione predefinita, il linker non combina una variabile globale (una 'D') nell'eseguibile di base con una in una libreria condivisa. L'eseguibile di base è speciale. Potrebbe esserci un modo oscuro per farlo con uno di quegli oscuri file di controllo che legge, ma ne dubito.

--export-dynamic farà sì che i simboli a.out "D" siano disponibili per le librerie condivise.

Tuttavia, considerare il processo. Passaggio 1: crei un DSO da un file .o con una 'U' e un .a con una 'D'. Quindi, il linker incorpora il simbolo nel DSO. Passaggio 2, si crea l'eseguibile con una 'U' in uno dei file .o e 'D' in entrambi .a e il DSO. Proverà a risolvere usando la regola da sinistra a destra.

Le variabili, al contrario delle funzioni, presentano comunque alcune difficoltà per il linker tra i moduli. Una pratica migliore è quella di evitare riferimenti var globali tra i limiti dei moduli e utilizzare le chiamate alle funzioni. Tuttavia, ciò fallirebbe ancora per te se si inserisse la stessa funzione sia nell'eseguibile base che in una lib condivisa.

+0

Vuoi dire che collegare il client * e * la libreria con 'libbase.a' è una cattiva idea? Ok, quindi suggerisci di non collegare la libreria a 'libbase.a' perché durante il runtime i simboli del client vengono presi per le chiamate nella libreria? Funziona ... ma è legale? – phlipsy

+0

Ho modificato per semplificare dal momento che non sono completamente sicuro dalla tua domanda su quali file sono finiti. – bmargulies

+0

C'è una libreria collegata staticamente (chiamata 'libbase.a'), una libreria caricata dinamicamente (un plugin) che usa parti di' libbase.a' e un client che carica il plugin e usa anche parti di 'libbase.a'. – phlipsy

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

CMake aggiunge -rdynamic opzione che permette biblioteca caricato per risolvere i simboli in eseguibile loading ... Così si può vedere che questo è ciò che non vuoi. Senza questa opzione manca semplicemente questo simbolo per sbaglio.

Ma ... Non dovresti fare cose del genere lì. Le tue librerie ed eseguibili dovrebbero non condividere i simboli a meno che non siano realmente dovrebbero essere condivisi.

Pensa sempre al collegamento dinamico come collegamento statico.

0

Vorrei consigliare di utilizzare un dlopen(... RTLD_LAZY|RTLD_GLOBAL); per unire tabelle di simboli globali.

0

proporrei di compilare qualsiasi libreria .a statica che si prevede di collegare a una libreria dinamica, con -fvisibility = parametro nascosto, in modo da:

g ++ -Wall -fvisibility = nascosti -g di base -c. cpp

+0

Puoi spiegare perché? – Charles

+0

Mi dispiace, era molto tempo in cui ero coinvolto in questo problema e non vedo più il contesto. Tuttavia, apprezzo il mio suggerimento è stato utile. Non era? ;-) – smrt28

Problemi correlati