Probleme mit delete bei Klassen aus DLL



  • Hallo,

    ich arbeite mit dem BCB5 und habe eine DLL erzeugt, aus der ich Klassen exporiteren möchte. Das klappt auch ohne Probleme und ich kann diese Klassen auch benutzen. Erzeugt habe ich die Instanzen der Klassen in der exe mit new in der Funktion FormCreate(). Nun muss man ja zum Programmende aufräumen und das genau will ich mit dem delete-Operator tun. Dann bekomme ich folgende Fehlermeldung: "Zugriffsverletzung bei Adresse 00328564 in Modul 'GLRender.dll'. Schreiben von Adresse 0000001C." Die DLL und die exe wurden beide mit dem BCB erstellt. Die gleiche DLL(identischer Quelltext) und auch eine ähnliche exe habe ich mit dem MS VC6 erstellt. Dort läuft es ohne deratige Probleme.
    Was mache ich falsch 😕
    Vielen Dank schon mal im Voraus.

    Gruß RastowMax



  • Liest sich für mich als würdest du da auf unsauberen Zeigern rumturnen...



  • Hallo junix,

    ich habe den Zeiger im private-Bereich der Form deklariert und in FormCreate() initialisiert. In FormDestroy() frage ich mit if ab, ob der Zeiger noch gültig ist, um ihn dann mit delete zu löschen. Dann bekomme ich aber diese böse Fehlermeldung. Mit dem Debugger bleibe ich im Destruktor der Klasse oder schon in einem Destruktor einer Basisklasse stecken mit genau der selben Fehlermeldung.

    Gruß RastowMax



  • Hallo

    nimm statt OnCreate den Destruktor, und statt OnDestroay den Destruktor.

    ob der Zeiger noch gültig ist

    Wie denn das?

    bis bald
    akari



  • Hallo akari,

    habe gerade versucht, die Klasse (den gültigen Zeiger darauf 😉 ) im Destruktor zu löschen, leider ohne Erfolg.
    Das mit dem gültigen Zeiger war wohl ein wenig falsch formuliert. Gemeint ist damit, dass wenn bei der Erzeugung der Klasse etwas schief geht, der Zeiger mit NULL initialisiert wird. Ist dieser also NULL, dann kann und brauch ich ihn nicht zu löschen.

    Gruß RastowMax



  • Hallo

    leider hilft uns das alles ohne relevanten Code nichts, um dir eine kokrete Lösung zu geben.
    Also untersuch mit dem Debugger den Verlauf deines Programmes. Insbesondere den Destruktor und desen Auswirkungen der zu löschenden Klassen, sowie die Pinter der Instanzen die damit im Spiel sind.

    bis bald
    akari



  • Hallo akari,

    am liebsten würde ich den kompletten Code in eine zip-Datei packen und hier einstellen oder dir schicken. Naja, ich versuch's mal so:

    Zum Ex- bzw. Importieren der Klassen und Funktionen aus der DLL benutze ich folgende Makros:

    #ifdef RD_DLL_EXPORTS
      #define RD_DLL_CLASS       __declspec(dllexport)
      #define RD_DLL_API         extern "C" __declspec(dllexport)
    #else
      #define RD_DLL_CLASS       __declspec(dllimport)
      #define RD_DLL_API         extern "C" __declspec(dllimport)
    #endif
    

    Dabei ist natürlich RD_DLL_EXPORTS nur im DLL-Projekt definiert.
    Bei der Klassendefinition sieht das dann wie folgt aus:

    class RD_DLL_CLASS GLBaseContext{
      private:
        ...
      protected:
        ...
      public:
        // Kontruktor / Destruktor
        GLBaseContext(HWND, HFONT, int, int); // Konstruktor
        virtual ~GLBaseContext(); // Destruktor
    
        ...
    };
    

    Der Destruktor dieser Klasse sieht wie folgt aus:

    //Destruktor
    GLBaseContext::~GLBaseContext(){
      if(Font2DDefault) delete Font2DDefault; // Font loeschen
      if(PrintFont2D) delete PrintFont2D; // DruckFont vorhanden? => loeschen
      MakeCurrent(false);        // Rendering-Kontext freigeben...
      wglDeleteContext(RC_Handle); // und loeschen
      if(Coords) delete[] Coords; // wenn vohanden, dann freigeben
      if(MsgString) delete[] MsgString; // wenn vohanden, dann freigeben
      ::ReleaseDC(Wnd_Handle, DC_Handle); // Handle fuer Geraetekontext freigeben 
    }
    //---------------------------------------------------------------------------
    

    Im exe-Projekt wird innerhalb der Formulardtei die (abgeleitete) Klasse im private-Bereich deklariert:

    private:	             // Anwender-Deklarationen
        ...
        GL3DCNCContext *GLContext; // Renderingkontext
        ....
    

    Die Destruktoren dieser Klasse und auch der noch zusätzlich vorhandenen "Zwischenklasse" sind aber leer.
    Zur Instantiierung gehe ich in FormCreate() wie folgt vor:

    void __fastcall TCncChildWindow::FormCreate(TObject *Sender){
      ... 
      // Rendering-Kontext erzeugen...
      GLContext = new GL3DCNCContext(PanelOGL->Handle, PanelOGL->Font->Handle,
                                     PanelOGL->ClientWidth, PanelOGL->ClientHeight);
      if(GLContext)              // hat Erzeugung geklappt...
        GLContext->CreateContext(); // Kontext mit Fenster verbinden
      else{
        GLContext = NULL;
      }
      ...
    }
    

    Jetzt kommt der problematische Teil (oder der Teil, der zum Problem führt).
    Das Auflösen der Instanz in FormDestroy():

    void __fastcall TCncChildWindow::FormDestroy(TObject *Sender){
      ...
      if(GLContext)
        delete GLContext;
      ...
    }
    

    Ich hoffe, dass du damit etwas anfangen kannst.

    Gruß RastowMax



  • Hallo,

    Setze mal einen Breakpoint in die Destruktoren und schau wo genau die Exception ausgelöst wird. Könntest du uns auch noch den Konstruktor zu der Klasse zeigen?
    Und bitte lösch die ganzen if-Abfragen vor den deletes. Die sind unnötig und blähen den Code nur auf. Ein delete auf einen Nullzeiger ist zulässig und verursacht somit keinen Fehler. Das wird intern abgefangen.



  • Hallo,

    hier ist der Konstruktor der Basisklasse:

    /////////////////////////////////////////////////////////////////////////////
    // GLBaseContext (abstrakte Basisklasse fuer Rendering Contexte)
    /////////////////////////////////////////////////////////////////////////////
    // Konstruktor
    GLBaseContext::GLBaseContext(HWND hwnd, HFONT hfont, int w, int h){
      Wnd_Handle = hwnd;         // mit Fester verbinden
      DC_Handle = ::GetDC(hwnd); // Geraetekontext holen
      FontHandle = hfont;        // FontHandle speichern
      Width = w; Height = h;     // Groesse speichern
      Coords = NULL;             // keine Koordinaten
      MsgString = NULL;          // keine Meldung
      ShowOGLMessage = NULL;     // kein Fktszeiger
      DrawOGLObjects = NULL;     // kein Fktszeiger
      InitOGL = NULL;            // kein Fktszeiger
      OGLSelect = NULL;          // kein Fktszeiger
      Font2DFirstGylph = 32;     // erstes Zeichen ist Leerzeichen
      Font2DNumGylph = 96;	  // ab hier 96 Zeichen weiter
      Font2DDefault = NULL;	  // kein Font
      LeftButton = MiddleButton = RightButton = false; // keine Maustaste gedrueckt
      MouseAction = MA_NONE;     // keine Aktion der Maus
      DepthTest = true;          // Tiefentest in Darstellung verwenden
      AntiAliazing = false;      // kein Antialiasing
      SetBkColor(0x00000000);    // Hintergrundfarbe setzen
      // Pixelformatdescriptor initialisieren
      ColorBits = 24;            // Farbtiefe
      // einzelne Farbbits setzen
      RedBits = 0; RedShift = 0; GreenBits = 0; GreenShift = 0; BlueBits = 0; BlueShift = 0;
      AlphaBits = 0; AlphaShift = 0; // Alpha-Buffer
      AccumBits = 0; AccumRedBits = 0; AccumGreenBits = 0; // Accumulation-Buffer
      AccumBlueBits = 0; AccumAlphaBits = 0;
      DepthBits = 32;            // Z-Buffer
      StencilBits = 8;           // Stencil-Buffer
      AuxBuffers = 0;            // Auxiliary Buffer
      LayerMask = 0;             // Layers
      VisibleMask = 0;           // Visible-Mask
      DamageMask = 0;            // Damage-Mask
      DoubleBuffer = true;       // Doppel-Buffering aktivieren
    
      // Datenelemente fuer Drucken initialisieren
      Printing = false;          // es wird nicht gedruckt
      UseColor = true;           // Farbe beim Drucken verwenden
      PrintColorBits = 24;       // Farbtiefe fuer Ausdruck
      MaxPrintMemSize = 8;       // acht MB Speicher fuer Ausdruck
      PrintFont2D = NULL;        // noch kein DruckFont vorhanden
      StartPrintOGL = NULL;      // kein Fktszeiger
      EndPrintOGL = NULL;        // kein Fktszeiger
    }
    //---------------------------------------------------------------------------
    

    Ich habe das ganze auch schon mit einer komplett leeren Klasse (nur leerer Konstruktor und leerer Destruktor und keine Datenelemente) probiert und bin mit der selben Fehlermeldung abgespeist worden.
    Das Debuggen hat ergeben, dass der Fehler auftritt, wenn der Destruktor der konkreten (also der letzten instantiierten Klasse) verlassen werden soll. Durch das virtual vor dem Destruktor werden ja nacheinander alle Basisklassen-Destruktoren aufgerufen.
    Auch habe ich schon einige andere abgedrehte Ideen gehabt, um das Problem in den Griff zu bekommen. Ich habe zum Beispiel versucht, die Klasse von TObject abzuleiten. Dann bekam ich aber seitens des Compilers eine Fehlermeldung: "E2113 Virtuelle Funktion 'GLBaseContext::~GLBaseContext()' verursacht Konflikte mit der Basisklasse 'TObject'".
    Auch habe ich die DLL mit dem VC6 neu erzeugt und versucht mit implib.exe und impdef.exe aus dem BCB-Bin-Verzeichnis eine Importbibliothek und eine Moduldefinitionsdatei zu erzeugen. Beim Versuch das Ganze in das exe-Projekt einzubinden und zu erzeugen erschlugen mich dann die Fehlermeldungen seitens des Compilers, so dass ich den ganzen Mist sofort wieder gelöscht habe.

    Gruß RastowMax



  • Es könnte auch daran liegen, dass du deine Klasse aus der dll exportierst. So etwas ist eigentlich immer unportabel. Mit dem VC geht das schon gar nicht.
    Versuch es mal so, dass du nicht die Klasse, sondern nur Funktionen exportierst, die Pointer auf diese Klasse liefern bzw. löschen. Kapsel die gesamte Erzeugung und Vernichtung in die dll.



  • Hallo,

    ich habe vor, mit dieser DLL für unterschiedliche Zwecke unterschiedliche Renderingkontexte zur Verfügung zu stellen (2D-Kontexte für technische Zeichnungen, 3D-Kontexte zur Darstellung von STL-Dateien, Simulation von CNC-Programmen usw.). Deshalb auch diese Heirarchie innnerhalb der DLL. In der Vorgängerversion gab es nur einen solchen Renderingkontext. Auch diesen habe ich aus einer DLL exportiert. Dazu habe ich eine abstrakte Basisklasse in eine Headerdatei geschrieben und diese sowohl ins exe-Projekt als auch ins DLL-Projekt eingebunden, damit das exe-Projekt an die VMT der Klasse kommt. Bei diesem einen Renderingkontext machte das ja auch noch Sinn. Allerdings, wenn ich jetzt eine abstrakte Basisklasse für die gesamte Hierarchie entwerfe, muss auch jede abgeleitete Klasse, die ich dann irgendwann einmal instantiieren will, alle diese rein virtuellen Funktionen überschreiben, obwohl beispielsweise eine 3D-Rotation mit einem Zeichenblatt wenig Sinn macht. Das ist aber erst der Anfang. Ich habe noch einen kleinen Geometriekern als statische LIB, der ebenfalls in eine DLL soll. Dieser Geschichte ist dann wohl mit der oben beschriebenen Methode kaum noch beizukommen.

    Was mir am Code des Konstruktors aufgefallen ist, die Initialisierungswerte für den Pixelformatdescriptor könnten alle raus und als globale Variablen deklariert werden, da die ja für alle Instanzen gleich sind.

    Gruß RastowMax



  • Mach die Variablen lieber nicht global aber dafür statisch. Dann hast du auch nur eine Variable für alle Instanzen, sie bleiben aber in der Klasse wo sie auch hingehören.
    Du kannst ja eine allgemeine abstrakte Basisklasse schaffen und davon abstrakte Basisklassen für 2D und für 3D ableiten. Davon könnte man dann die realen Klassen ableiten.



  • Hallo,

    statische Variablen ist sehr gut, weil die sauberere Lösung. Werd' ich machen. 👍

    Aber noch mal zu dem Exportproblem. Die VCL und auch andere Klassenbibliotheken machen uns doch den Export permanent vor. Es muss folglich irgendwie möglich sein, Klassen "sauber" aus einer DLL zu exportieren. Gibt es da nicht vielleicht ein "verstecktes Borland-internes Zaubermakro", welches die Lösung des Problems herbeiführen könnte. In MS VC 6 klappt's doch auch. Nur will ich die Benutzerschnittstelle des exe-Projektes (also das Erscheinungsbild der eigentlichen Anwendung) nicht mühsam mit Quelltext zusammenschreiben sondern eben die Vorzüge eines echten RAD-Tools, wie der BCB eines ist, nutzen. Der Ärger geht schon los bei der Erstellung eines einfachen geteilten Fensters mit im Client-Bereich liegender Eingabezeile. Das klappte mit dem Borland Turbo C++ Version 4.5 aus dem Jahre 1995!!! schon wesentlich besser, weil dieser einige Hilfsklassen wie hierfür z.B. TLayoutMetrics in seiner Klassenbibliothek OWL 2.5 enthielt. Tagelanges Suchen in der MFC-Hilfe (MSDN) nach ähnliche "Helpers" hat leider keine Ergebnisse gebracht.

    Also gut, genug gejammert, an's Werk 😃

    Gruß RastowMax



  • I've fixed it!

    Ja, ja, so kann's geh'n. Wer lesen kann, ist klar im Vorteil!

    Wie ich bereits im ersten Beitrag schrieb, ist der Quelltext der DLL sowohl für den VC 6 als auch für den BCB 5 identisch (mit einigen Präprozessorderektiven funktioniert das). Dadurch war auch der Einsprungpunkt für die DLL identisch. Bisher war die DLL im VC-Stil erstellt worden und hatte DllMain() als Einsprungpunkt. Das habe ich jetzt wie folgt geändert:

    /////////////////////////////////////////////////////////////////////////////
    // Einsprungpunkt fuer die DLL-Anwendung
    #ifdef MS_VC_6_0
    BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, LPVOID lpReserved){
    #else
    int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved){
    #endif
      switch (reason){           // wird beim Laden und Entladen durchlaufen
        case DLL_PROCESS_ATTACH: break;
        case DLL_THREAD_ATTACH: break;
        case DLL_THREAD_DETACH: break;
        case DLL_PROCESS_DETACH: break;
      }
      return true;
    }
    

    Nach erneutem Kompileren und Ausführen der Anwendung bekam ich dann folgende Fehlermeldung: "Zugriffsverletzung bei Adresse 41004496 in Modul 'BORLNDMM.DLL'. Schreiben von Adresse 70614307." Danach las ich den "wichtigen Hinweis", der bei der Erstellung einer DLL mit dem DLL-Experten erzeugt wird. Dort stand, dass man, wenn man denn die statische Version der RTL verwendet, MEMMGR.LIB explizit sowohl in das DLL-Projekt als auch in das exe-Projekt oder aber eben die RTL.DLL mit einbinden muss.
    So setzte ich also das Häkchen unter "Projektoptionen/Linker" im Kontrollkästchen "Dynamische RTL verwenden". Und siehe da, es funktioniert.
    Wie doch ein kleines Häkchen die Welt verändern kann 😃

    Vielen Dank an alle, die sich damit beschäftigt haben.

    Gruß RastowMax


Log in to reply