Dll Fragen



  • Hi com,

    Ich arbeite gerade mit VC++ 2008 an DLLs, von denen ich explizit Functionen importieren möchte, also so:

    test.cpp

    #include <windows.h> 
    #include <iostream>
    using namespace std; 
    
    typedef int (__cdecl *testfunc_t)(int); 
    
    VOID main(int argc, char** argv) 
    { 
        HINSTANCE hDLL; 
        testfunc_t testfunc;   
    	hDLL = LoadLibrary(TEXT("C:\\der\\pfad\\test.dll")); 
    
        if (hDLL != NULL) 
        { 
            testfunc = (testfunc_t)GetProcAddress(hDLL, "testfunc"); 
    
            if (NULL != testfunc) 
    			cout << "Func: " << testfunc(10) << endl; 
    
    		FreeLibrary(hDLL); 
        } 
    
    	cin.sync();
    	cin.get();
    }
    

    die DLL exportiert mithilfe einer DEF Datei:

    exp.def

    LIBRARY   test
    EXPORTS
       testfunc  @1
       moreTests @2
    

    Es funktioniert prima aber wenn ich Funktionen mit __declspec(dllexport) exportiere, dann geht das nicht, weill die Funktionen dann plötzlich etwa so heißen wie "?testfunc@@YAHH@Z".

    Hab ich irgendwie einen Einfluss über __declspec(dllexport), wie die Funktionen nachher heißen?

    Nebenbei würde mich auch interessieren, wie man auf diese Art und Weise Klassen importiert bzw. ob man Funktionen anhand ihrer Ordinazahl importieren kann wie

    testfunc = (testfunc_t)GetProcAddress(hDLL, "@1" /* "testfunc" */ );
    

    Vielen Dank im voraus!

    Mfg



  • die funktionen wurden von einem c++ komplier erzeugt. da c++ funktionen überladen kann, müssen sie durch diese buchstaben/zeichen einheitlich gekennzeichent werden.

    um die funktionen nicht als c++ sondern als c funktion zu markieren gibt es extern"C"

    extern "C"
    {
    int func(int wert)
    {
        // mach was tolles mit dem wert
    }
    }
    

    zur ordinalzahl:

    msdn suchen !

    um eine funktion per ordinal zu laden musst du im lower-word die zahl haben, im higher-word eine 0.

    [cpp]
    GetProcAddress(hDll, reinterpret_cast<LPCSTR>(diezuladnedeordinalzahl));



  • zuschnell abgeschickt...naja

    klassen exportieren:

    die anwendung definiert eine basisklasse mit virtuellen funktionen (im header)

    class test
    {
    public:
        void foo() = 0;
    };
    

    (die dll muss den header auch haben oder die klasse genauso definieren)

    jetzt implemetierst du die zu exportierende klasse in der dll

    class dlltest : public test
    {
    public:
        void foo(){cout << "huhu" << endl;}
    };
    

    dann bruachst du noch einen loader, der die instanz erzeugt

    test *Create()
    {
        return new dlltest;
    }
    

    diesen loader holst du dir mit getprocaddress.

    jetzt kannst du in der anwendung die klasse ganz normal benutzen.

    hinweis:

    wenn du das objekt löschst kann es zu einem zugriffsfehler kommen, da
    das objekt im speicherraum der dll liegt.

    dann musst du nur eine löschfunktion exportieren

    void Destroy(test *t)
    {
        delete t;
    }
    


  • Siehe:
    http://blog.m-ri.de/index.php/2008/02/22/vs-tipps-tricks-benoetigt-man-eigentlich-noch-def-dateien/

    Mein Rat: Wenn Du *richtige* Namen haben willst, dann verwende DEF-Dateien.

    PS: Klassen kannst Du nicht via GetProcAddress importieren.



  • Thx an alle, jtzt weis ich bescheid:)

    Hmm aber gibt es noch andere Möglichkeiten Klassen explizit zu importieren?

    Wenn man es mit einer fremden Dll zu tun hat ist das ja etwas schwieriger...

    Mfg
    DLLnub



  • nein man kann eigentlich keine klassen exportieren. mit dem beispiel dass ich dir gezeigt habe, konnte man die vtbl und damit die funktionen exportieren. das läuft dann über die schnittstelle in der anwendung. du kannst aber nicht auf einmal neue member der klasse hinzufügen oder neue funktionen.



  • Wenn ich so eine quasi C-Schnittstelle (Create, Destroy) in der Dll habe, kann ich diese Dll dann mit jedem C++-Compiler nutzen (normal geht das ja nicht bei Klassen)?



  • Wenn du Klassen aus einer DLL exportieren willst, dann musst du COM verwenden.




  • Mod

    Redhead schrieb:

    Wenn du Klassen aus einer DLL exportieren willst, dann musst du COM verwenden.

    Wenn man aber Klassen einfach nutzen will, benötigt man nur ein einfaches Interface Schema und eine Factory Methode, die das Erzeugen und Zerstörn besorgt...

    COM exportiert auch keine Klassen und macht es letzten Endes auch nur so indem Interfaces ausgetauscht werden. Aber oftgenug ist COM einfach zuviel Overhead.



  • dllll schrieb:

    wenn du das objekt löschst kann es zu einem zugriffsfehler kommen, da
    das objekt im speicherraum der dll liegt.

    dann musst du nur eine löschfunktion exportieren

    void Destroy(test *t)
    {
        delete t;
    }
    

    Wenn das Objekt einen virtuellen Destruktor hat, ist das nicht nötig; siehe hier.

    YaDllNub schrieb:

    Wenn ich so eine quasi C-Schnittstelle (Create, Destroy) in der Dll habe, kann ich diese Dll dann mit jedem C++-Compiler nutzen (normal geht das ja nicht bei Klassen)?

    Nein, das ist nach wie vor compilerspezifisch, da vom C++-Standard kein ABI vorgeschrieben ist. Für so etwas muß entweder eine C-Schnittstelle oder COM verwendet werden.


  • Mod

    audacia schrieb:

    YaDllNub schrieb:

    Wenn ich so eine quasi C-Schnittstelle (Create, Destroy) in der Dll habe, kann ich diese Dll dann mit jedem C++-Compiler nutzen (normal geht das ja nicht bei Klassen)?

    Nein, das ist nach wie vor compilerspezifisch, da vom C++-Standard kein ABI vorgeschrieben ist. Für so etwas muß entweder eine C-Schnittstelle oder COM verwendet werden.

    Nein muss man nicht. Man kann auch dieses Interface Cmpiler neutral aufbauen. Macht COM ja auch!

    Ich wüsste aktuell keinen C++ Compiler, der keine COM Interfaces unterstützt, die nicht mit einer reinen virtuellen Interface Klasse identisch sind.
    Oder weißt Du da gegenteiliges?



  • Alternativ kann man in der Dll auch mit einem Objekt arbeiten und von außerhalb mit normalen API-Funktionen darauf zugreifen. Ein Beispiel ohne Fehlerbehandlung:

    HANDLE CreateObject()
    {
        CTest* pTest = new CTest;
        return reinterpret_cast<HANDLE>(pTest);
    }
    
    BOOL DoSomething(HANDLE hTest, int iParam1, int iParam2)
    {
        return reinterpret_cast<CTest*>(hTest)->DoSomething(iParam1, iParam2);
    }
    
    void DestroyObject(HANDLE hTest)
    {
        delete reinterpret_cast<CTest*>(hTest);
    }
    


  • Martin Richter schrieb:

    Nein muss man nicht. Man kann auch dieses Interface Cmpiler neutral aufbauen. Macht COM ja auch!

    Ich wüsste aktuell keinen C++ Compiler, der keine COM Interfaces unterstützt, die nicht mit einer reinen virtuellen Interface Klasse identisch sind.
    Oder weißt Du da gegenteiliges?

    Na, dann viel Vergnügen damit:

    • sind die Destruktor-Parameter standardisiert?
    • kann es sein, daß einige Compiler eine Klasse nur dann an die COM-Spezifikation anpassen, wenn sie dazu explizit aufgefordert werden (also wenn du explizit eine COM-Klasse erstellst)?
    • wie sehen VTable und RTTI-Descriptor aus, wo im Objektlayout wird auf sie verwiesen?
    • ist das Klassenlayout eindeutig? Im BCC gibt es beispielsweise zwecks Abwärtskompatibilität diverse Optionen, um das Objektlayout an frühere Versionen anzupassen.
    • wie setzt der Compiler Mehrfach- und virtuelle Vererbung um?
    • was passiert, wenn das Klasseninterface mit Iteratoren, Containern oder anderen Dingen der Standard-Library arbeitet?
    • etc. pp.

    Daß die gängigen Compiler COM unterstützen, heißt noch lange nicht, daß sie immer binärkompatible Klassenlayouts erzeugen.



  • sri schrieb:

    Alternativ kann man in der Dll auch mit einem Objekt arbeiten und von außerhalb mit normalen API-Funktionen darauf zugreifen. Ein Beispiel ohne Fehlerbehandlung:

    <snip>
    

    So ähnlich funktioniert das z.B. in COM+. Dort gibt es zusätzlich noch Klassen-Wrapper, die vollständig im Header implementiert sind - auch eine Möglichkeit, wenngleich sehr arbeitsintensiv.

    Ein Programm, das diesen Prozeß automatisiert und all diese Wrapperklassen und -funktionen zu generieren imstande ist, wäre reizvoll 🙂


  • Mod

    Mir ging es in keiner Weise um RTTI und virtuele Destruktoren! Geau das ist der Punkt.

    Wenn ich nur mit

    class IFoo
    {
    public:
     virtual void Foo1() = 0;
     virtual void Foo2(int i) = 0;
     virtual void Foo32(char *p) = 0;
    };
    

    eine Interface Klasse anlege und nur deren Zeiger austtausche, dürte es nicht schwierig sein mit allen Compilern eine entsprechende Funktionalität zu erzeugen.
    Jetzt brauche ich nurnoch ene Factory zum Erzeugen UND Zerstören, denn auf einen virtuelen Destruktor kann ich mich eben nicht verlassen... 😉

    Alle Deine Einwürfe (RTTI, virtueller Destruktor, Mehrfachvererbung) lass ich gelten, aber ich will Sie ja nicht nutzen. Genauso wenig wie sie COM nutzt!

    Meine Frage war:
    Welcher Compiler würde mit soetwas nicht zurande kommen, vorrausgesetzt man bekomt auch die Calling Konventions über entsprechende #defines übereinander.



  • Martin Richter schrieb:

    Wenn ich nur mit

    class IFoo
    {
    public:
     virtual void Foo1() = 0;
     virtual void Foo2(int i) = 0;
     virtual void Foo32(char *p) = 0;
    };
    

    eine Interface Klasse anlege und nur deren Zeiger austtausche, dürte es nicht schwierig sein mit allen Compilern eine entsprechende Funktionalität zu erzeugen.
    Jetzt brauche ich nurnoch ene Factory zum Erzeugen UND Zerstören, denn auf einen virtuelen Destruktor kann ich mich eben nicht verlassen... 😉

    Das mag soweit sogar funktionieren, aber IMHO ist gerade der Punkt mit dem Destruktor essentiell, weil er verhindert, daß der Zeiger in einem herkömmlichen Smart-Pointer untergebracht werden kann. Eine Wrapperklasse könnte hier Abhilfe schaffen (vielleicht ähnlich wie DelphiInterface<> in C++Builder?).

    Weiter wäre zu verifizieren, daß ein Klassenlayout in jedem Fall COM-kompatibel ist, auch wenn die Klasse nicht explizit als COM-Klasse deklariert wurde.

    Martin Richter schrieb:

    Meine Frage war:
    Welcher Compiler würde mit soetwas nicht zurande kommen, vorrausgesetzt man bekomt auch die Calling Konventions über entsprechende #defines übereinander.

    Wie gesagt: das bleibt zu überprüfen. Das werde ich bei Gelegenheit mal angehen, da die Frage mich auch interessiert. (Es ist btw sogar möglich, wenngleich nur mit C++Builder, auf ähnlichem Wege C++-Objekte in Delphi zu verwenden, insofern ist es ganz unwahrscheinlich nicht.)


  • Mod

    audacia schrieb:

    Das mag soweit sogar funktionieren, aber IMHO ist gerade der Punkt mit dem Destruktor essentiell, weil er verhindert, daß der Zeiger in einem herkömmlichen Smart-Pointer untergebracht werden kann. Eine Wrapperklasse könnte hier Abhilfe schaffen (vielleicht ähnlich wie DelphiInterface<> in C++Builder?).

    Selbst dies kann man mit einem eigenen Design sofort ösen, wenn z.B. in der Klasse selbst eine Methode Delete engebaut würde. Eine Smartpointer Klasse wäre hier in 2 Minuten geschrieben.

    Ich habe es sogar schon mal so weit gemacht, dass ich die COM Technologie in dem Sinne kopiert habe in dem ich selbst eine eigene Basis IUnknown-like Klasse genutzt habe inkl. Refrence Counting.
    Wen interessiert schon der ganze COM Overhead bzgl. Marshalling, Appartments und Klassenregistierung 🤡
    Man kann COM sofort clonen ohne selbst auch nur ein Stück COM nutzen zu müssen. Und schon kann man z.B. CComPtr verwenden 😉

    Für eine "Adapter-like" Techonolgie in meiner Software habe ich das sehr erfolgreich ausgenutzt. Allerdings hier alles komplett mit VS und unter Windows!



  • Man kann auch in wenigen Minuten Reference-Counting implementieren und dann einfach boost::intrusive_ptr verwenden 😉
    Das geht dann auch wenn die Funktionen nicht gerade AddRef und Release heissen.



  • Martin Richter schrieb:

    Selbst dies kann man mit einem eigenen Design sofort ösen, wenn z.B. in der Klasse selbst eine Methode Delete engebaut würde. Eine Smartpointer Klasse wäre hier in 2 Minuten geschrieben.

    Das ist mir schon klar. Aber die bestehenden lassen sich nicht nur nicht verwenden, sondern das muß gar unterbunden werden.

    Martin Richter schrieb:

    Ich habe es sogar schon mal so weit gemacht, dass ich die COM Technologie in dem Sinne kopiert habe in dem ich selbst eine eigene Basis IUnknown-like Klasse genutzt habe inkl. Refrence Counting.
    Wen interessiert schon der ganze COM Overhead bzgl. Marshalling, Appartments und Klassenregistierung 🤡
    Man kann COM sofort clonen ohne selbst auch nur ein Stück COM nutzen zu müssen. Und schon kann man z.B. CComPtr verwenden 😉

    So ein Ansatz hat zudem den Vorteil, daß er sich auch unter Nicht-Windows-Systemen implementieren ließe.

    Einen wichtigen Punkt habe ich übrigens noch vergessen: Exceptions. Wenn du verschiedene Compiler benutzt, mußt du zumindest ein rudimentäres Exception-Marshaling implementieren. Wenn eine Exception aus dem Code des einen Compilers in den des anderen gerät, dürften zwar, da vermutlich alle Windows-Compiler C++-Exceptions mittels SEH implementieren, die Destruktoren korrekt aufgerufen werden, aber die Exception wird nie gefangen werden können.


Log in to reply