c++ Klasse in dll nutzen - wie?



  • xenayoo schrieb:

    Allerdings ist die Existenz des Objektes an den Block dieser Funktion gebunden.

    Nee, noch viel schlimmer: mit deinem new erzeugst du das Objekt auf dem Heap, verlierst danach sofort den Pointer auf das Objekt aus den Augen und wenn es nicht gestorben ist, dann hängt es noch heute unerreichbar irgendwo rum. C++ ist nicht C#, wo der Garbage Collector hinter dir aufräumt, new hat daher in C++ eine ganz andere Semantik.

    xenayoo schrieb:

    Ich habe die dll intern als Klasse aufgebaut. Diese Klasse hat natürlich einen Konstruktor, einen Destruktor und 2 Memberfunktionen.

    [...] sondern es waren statt 4 Funktionen 7 plus die Klasse.

    Weil die compilergenerierten Copy-Ctor, op= und noch irgendwas (move-ctor?) bei DLL-Export mit geliefert werden.

    xenayoo schrieb:

    Hat jemand eine Idee, wie ich in der dll eine bleibende Instantz erzeugen kann, die dann von den Funktionen genutzt werden kann?

    Indem du ein Handle auf das erzeugte Objekt zurück gibst. Möglich wäre ein void-Pointer, evtl. funktioniert aucch sowas:

    //dll.h
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
      struct DLLClassProxy;
    
      DLLClassProxy* createDLLClass();
      void releaseDLLClass(DLLClassProxy*);
      int getSomeValueFromDLLClass(DLLClassProxy const*);
    
    #ifdef __cplusplus
    } //end extern "C"
    #endif
    
    //dll.cpp
    #include "dll.h"
    #include "myclass.h"
    extern "C" {
    
    struct DLLClassProxy
    {
      MyClass mc;
      DLLClassProxy(Foo const& f) : mc(f) {}
    };
    
    DLLClassProxy* createDLLClass() {
      return new DLLClassProxy(Foo(42));
    }
    void releaseDLLClass(DLLClassProxy* dcp)
    {
      delete dcp;
    }
    int getSomeValueFromDLLClass(DLLClassProxy const* dcp)
    {
      return dcp->mc.getSomeValue();
    }
    
    } //end extern "C"
    
    //main.c
    #include <stdio.h>
    
    int main()
    {
      DLLClassProxy* proxy = createDLLClass();
      int i = getSomeValueFromDLLClass(proxy);
      releaseDLLClass(proxy);
    
      printf("i: %d", i);
    }
    


  • Hallo pumuckl,
    vielen Dank für deine Hilfe. So ganz verstehe ich das Listing nicht. Zwischenzeitlich habe ich die Klasse aufgelöst und das Ganze dann direkt in din Funktionen verteilt. Da ich aber deque nutze, was ja auch eine eine Klasse aus der STL ist, habe ich mit der Fehlermeldung

    expected constructor, destructor, or type conversion before "double"
    

    vermutlich das selbe Problem.

    dllmain.cpp

    /* Replace "dll.h" with the name of your header */
    #include "dll.h"
    #include <windows.h>
    #include <deque>
    
    using namespace std;
    
    int maxDeq = 100;
    deque<double> Puffer(maxDeq);
    //double range(0);
    
    extern "C"
    {
     DLLIMPORT void AvgRa32Init( int a )
     {
      maxDeq = a;
      Puffer.resize(maxDeq);
      }
    
      DllImport double AvgRa32Range(double high, double low)
      {
       double range;
       range = high > low ? high - low : low - high;       
    
       if(Puffer.size() < maxDeq)
       { Puffer.push_front(range); }
       else
       { Puffer.pop_back();
       Puffer.push_front(range); }
    
       return range;
      }
    
      DLLIMPORT double AvgRa32Avg()
      {
       if(Puffer.size() == maxDeq)
       {
        double summe = 0;
        for(int i = 0; i < maxDeq; i++)
        { summe += Puffer[i]; }
        return (summe / maxDeq);
       }
       else return -0.99;
      }
    }
    
    BOOL APIENTRY DllMain (HINSTANCE hInst     /* Library instance handle. */ ,
                           DWORD reason        /* Reason this function is being called. */ ,
                           LPVOID reserved     /* Not used. */ )
    {
        switch (reason)
        {
          case DLL_PROCESS_ATTACH:
            break;
    
          case DLL_PROCESS_DETACH:
            break;
    
          case DLL_THREAD_ATTACH:
            break;
    
          case DLL_THREAD_DETACH:
            break;
        }
    
        /* Returns TRUE on success, FALSE on failure */
        return TRUE;
    }
    

    dll.h

    #ifndef _DLL_H_
    #define _DLL_H_
    
    # include <deque>
    
    #if BUILDING_DLL
    # define DLLIMPORT __declspec (dllexport)
    #else /* Not BUILDING_DLL */
    # define DLLIMPORT __declspec (dllimport)
    #endif /* Not BUILDING_DLL */
    
    using namespace std;
    
    extern "C" 
    {
          DLLIMPORT void AvgRa32Init(int a);
          DLLIMPORT double AvgRa32Range(double high, double low);
          DLLIMPORT double AvgRa32Avg(); 
    }
    
    #endif /* _DLL_H_ */
    

    Der Fehler ist in Line 21 von dllmain.cpp (DllImport double AvgRa32Range(...) ). Auch hier kann ich mich drehen und wenden, wie ich will, ich sehe den Fehler nicht....

    Jens



  • Zeile 21? -> case sensitive?

    MfG f.-th.



  • Zeile 9 und 10 in der .cpp sind übel. Vermeide globale Variablen der Art, sowas macht nur unglücklich.



  • Hallo f.-th.,

    sorry, ich stehe auf dem Schlauch. Leider verstehe ich nicht,, was du meinst.

    Hallo pumuckl,

    sowohl maxDeq als auch Puffer wird global verwendet. Wie soll ich das anders lösen, wenn ich diese Informationen nicht ständig als Parameter übergeben möchte?

    MfG Jens



  • xenayoo schrieb:

    Hallo f.-th.,

    sorry, ich stehe auf dem Schlauch. Leider verstehe ich nicht,, was du meinst.

    GIYFF. Case-Sensitive → Groß/Klein-Schreibung wird unterschieden.



  • Hacker schrieb:

    xenayoo schrieb:

    Hallo f.-th.,

    sorry, ich stehe auf dem Schlauch. Leider verstehe ich nicht,, was du meinst.

    GIYFF. Case-Sensitive → Groß/Klein-Schreibung wird unterschieden.

    Arrrrrrghhhh! <vor die Birne Klatsch> Wie kann mann bloß so blind sein wie ich?

    Danke Hacker.... 👍

    Jens

    BTW Ich werde auch die Variante von Pumuckl durchgehen und ausprobieren....



  • Hallo pumuckl,

    ich befasse mich jetzt mal mit Deinem Vorschlag. Mal sehen, ob ich das verstehe:

    dll.h:

    #ifdef __cplusplus 
    extern "C" { 
    #endif 
    
      struct DLLClassProxy; 
    
      DLLClassProxy* createDLLClass(); 
      void releaseDLLClass(DLLClassProxy*); 
      int getSomeValueFromDLLClass(DLLClassProxy const*); 
    
    #ifdef __cplusplus 
    } //end extern "C" 
    #endif
    

    Hier stehen die Funktionsprototypen.

    nun die dll.cpp:

    #include "dll.h" 
    #include "myclass.h" 
    extern "C" { 
    
    struct DLLClassProxy 
    { 
      MyClass mc;   // Instanz von MyClass erzeugen
      DLLClassProxy(Foo const& f) : mc(f) {} // ????
    }; 
    
    DLLClassProxy* createDLLClass() { 
      return new DLLClassProxy(Foo(42)); 
    } 
    void releaseDLLClass(DLLClassProxy* dcp) 
    { 
      delete dcp; 
    } 
    int getSomeValueFromDLLClass(DLLClassProxy const* dcp) 
    { 
      return dcp->mc.getSomeValue(); 
    } 
    
    } //end extern "C"
    

    Hier passiert folgendes: Zuerst wird mit struct ein Datentyp namens DllClassProxy definiert. Wird DllClassProxy verwendet, wird MyClass instanziert und der Konstruktor aufgerufen. Zwar verstehe ich DLLClassProxy(Foo const& f) : mc(f) {} nicht vollständig, aber ich vermute, dass die Struktur ebenso wie die Funktionen exportiert wird, damit der Typ extern zur Verfügung steht. (Foo ist ein Platzhalter für den Datentyp des Parameters namens f in Form einer konstanten Referenz) nach dem Doppelpunkt folgt der Konstruktoraufruf mit dem Parameter.

    Dann kommt die Funktion createDLLClass, die Instanz erzeugt und als Rückgabewert einen Zeiger auf die Klasse liefert, mit dem Beispielparameter 42. Diese würde vom externen Programm über als einmalige Initialisierung aufgerufen.

    Passend dazu die Funktion releaseDLLClass, die die Instanz wieder löscht.

    und schließlich die Funktion getSomeValueFromDLLClass(...), mit der Werte aus der Dll geholt werden. Analog dazu kann man Parameter übergeben und damit auch eine putAValueIntoDLLClass(Foo param) entwerfen.

    in der main.c ist dannn der Musteraufruf der dllKlasse:

    #include <stdio.h> 
    
    int main() 
    { 
      DLLClassProxy* proxy = createDLLClass(); 
      int i = getSomeValueFromDLLClass(proxy); 
      releaseDLLClass(proxy); 
    
      printf("i: %d", i); 
    }
    

    Durch Aufruf der Funktion createDllClass( param ) wird die Instanz erzeugt und Ein Zeiger auf die Klasse zurückgeliefert. Diesen Zeiger muss man beim dll-Aufruf mitliefern, damit in der dll die Memberfunktionen Aufgerufen werden können.

    Soweit. Nun meine Fragen:
    - Was genau bedeutet die Zeile DLLClassProxy(Foo const& f) : mc(f) {} ?
    - Normalerweise wird bei jedem erneuten Aufruf der dll ein neuer Thread gestarted. Hier kann ich also entweder mehrere Instanzen der Klasse von einem Thread aus erzeugen oder, wenn die dll statisch ist von verschiedenen Programmen lediglich neue Instanzen der Klasse anlegen. (Nur zu meinem Verständnis). Nette Spielerei.

    Jens



  • xenayoo schrieb:

    Hier passiert folgendes: Zuerst wird mit struct ein Datentyp namens DllClassProxy definiert. Wird DllClassProxy verwendet, wird MyClass instanziert und der Konstruktor aufgerufen.

    Richtig.

    ich vermute, dass die Struktur ebenso wie die Funktionen exportiert wird, damit der Typ extern zur Verfügung steht.

    Jein. Alles was von der DLL exportiert wird, ist die Deklaration der Struktur. Wie die Struktur genau aussieht, weiß man außerhalb der DLL nicht, braucht man aber auch nicht zu wissen, wenn man nur mit Pointern hantiert. Aus dem Grund darf man in der Struktur auch Methoden, Konstruktoren usw. definieren.

    Dann kommt die Funktion createDLLClass, die Instanz erzeugt und als Rückgabewert einen Zeiger auf die Klasse liefert, mit dem Beispielparameter 42. Diese würde vom externen Programm über als einmalige Initialisierung aufgerufen.

    Sie liefert einen Zeiger auf die Proxy-Struktur, ja. Die create-Funktion darf auch mehrfach aufgerufen werden, dann werden eben mehrere Proxy-Objekte (und damit Objekte der Klasse) erzeugt.

    Passend dazu die Funktion releaseDLLClass, die die Instanz wieder löscht.
    und schließlich die Funktion getSomeValueFromDLLClass(...), mit der Werte aus der Dll geholt werden. Analog dazu kann man Parameter übergeben und damit auch eine putAValueIntoDLLClass(Foo param) entwerfen.

    Richtig.

    in der main.c ist dannn der Musteraufruf der dllKlasse:

    #include <stdio.h> 
    
    int main() 
    { 
      DLLClassProxy* proxy = createDLLClass(); 
      int i = getSomeValueFromDLLClass(proxy); 
      releaseDLLClass(proxy); 
    
      printf("i: %d", i); 
    }
    

    Durch Aufruf der Funktion createDllClass( param ) wird die Instanz erzeugt und Ein Zeiger auf die Klasse zurückgeliefert. Diesen Zeiger muss man beim dll-Aufruf mitliefern, damit in der dll die Memberfunktionen Aufgerufen werden können.

    Genau. Das ist im Grunde OOP in C, mit einem opaquen Typ. Klassisches Beispiel dafür sind die Dateifunktionen in <stdio.h>:

    FILE        => DllClassProxy            = opaquer Typ
    fopen       => createDLLClass           = Konstruktor
    fclose      => releaseDLLClass          = Destruktor
    fwrite etc. => getSomeValueFromDLLClass = Methode
    

    Diese Art und Weise, in C Objektorientiert zu arbeiten, findet man immer häufiger, vor Allem wenn C nur deshalb genommen wurde, weil es auf noch mehr Plattformen als C++ zur Verfügung steht.

    (Bevor jetzt jemand schreit, dass man in C nicht objektorientiert arbeiten kann, weil Vererbung oder <insert some feature> fehlt: Es gibt verschiedenste Definitionen von Objektorientierung. Die Minimalanforderungen werden durch diese Arbeitsweise mit C erfüllt. Hier werden die Member der Klassen sogar besser gekapselt als in C++-Klassen, die nicht gepimplt werden.)

    - Was genau bedeutet die Zeile DLLClassProxy(Foo const& f) : mc(f) {} ?

    Das ist eine ganz normale Konstruktor-Definition (C++) Der Parameter f wird einfach an den Konstruktor der DLL-Klasse weitergeleitet. Was ist dir daran nicht bekannt? Die Initialisierungsliste? (d.h. der Doppelpunkt und die Initialisierung danach?)

    - Normalerweise wird bei jedem erneuten Aufruf der dll ein neuer Thread gestarted. Hier kann ich also entweder mehrere Instanzen der Klasse von einem Thread aus erzeugen oder, wenn die dll statisch ist von verschiedenen Programmen lediglich neue Instanzen der Klasse anlegen. (Nur zu meinem Verständnis). Nette Spielerei.

    Du kannst so viele Instanzen erzeugen wie du möchtest. Ob in einem oder mehreren Threads ist egal - wenn die eigentliche Klasse threadsafe ist.

    PS: Wenn du die DLL-Klasse selber verändern kannst, könntest du auch den Proxy umgehen und die DLL-Klasse selbst als extern "C" Struktur definieren.



  • Hallo Pumuckl,

    PS: Wenn du die DLL-Klasse selber verändern kannst, könntest du auch den Proxy umgehen und die DLL-Klasse selbst als extern "C" Struktur definieren.
    

    Jetzt mach ich aber große Augen... 😮 Ganz am Anfang hatte ich diese Idee auch schon - allerdings nahm ich nach intensivem Bemühen von Tante Google an, dass dies nur mit Funktionen im 'C' Stil funktioniert und nicht mit ganzen Klassen. Ohne das extern "C"{} sind die Namen unbrauchbar. Wenn ich die Klasse einfach mit extern "C" exportieren kann, dann war der ganze Aufwand unnötig....

    Danke für deine ausführlichen Erklärungen. 👍



  • Ich hab das Gefühl, dass du dir eigentlich in etwa folgendes vorstellst: Du schreibst in C++ eine Klasse, packst diese in eine dll und verwendest die C++ Klasse dann wie eine ganz normale .NET Klasse in C#.

    Wenn ja: Vergiss es. Das ist schlicht und einfach unmöglich. Selbst C++ Klassen in dlls zu exportieren und diese dann in C++ zu verwenden, ist bereits hochproblematisch.

    Die Lösung, die du suchst, nennt sich C++/CLI. Du solltest dir aber erstmal ernsthaft folgende Frage stellen: Muss es wirklich C++ sein und muss es wirklich eine Klasse sein? Wenn ja, dann überleg dir, ob nicht ein einfaches C-Style Interface für deine dll ausreicht. Das ist, was pumuckl dir hier eigentlich erklären wollte und sowas lässt sich meist sogar mit nur mittelschweren Schmerzen von C# aus ansprechen. C++/CLI erlaubt dir, .NET Wrapper für deinen native C++ Code zu schreiben. Damit würde ich aber nur anfangen, wenn es wirklich keinen anderen Weg gibt...

    EDIT: Eine weitere Alternative wäre, deinen C++ Code in eine COM Komponente zu stecken.



  • xenayoo schrieb:

    Jetzt mach ich aber große Augen...

    Shame on me - Klassen/struct-namen werden offenbar garnicht gemangelt, wenn sie nicht in irgendwelchen Namensräumen liegen. Man sieht, ich habs noch nie wirklich ausprobuiert, kenns nur in der Theorie - und auch das offenbar nciht richtg... Wenn du Lust hast, könntest du mal folgendes probieren: (.cpp mit C++-Compiler, .c mit C-Compiler):

    1) zwei Header:

    //myclass.hpp
    //die DLL-Klasse: eine ganz normale C++-Klasse, nur struct genannt, 
    //damit der C++-Compiler bei der C-Deklaration später nicht meckert
    struct MyClass 
    {
      MyClass(int i_) : i(i_) {}
      int geti() const {return i;}
      void seti(int i_) {i = i_;}
    private:
      int i;
    };
    
    /* myclass_c.h - ein C-Header */
    struct MyClass;
    
    struct MyClass* newMyClass(int);
    int getiMyClass(struct MyClass const*);
    void setiMyClass(struct MyClass*, int);
    void deleteMyClass(struct MyClass*);
    

    2) die DLL-Source, in C++

    //myclass_c.cpp
    #include "myclass.hpp"
    
    extern "C" {
    #include "myclass_c.h"
    
    MyClass* newMyClass(int i) { return new MyClass(i); }
    int getiMyClass(struct MyClass const* pmc) { return pmc->geti(); }
    void setiMyClass(struct MyClass* pmc, int i) { pmc->seti(i); }
    void deleteMyClass(struct MyClass* pmc) {delete pmc; }
    
    } //end extern "C"
    

    3) die C-Source

    /* main.c */
    #include "myclass_c.h"
    #include <stdio.h>
    #include <stdlib.h>
    typedef struct MyClass* MCP;
    int main(void)
    {
      MCP mcp = newMyClass(25);
      printf("i vorher: %d\n", getiMyClass(mcp)); //25
      setiMyClass(mcp, 42);
      printf("i nachher: %d\n", getiMyClass(mcp)); //42
      deleteMyClass(mcp);
      return 0;
    }
    

    bin gespannt...


Anmelden zum Antworten