dlclose() -> delete auf alle zeiger?!



  • Ich habe jetzt endlich zum thema ergoogeln können! Es ist wohl so das die shared library einen eigenen heap/heap manager besitzt. Dynamisch angeforderter Speicher muss daher innerhalb des moduls auch wieder entfernt werden. Im hauptprogramm kann da nix nachträglich gelöscht werden. Richtig? :xmas1:

    Forzsetzung folgt... 🙄

    P.S: Danke namensloser



  • könntest du mir bitte die url zu dieser information zeigen? wäre nett.

    hier ein programm, das im prinzip nichts anderes tut, als eine lib zu laden, die einen int allokiert. wenn du dir die orte ansiehst, an denen die ints (einer von der lib allokiert, einer direkt im programm) stehen, zeigt sich, dass hier der gleiche heap verwendet wird. außerdem passiert keine segfault und valgrind --tool=memcheck ./libexe zeigt auch keine fehler. es gibt nur einen heap.

    lib:

    extern "C" int* allocate(void) { return new int; }
    

    exe:

    #include <dlfcn.h>
    #include <iostream>
    
    typedef int* (*allocate)(void);
    
    int main(void) {
      void* handle = dlopen("./liblib.so.1", RTLD_LAZY);
      if(!handle) {
        std::cerr << "failed" << std::endl;
        return 1;
      }
    
      allocate a = reinterpret_cast<allocate>(dlsym(handle, "allocate"));
      if(!a) {
        std::cerr << "failed2" << std::endl;
        return 1;
      }
    
      int* data = a();
      std::cout << static_cast<void*>(data) << std::endl;
      *data = 123;
      int* data2 = new int;
      std::cout << static_cast<void*>(data2) << std::endl;
      dlclose(handle);
      std::cout << *data << std::endl;
      return 0;
    }
    


  • Für mal mein beispielprogramm aus ... du wirst schon sehen! *URL :xmas1: such*



  • Ich muss sagen ich verstehe das Problem nicht. Warum willst Du die Bibliothek überhaupt mit dlclose schließen, wenn Du sie noch benutzen willst? Das macht man am Programmende, fertig ist die Kiste, oder?



  • es ist nicht gesagt, dass er das plugin noch braucht. es ist sicher keine schlechte idee, plugins dann zu entladen, sobald sie nicht mehr benötigt werden.



  • Ich glaube es steht ausser frage des ist unangebracht ist einfach das plugin geöffnet zu lassen um den bug zu umgehen (Vll will der user später ja eine neue version des plugins in sein verzeichnis packen ohne dass seine bisherige arbeit weg ist, oder ich möchte mehr als ein plugin verwenden ohne dass ich ein stack voller plugin handle speichere ... 😉 )

    Ich habe hier nochmal ein einfaches beispiel gemacht... was denn irgendwer eine lösung?

    "daten":

    #ifndef DATA_H
    #define DATA_H
    
    #include <iostream>
    
    class data
    {
    public:
    	data(int d) : i(d) {}
    	virtual ~data() {}
    	virtual void print(){ std::cout <<  i << std::endl;}
    
    private:
    	int i;
    };
    
    #endif
    

    plugin:

    #include "data.h"
    
    extern "C" data* allocate(void)
    {
    	return new data(13);
    }
    

    programm:

    #include "data.h"
    
    using namespace std;
    
    typedef data* (*allocate)();
    
    int main()
    {
    	void* handle = dlopen("./liblib.so", RTLD_LAZY);
    	if(!handle){
    		std::cerr << "failed" << std::endl;
    		return 1;
    	}
    
    	allocate load = reinterpret_cast<allocate>(dlsym(handle, "allocate"));
    	if(!load) {
    		std::cerr << "failed2" << std::endl;
    		return 1;
    	}
    
    	data* d = load();
    
    	dlclose(handle);
    
    	d->print(); //absturz
    	delete d; //absturz
    }
    

    Also wenn das noch dieses Jahr gelöst wird, wäre weihnachten für mich gerettet! :xmas1: :xmas2: Danke danke! 🤡



  • Das Problem hier ist, das die Addresse unter der sich die funktion data::print befindet nicht mehr gültig ist. Das selbe gilt auch für die delete zeile, wobei es hier um die destructor-funktion handelt.

    Der Code für die Klasse data befindet sich einmal im Programm und einmal im plugin (liblib.so).

    Jede Methode einer Klasseninstanz ist im Speicher nur eine art funktions-zeiger auf die eigentliche Methode der Klasse, welche sich im Data-segment der geladenen Binary.
    In deinem Falle ist also der Code für die Klasse data zweimal im speicher vorhanden. Einmal im Data-Speicherbereich des Programms und das 2. mal im Data-Speicherbereich des Plugins.

    Beim erzeugen einer Instanz einer Klasse müssen dann die addressen der Methoden aufgelöst werden. Sprich die "funktions-zeiger" der Instanz müssen mit der addresse der dazugehörigen Methode, welche sich im data-segment der geladenen binaries befinden, gefüllt werden.

    Beim suchen der Addressen der gesuchten Methoden wird in dem binary in dem die Instanz erzeugt wurde begonnen. In diesem Falle findet der Laufzeitcode die gesuchten Methoden im data-Speicherbereich des Plugins.

    Durch das entladen des Plugins wird der Speicherbereich (data-segment), welche vom Plugin verwendet wurde, wieder freigegeben. Und dadurch sind die Addressen der Methoden in der Instanz der Klasse data, welche sich hinter der variable "d" verbirgt, nicht mehr gültig.

    Um das Beispiel zum laufen zu bringen gibt es folgende (mir bekannte) möglichkeiten:
    1. die Klasse implementiert einen Copy-Constructor, oder
    2. das Plugin wird erst nachdem verwenden der erzeugten Klasseninstanz entladen.

    Hier die Änderungen des Beispiel-codes
    zu Möglichkeit 1:

    data.h:

    #ifndef DATA_H
    #define DATA_H
    
    #include <iostream>
    
    class data
    {
    public:
        data(int d) : i(d) {}
    
        // Copy-Konstruktoren
        data(data *p)
        {
    	std::cout<<"*-copy-ctor"<<std::endl;
    	i = p->i;
        }
        data(data &p)
        {
            std::cout<<"&-copy-ctor"<<std::endl;
    	i = p.i;
        }
        virtual ~data() {std::cout<<"dtor data"<<std::endl;}
        virtual void print(){ std::cout <<  i << std::endl;}
    
    private:
        int i;
    };
    
    #endif
    

    programm:

    #include "data.h"
    #include "dlfcn.h"
    using namespace std;
    
    typedef data* (*allocate)();
    
    int main()
    {
        void* handle = dlopen("./liblib.so", RTLD_LAZY);
        if(!handle){
            std::cerr << "failed" << std::endl;
            return 1;
        }
    
        allocate load = reinterpret_cast<allocate>(dlsym(handle, "allocate"));
        if(!load) {
            std::cerr << "failed2" << std::endl;
            return 1;
        }
    
        data *d = load();
        data p(d);
        delete d;
    
        dlclose(handle);
    
        p.print();
    }
    

    Zur Möglichkeit 2:

    Programm:

    #include "data.h"
    
    using namespace std;
    
    typedef data* (*allocate)();
    
    int main()
    {
        void* handle = dlopen("./liblib.so", RTLD_LAZY);
        if(!handle){
            std::cerr << "failed" << std::endl;
            return 1;
        }
    
        allocate load = reinterpret_cast<allocate>(dlsym(handle, "allocate"));
        if(!load) {
            std::cerr << "failed2" << std::endl;
            return 1;
        }
    
        data* d = load();
    
        d->print();
        delete d;
        dlclose(handle);
    }
    


  • Herzlichen dank für die erklärung!

    Ich mache es derzeit ähnlich wie in Möglichkeit 1 von dir beschrieben, habe das allerdings mehr als notlösung angesehen .... Bringt der kopierkonstruktor gegenüber operator= irgnendwelche Vorteile?



  • Pigeon schrieb:

    Herzlichen dank für die erklärung!

    Ich mache es derzeit ähnlich wie in Möglichkeit 1 von dir beschrieben, habe das allerdings mehr als notlösung angesehen .... Bringt der kopierkonstruktor gegenüber operator= irgnendwelche Vorteile?

    Ach den zuweisungs-operator (operator 🙂 habe ich ja ganz vergessen 🙂
    Soweit ich weis hat der Copy-Konstruktor eigentlich keine Vorteile gegenüber dem zuweisungs-operator.
    Es kann aber sinnvoll sein beide zu implementieren, denn ich hatte schon das phänomen, das der Copy-Konstruktor vom compiler verwendet wurde, obwohl ich dachte der zuweisungs-operator würde verwendet werden.

    egal du du jetzt den Copy-Konstruktor, den Zuweisungs-operator oder beide verwendest, in deinem Beispiel musst du auf jeden fall über eine temporäre variable gehen, um das im plugin dynamische(per new) erzeugte Objekt wieder freizugeben.
    Ansonsten erzeugst du ein Speicherleak.



  • zuweisungsoperator und copy constructor sind zwei ganz verschiedene dinge. man kann sich nicht für eines entscheiden. der zuweisungsoperator wird verwendet, um einer existierenden instanz einer klasse, etwas neues zuzuweisen. der copy constructor wird verwendet, um eine neue instanz als kopie einer anderen zu erzeugen.

    klasse k1;
    klasse k2 = k1;
    klasse k3(k1);
    

    hier wird 2 mal der copy-constructor aufgerufen. auch in zeile 2 und trotz des zuweisungszeichens.
    wenn du eine deep copy (ich weiß, ein fachvokbal... http://de.wikipedia.org/wiki/Kopierkonstruktor#Verwendung) erzeugen willst, musst du zumindest den copy constructor implementieren. die zuweisung kannst du auch auf andere art (z.b. per assign()-methode) realisieren.



  • 🤡 Hallo allerseits.

    Sorry, aber ich bin immernoch nicht zufrieden mit der jetzigen Lösung! Ich lade in meiner library einen relativ komplexen "Datenbaum", der schlicht dynamisch Speicher anfordern muss! Nun ist es einfach zu zeitaufwändig alles nocheinaml durchlaufen zu lassen um die daten erstmal nutzbar zu machen?! Gibt es denn wirklich keine andere Lösung? Meine google-Bemühungen haben mich zu Memory-Management und Virtuellem Speicher geführt ... hilft das in diesem Fall was? Oder gibt es nicht irgendeine bessere Lösung?

    Ein guten Rutsch! 🙂



  • wo genau liegt das problem? bekommst du immer noch segfaults?



  • namenlos schrieb:

    wo genau liegt das problem? bekommst du immer noch segfaults?

    Nein, der code läuft nur sehr langsam:
    Ich habe eine reihe von klassen mit virtuellen Funktionen und einer abstrakten Basisklasse. In meiner library werden die dynmaisch angefordert und in einer Art BS-Tree gespeichert. Wenn ich also nun das zeug lade muss ich alles neu allocieren, damit ich meine virtuellen Funktionen auch ohne segfault nutzen kann (analog zu fireflys vorschlag):

    //dlopen etc. ...
        allocate load = reinterpret_cast<allocate>(dlsym(handle, "allocate"));
        if(!load) {
            std::cerr << "failed2" << std::endl;
            return 1;
        }
    
        data *d = load();
        data* p = new data(*d);
        //Ab jetzt kann ich die daten erst nutzen!
        delete d;
    
        dlclose(handle);
    

    Ich muss jedoch durch den gesamten baum erstmal durchlaufen. Neues element erstellen, eingliedern, altes löschen.... DIE FOLGE IST DOPPELETE LADEZEIT!
    Oder versteh ich hier was grundlegendes nciht?!

    //Edit: Oder baue ich das zeug vielleicht falsch?!

    g++ -Wall -g -fPIC -c plugin.cpp -o plugin.o
    g++ -Wall -g -shared -fPIC plugin.o -o plugin.so



  • Ich habe nun eine (zugegeben grauenvolle) "Lösung" gefunden *grml*:
    Hoffentlich ist sie nur vorübergehend...

    Eine weitere shared library wird im hauptprogramm als erstes geladen und bleibt bis zum bitteren ende göffnet! 😉
    Sie ist nämlich für das allokieren zuständig ... genug geredet! CODE:

    main.cpp

    #include <iostream>
    #include <dlfcn.h>
    
    #include "allocator.h"
    #include "plugin.h"
    
    int main()
    {
    	dlerror();
    
    	void* alloHandle = dlopen("./allocator.so", RTLD_LAZY);
    	if(!alloHandle){
    		std::cout << "error: " << dlerror() << std::endl;
    		return 0;
    	}
    	new_datatype_func_t new_datatype = (new_datatype_func_t)dlsym(alloHandle, "new_datatype");
    	if(!new_datatype){
    		std::cout << "error: " << dlerror() << std::endl;
    		return 0;
    	}
    
    	dlerror();
    
    	void *handle = dlopen("./plugin.so", RTLD_LAZY);
    	if(!handle){
    		std::cout << "error: " << dlerror() << std::endl;
    		return 0;
    	}
    
    	dlerror();
    	load_func_ptr load = (load_func_ptr)dlsym(handle, "load");
    	if(!load){
    		std::cout << "error: " << dlerror() << std::endl;
    		return 0;
    	}
    
    	data whatiwant;
    	load(whatiwant, new_datatype);
    
    	dlclose(handle);
    
    	if(!whatiwant.content.empty()){
    		whatiwant.content.back()->func();
    		whatiwant.content.back()->vfunc();
    	}
    	delete whatiwant.content.back(); 
    
    	std::cout << "Normales Programmende?! ;-)" << std::endl;
    
    	dlclose(alloHandle);
    };
    

    plugin.h

    #ifndef PLUGIN_H
    #define PLUGIN_H
    
    #include "datatype.h"
    #include "allocator.h"
    
    #include <vector>
    
    struct data
    {
    	std::vector<datatype*> content;
    };
    
    typedef void (*load_func_ptr)(data&, new_datatype_func_t);
    
    #endif
    

    plugin.cpp

    #include "plugin.h"
    
    #include <dlfcn.h>
    #include "allocator.h"
    
    extern "C" void load(data& target, new_datatype_func_t new_datatype)
    {
    
    //	target.content.push_back(new datatype(13));
    	target.content.push_back(new_datatype(13));
    }
    

    allocator.h

    #ifndef ALLOCATOR_H
    #define ALLOCATOR_H
    
    #include "datatype.h"
    
    typedef datatype* (*new_datatype_func_t)(int);
    
    #endif
    

    allocator.cpp

    #include "datatype.h"
    
    template<class T, class A> T* new_T(A arg)
    {
    	return new T(arg);
    }
    
    extern "C" datatype* new_datatype(int i)
    {
    	return new_T<datatype, int>(i);
    }
    


  • ähm wiso willst du überhaupt das plugin, welches den "Baum" erstellt überhaupt sofort nach dem erzeugen des baums wieder entladen?



  • ich denke, dass wir die ganze source brauchen, um dir zu helfen.

    data *d = load(); 
      data* p = new data(*d);
    

    scheint mir keine kluge lösung zu sein. mir ist nicht klar, wieso du das so machst und nicht einfach die daten, auf die d zeigt, weiter verwendest. werden die daten, auf die d zeigt, nicht dynamisch allokiert?



  • namenlos schrieb:

    ich denke, dass wir die ganze source brauchen, um dir zu helfen.

    data *d = load(); 
      data* p = new data(*d);
    

    scheint mir keine kluge lösung zu sein. mir ist nicht klar, wieso du das so machst und nicht einfach die daten, auf die d zeigt, weiter verwendest. werden die daten, auf die d zeigt, nicht dynamisch allokiert?

    naja das problem ist, das "d"(type data) nicht nur einfach Daten enthält sondern auch Methoden, welche Pigeon anscheinend im weiteren Programmablauf verwenden möchte. Da Pigeon nachdem Aufruf von load das Plugin entladen möchte (wiso auch immer) zeigen die "Funktionszeiger" des Objektes, nachdem Entladen des Plugins, auf ungültige Adressen.
    Das ist auch der Grund für den Absturz den er in seinem Ausgangspost beschrieben hat.



  • Zu fireflys ausführung habe ich nichts mehr hinzuzufügen 🙂

    Ihr meint also ich sollte sämtliche plugins die ganze zeit geöffnet lassen? Die daten aus dem plugin werden wohl im programm die ganze zeit genutzt werden, wobei das laden 1-30s dauert... Zudem dachte ich mir, dass der benutzer evtl ein plugin aktualisieren möchte ohne dass alles abstürzt und die daten verloren gehen... Natürlich muss er dass nicht können, aber wenn er es einfach ausprobiert... ? 🙄

    Was stichhaltigeres fällt mir im Moment nicht ein... 😃 😉



  • Pigeon schrieb:

    Zu fireflys ausführung habe ich nichts mehr hinzuzufügen 🙂

    Ihr meint also ich sollte sämtliche plugins die ganze zeit geöffnet lassen? Die daten aus dem plugin werden wohl im programm die ganze zeit genutzt werden, wobei das laden 1-30s dauert... Zudem dachte ich mir, dass der benutzer evtl ein plugin aktualisieren möchte ohne dass alles abstürzt und die daten verloren gehen... Natürlich muss er dass nicht können, aber wenn er es einfach ausprobiert... ? 🙄

    Was stichhaltigeres fällt mir im Moment nicht ein... 😃 😉

    Naja es gibt folgende Möglichkeiten (bestimmt unvollständig)
    - das Programm erlaubt das neuladen von aktualisierten Plugins nur beim Programmstart
    - wenn ein Plugin aktualisiert wird, müssen die Daten neu generiert werden.

    Aber mal ne andere Frage:
    Was sollen die Plugins denn genau machen können?
    Liefern die Plugins nur Daten die in der Klasse Data Verwendung finden? Können die Plugins die Klasse Data um weitere Methoden ergänzen oder werden die Methoden vom Programm festgelegt?

    Wenn die Plugins nur ein Daten liefern sollen, wäre es eventuell sinnvoll, das die Plugins nicht ein Data Objekt selbst erzeugt sondern ihre Daten in ein bestehendes Objekt "übertragen".



  • Jetzt mal ganz konkret:
    Ich lade 3D Dateien vom Typ *.obj (Wavefront) und *.3ds (3D Stduio). Ich hab vorher keine ahnung wie viel da drin steht und was. Also muss das zeug dynmaisch angefordert werden. Ein bestehendes Objekt zu verwenden ist somit schwierig (oder?). Zumal es ja nicht auf 1 beschränkt ist... Fast alle klassen haben dabei mindestens eine virtuelle funktion.

    P.S:
    Wie es scheint funktioniert es wenn ich einen zeiger ans plugin übergebe, der auf eine funktion im hauptprogramm zeigt die dann irgendwas allokiert. Im plugin wird dann natürlich ausschließlich diese mehthode verwendet. Was haltet ihr davon?


Anmelden zum Antworten