2010-06-09 12 views
9

Da ich in meinen dynamisch geladenen Bibliotheken seltsames Verhalten von globalen Variablen beobachtet habe, schrieb ich den folgenden Test.Dynamisch geladene Bibliotheken und gemeinsame globale Symbole

Zunächst brauchen wir eine statisch gelinkte Bibliothek: Der Header 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 

und die Quelle test.cpp

#include "base.hpp" 

test global_test = test(1); 

Dann habe ich eine dynamisch geladene Bibliothek schrieb: library.cpp

#include "base.hpp" 

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

und ein Client-Programm laden g dieser Bibliothek: 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; 
} 

Nun kompilieren ich die statisch geladenen Bibliothek mit

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

das dynamisch geladene Bibliothek

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

und der Client

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

Nun beobachte ich: Der Client und die dynamisch geladene Bibliothek besitzen eine andere Version der Variablen global_test. Aber in meinem Projekt verwende ich cmake. Das Build-Skript sieht wie folgt aus:

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) 

die erstellten makefile s Analyse fand ich, dass Cmake den Client mit

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

baut Dieser endet in einem etwas anderen, aber fatal Verhalten: Die global_test des Kunden und die dynamisch geladene Bibliothek sind gleich, werden aber am Ende des Programms zweimal zerstört.

Verwende ich cmake falsch? Ist es möglich, dass der Client und die dynamisch geladene Bibliothek das gleiche global_test verwenden, aber ohne dieses Doppelzerstörungsproblem?

+0

Meine erste Reaktion wäre, die Notwendigkeit für diese globale Variable in Frage zu stellen. –

+0

Ok, in meinem ursprünglichen Programm ist diese globale Variable eine Konstante, die von einer statisch verbundenen Bibliothek bereitgestellt wird. Aber nichtsdestoweniger wird es zweimal in der cmake-Version – phlipsy

+0

zerstört werden Das gleiche Problem würde für jedes Singleton-Muster gelten, so sehe ich kein Problem mit dem globalen – Elemental

Antwort

1

Meine erste Frage ist, wenn es einen bestimmten Grund gibt, für den Sie sowohl statisch als auch dynamisch (über dlopen) den gleichen Code verknüpfen?

Für Ihr Problem: -rdynamic exportiert die Symbole aus Ihrem Programm und was wahrscheinlich passiert, ist, dass dynamische Linker alle Verweise auf Ihre globale Variable auf das erste Symbol in Symboltabellen auflöst. Welches ist das, was ich nicht weiß.

EDIT: Ihr Zweck gegeben Ich würde verknüpfen Sie Ihr Programm auf diese Weise:

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

Unter Umständen müssen Sie den Auftrag beheben.

+0

Unter einigen Funktionen habe ich einige voluminöse Konstanten in der statisch verknüpften Bibliothek definiert. Die dynamisch geladene Bibliothek ist ein Plugin für den Client, das diese Funktionen verwendet, die von der statisch verknüpften Bibliothek bereitgestellt werden. – phlipsy

+0

@phlipsy: Die Art und Weise, wie Sie diese Konstanten verwenden sollten, besteht darin, die statische Bibliothek mit der Hauptprogrammdatei zu verbinden, die Hauptprogrammdatei mit -rdynamic zu verknüpfen (wie es cmake tut) und Plugins nicht mit der statischen Bibliothek zu verknüpfen. Ich weiß jedoch nicht, wie man das Plugin mit dem Programm verbindet. Sie können versuchen, den gemeinsamen Code (statische Bibliothek) zur dynamischen Bibliothek herauszufiltern und sowohl Programm als auch Plugin damit zu verknüpfen. – Tomek

+0

Dann zur Laufzeit verwendet das Plugin die Definitionen der verwendeten Symbole in der ausführbaren Client-Datei? Ist das legal? – phlipsy

2

Wenn Sie gemeinsam genutzte Bibliotheken verwenden, müssen Sie das Material definieren, das Sie mit einem Makro wie here exportieren möchten. Siehe DLL_PUBLIC-Makrodefinition dort.

+1

Nur unter Windows! – bmargulies

+0

Es ist ein allgemeines Makro. Ich arbeite sowohl unter GNU/Linux als auch unter Windows. Siehe #ifdef in der Deklaration. – INS

2

Standardmäßig wird der Linker keine globale Variable (ein "D") in der ausführbaren Basisdatei mit einer in einer gemeinsam genutzten Bibliothek kombinieren. Die ausführbare Basisdatei ist speziell. Es könnte einen obskuren Weg geben, dies mit einer der obskuren Kontrolldateien zu tun, die ich gelesen habe, aber ich bezweifle es.

--export-dynamic bewirkt, dass a.out-D-Symbole für gemeinsam genutzte Bibliotheken verfügbar sind.

Betrachten Sie jedoch den Prozess. Schritt 1: Sie erstellen ein DSO von einem .o mit einem 'U' und einem .a mit einem 'D'. Der Linker enthält also das Symbol im DSO. In Schritt 2 erstellen Sie die ausführbare Datei mit einem "U" in einer der O-Dateien und "D" in einem .a und dem DSO. Es wird versucht, die Links-zu-Rechts-Regel aufzulösen.

Variablen, im Gegensatz zu Funktionen, stellen auf jeden Fall gewisse Schwierigkeiten für den Modul-Linker dar. Eine bessere Vorgehensweise besteht darin, globale Var-Referenzen über Modulgrenzen hinweg zu vermeiden und Funktionsaufrufe zu verwenden. Dies würde jedoch immer noch für Sie scheitern, wenn Sie dieselbe Funktion sowohl in die Basis-ausführbare Datei als auch in eine gemeinsame Lib setzen.

+0

Sie meinen, die Verbindung zwischen dem Client * und * der Bibliothek gegen 'libbase.a' ist eine schlechte Idee? Ok, also schlagen Sie vor, die Bibliothek nicht mit 'libbase.a' zu verknüpfen, weil zur Laufzeit die Symbole vom Client für die Aufrufe in der Bibliothek genommen werden? Es funktioniert ... aber ist es legal? – phlipsy

+0

Ich habe es bearbeitet, um zu vereinfachen, da ich nicht ganz sicher bin, aus welcher Frage welche Dateien wo landen. – bmargulies

+0

Es gibt eine häufig verwendete statisch verknüpfte Bibliothek (libbase.a), eine dynamisch geladene Bibliothek (ein Plugin), die Teile von libbase.a verwendet, und einen Client, der das Plugin lädt und Teile von libbase.a benutzt. – phlipsy

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

CMake fügt -rdynamic Option geladene Bibliothek ermöglicht Symbole in der Lade ausführbare Datei zu lösen ... So kann man sehen, dass das ist, was Sie nicht wollen. Ohne diese Option wird dieses Symbol nur zufällig übersehen.

Aber ... Du solltest so etwas nicht machen. Ihre Bibliotheken und die ausführbare Datei sollten keine Symbole teilen, außer sie sollten wirklich geteilt werden.

Denken Sie immer an dynamische Verknüpfung als statische Verknüpfung.

0

Ich würde empfehlen, eine dlopen(... RTLD_LAZY|RTLD_GLOBAL); zu verwenden, um globale Symboltabellen zusammenzuführen.

0

Ich würde vorschlagen, jede .a statische Bibliothek erstellen, die Sie planen, zu einem dinamic Bibliothek zu verknüpfen, mit -fvisibility = hidden Parametern, so:

g ++ -Wall -fvisibility = hidden -gC Basis. cpp

+0

Können Sie erklären warum? – Charles

+0

Es tut mir leid, es war eine lange Zeit, als ich an diesem Problem beteiligt war und ich sehe den Kontext nicht mehr. Ich weiß jedoch, dass mein Vorschlag hilfreich war. War nicht? ;-) – smrt28

Verwandte Themen