Mit Shared Libs auf Funktionen im Hauptprogramm zugreifen?



  • hm, gibt es sowas auch unter Windows? und wie verhinder ich dann, dass der compiler beim kompilieren der .so/.dll über die fehlenden referenzen meckert?

    Ich will auch nochmal etwas näher in die Details gehen, dass ich noch etwas besser verstanden werde. (ich rede hier mal von dll statt shared library)

    Also, ich habe zum einen die Hauptanwendung und die DLL. Die Hauptanwendung hat ein Objekt vom der Klasse Client. Diese Klasse hat verschiedene Methoden und Attribute. Dann wird die DLL vom Hauptprogramm dynamisch zur laufzeit geladen und eine Funktion der DLL aufgerufen. Dieser Funktion wird ein Pointer auf das Client-Objekt übergeben. Die DLL soll jetzt auf die Methoden des Client-Objektes Zugreifen können, OHNE, dass die entsprechenden Methoden beim Kompilieren der DLL mit ringelinkt wurden.
    Das würde zum einen die DLL verkleinern, weil die Methoden dann nur einmal vorhanden sind (im Hauptprogramm), statt in beiden Teilen und würde auch die Wartung vereinfachen, weil man die Methode nur noch im Hauptprogramm ändern muss, aber nicht mehr in den DLLs.

    Mit Funktionspointern bzw. Funktors hab ich auch schon überlegt, aber das währe ein extremer Aufwand, wenn das Hauptprogramm viele Objekte mit vielen Methoden zur Verfügung stellt, gibt es da auch was komfortableres?



  • hallo, eigentlich sollte es reichen, wenn du die methoden von Client alle virtuell machst.

    du musst dann in der dll natürlich die header-datei includen.

    die implementierung der methoden kann dann aber in dem hauptprogramm liegen.

    wenigstens klappt das bei mir so, das geht dann auch zwischen dlls untereinander.

    ciao,
    jenz



  • So wie du eine dll laden kannst kannst du auch eine exe laden, kommt aufs gleiche raus, musst die Funktionen nur exportieren, dann kann man sie aus der dll heraus laden. Ob du eine exe statisch (im Sinne von __declspec( dllexport/dllimport ) zur dll linken kannst, kann ich dir jetzt nicht sagen, aber im Zeifelsfall kannst es ja dynamisch machen (ist eh besser, dann kannst du bessere Fehlermeldungen ausgeben).



  • Danke jenz, ich glaube, das war genau die lösung, die ich gesucht habe. Weis zwar noch nicht, ob das so funktioniert, wie ich es mir vorstelle, weil ich das erst morgen ausprobieren kann, aber es linkt jetzt zumindest auch ohne implementierung.

    Danke!



  • Du schreibst ne mittelgroße Applikation und weißt sowas nicht?
    Das wird was für thedailywtf.com



  • Eine für mich mittelgroße Applikation 🙂 (eigendlich sogar bis jetzt meine größte).

    Ich wusste bis jetzt durchaus, was virtuelle Methoden sind, aber ich wusste nicht, dass das auch geht ohne den code zu kennen, der später da rein kommt 🙂



  • Hm, irgendwie will das bei mir nicht 😞

    Ich habe jetzt die Methoden virtuell gemacht und die dll linkt auch, aber sobald ich aus der DLL heraus eine der Methoden aufrufen will stürzt mir das Programm ab 😞



  • zeig doch mal ein bisschen code,
    oder schick es mir zu ...

    eigentlich müsste das funktionieren.

    jenz



  • Hab den Fehler gefunden, hatte in paar virtual deklartionen im Hauptprogramm vergessen.



  • Was muss ich eigendlich bei der Programmierung mit virtuellen Methoden alles beachten, weil, die Header von Hauptprogramm und DLL scheinen ja exakt gleich sein zu müssen, damit das Funktioniert. Sprich, wenn ich die API im Hauptprogramm erweiter muss ich auch den Header der DLL anpassen, auch wenn die die neue Funktion gar nicht benutzt, oder seh ich das falsch?

    Wird vor allem später dann blöde sein, wenn ich DLLs/Plugins von Drittanbietern haben sollte, dann müssen die ja jedes mal neu Kompiliert werden, wenn sich etwas an der API ändert 😕



  • @FloFri
    Wenn du die API änderst, müssen alle Plugins neu kompiliert werden. Daher versuchen viele Projekte API Änderungen zu vermeiden und eine API Änderung ist oft ein Grund dafür die Major-Versionsnummer zu ändern.



  • Es geht mir ja nicht darum, die Signatur einer Funktion oder die Funktionsweise zu ändern.

    Ich meinte eher das folgende:
    Wenn wir mal von einem Socket-Objekt aus einer Netzwerk-Klasse ausgehen und sagen wir mal, das hat Funktionen zum connecten, senden und empfangen und wird von dem Plugin benutzt.
    Jetzt kommen aber noch Funktionen für einen Server, also zum beispiel listen dazu, dann müsste, wenn ich das richtig sehe, bei der vorgehensweise mit den virtuellen methoden, gleich alle plugins, die so einen socket benutzen neu kompiliert werden, auch wenn sie die listen-methode gar nicht verwenden.
    Wenn ich zum Beispiel funktionspointer benutzen würde, währe das nicht der fall, aber funktionspointer sind eben um einiges aufwendiger, als virtuelle methoden.



  • Sieht da noch jemand eine Möglichkeit das Problem auf eine einfache Art und Weise zu lösen oder muss ich mir doch einen komplexen Pluginhandler mit Funktionspointern/Funktoren basteln?



  • Hallo,

    warum nimmst du dann nicht einfach eine weitere "virtuelle plugin klasse" her?

    Und dann mit dynamischen Casts herausfinden, um welche Klasse es sich wirklich handelt...

    jenz



  • Hm, ich verstehe nicht ganz, wie du das meinst, also, jedes mal, wenn ich die API erweiter soll ich von der vorherigen Klasse eine neue ableiten und ergänzen?



  • Hallo,

    ableiten wäre wahrscheinlich am sinnvollsten.
    Wenn ich das jetzt richtig verstanden habe funzt es ja so, dass eine dll geladen und von ihr ein Objekt an das Hauptprogramm übergeben wird, oder?
    Oder funktioniert es andersrum? Egal, jeweils der der das Objekt empfängt muss halt im Rahmen seiner Kenntnis herausfinden, um was für einen Typ es sich handelt.
    Du übergibst ja bestimmt nur einen Pointer, oder?

    Zeig doch mal ein bisschen Code, wo dein Problem auftreten könnte. Das ist so luftleer einfach unklarer zu erklären...

    jenz



  • Ok, ich poste mal ein bisschen Beispielcode:

    Hier die DLL-Files

    main.cpp

    #include <iostream>
    
    #include <windows.h>
    #include "client.h"
    
    using namespace std;
    
    #define _DLL_FUNCTION extern "C" __declspec(dllexport)
    
    _DLL_FUNCTION void myFunc(Client *myclitest);
    
    // a sample exported function
    void myFunc(Client *myclitest)
    {
        cout << "Calling Client-Function" << endl;
        myclitest->ShowString();
    }
    
    BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
    {
        switch (fdwReason)
        {
            case DLL_PROCESS_ATTACH:
                // attach to process
                // return FALSE to fail DLL load
                break;
    
            case DLL_PROCESS_DETACH:
                // detach from process
                break;
    
            case DLL_THREAD_ATTACH:
                // attach to thread
                break;
    
            case DLL_THREAD_DETACH:
                // detach from thread
                break;
        }
        return TRUE; // succesful
    }
    

    client.h

    #ifndef CLIENT_H
    #define CLIENT_H
    
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    class Client
    {
        public:
            Client(string initStr);
            virtual ~Client();
            virtual void ShowString();
    };
    
    #endif // CLIENT_H
    

    Hier das Hauptprogramm

    main.cpp

    #include <iostream>
    #include <windows.h>
    
    #include "client.h"
    
    using namespace std;
    
    typedef void (WINAPI * p_myFunc) (Client *myclitest);
    
    int main()
    {
        Client* myCli = new Client("ichbins");
    	cout << "Loading DLL" << endl;
    
        HINSTANCE dllInstance = LoadLibrary("testdll/testdll.dll");
    
        if (dllInstance == NULL)
        {
            cout << "Can not load!" << endl;
            return 1;
        }
    
        p_myFunc myFunc = (p_myFunc)GetProcAddress((HMODULE)dllInstance, "myFunc");
    
        if (myFunc == NULL)
        {
            cout << "Error" << endl;
            return 1;
        }
    
        cout << "Calling DLL-Function" << endl;
        myFunc(myCli);
    
    	return 0;
    }
    

    client.h

    #ifndef CLIENT_H
    #define CLIENT_H
    
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    class Client
    {
        public:
            Client(string initStr);
            virtual ~Client();
            virtual void ShowString();
            void check();
        private:
            string myStr;
    };
    
    #endif // CLIENT_H
    

    client.cpp

    #include "client.h"
    
    Client::Client(string initStr)
    {
        myStr = initStr;
    }
    
    Client::~Client()
    {
    }
    
    void Client::ShowString()
    {
        cout << myStr << endl;
    }
    

    So. Soweit funktioniert das Beispielprogramm auch. Jetzt zu meinem Problem.
    Gehen wir mal davon aus, die DLL kommt von einem Drittanbieter und es gibt auch noch eine Reihe Anderer.

    Jetzt füge ich eine neue Funktion

    virtual void SetString(string newString);
    

    zu der Client-Klasse im Hauptprogramm hinzu, damit die Dlls bei Bedarf noch etwas mehr machen können.
    Jetzt muss ich aber alle Dlls neu kompilieren, bzw. kompilieren lassen, da die VMT der Klasse Client in dem Hauptprogramm und in den Dlls nicht mehr übereinstimmt.



  • mit vmt meinst du die virtual method table? nur um sicherzugehen.

    ja, stimmt. die könnte sich verändern. wahrscheinlich auch dann, wenn man ableitet.
    aber eigentlich sollte die sich nicht mit jeder neuen methode vergrößern, die reservieren meist mehr platz als nötig. aber das ist dann natürlich compilerabhängig. also eher unsicher und damit unbrauchbar.

    wie wäre es, wenn du für neue funktionalitäten neue klassen einführst und dazu auch passende myfuncs, dann kannst du daran entscheiden, welche klasse die dll haben will und übergibst eben die entsprechende.
    dann ändert sich die gemeinsame header datei nicht mehr..

    warum hast du eigentlich zwei client.h dateien gepostet?

    ciao,
    jenz



  • Gibt da zwei Möglichkeiten die mir einfallen:
    a) Du fügst neue Funktionalität hinzu indem du ein neues Interface erstellst, dass das alte erbt, dann können neue Plug-Ins das neue nehmen und alte Plug-Ins laufen weiterhin, ungefähr so:

    class SocketInterfaceVersion1
    {
      public:
      //Methoden
    };
    
    class SocketInterfaceVersion2 : public SocketInterfaceVersion1
    {
      public:
      //Neue Methoden
    };
    

    b) Du benutzt die Interfaces nicht mehr direkt sondern über Proxies welche automatisch für jedes Plug-In die passende Version raussuchen.
    Einen Artikel dazu gibts hier



  • Ja, die Virtual Method Table meinte ich. Die wird ja inkonsistent, wenn ich im Hauptprogramm eine neue virtuelle Methode hinzufüge, in der DLL aber nicht.

    Ich hab zwei client.h gepostet, weil die eine die ist, die in der DLL verwendet wird und die andere die, die im Hauptprogramm verwendet wird.

    Mit den Proxys, das schau ich mir mal an, das könnte eventuel was sein.


Anmelden zum Antworten