Kleines Problem mit Dynamic Link Libraries (DLL)



  • Hallo zusammen,

    ich bin gerade dabei eine kleine .dll zu schreiben, die eine TStringList zurückgeben soll.

    Das funktioniert auch soweit, nur habe ich den Verdacht, dass die .dll nicht ordnungsgemäß freigegeben wird:

    //---------------------------------------------------------------------------
    
    #include <vcl.h>
    #include <windows.h>
    #pragma hdrstop
    
    TStringList* FStringList;
    
    //---------------------------------------------------------------------------
    //   Important note about DLL memory management when your DLL uses the
    //   static version of the RunTime Library:
    //
    //   If your DLL exports any functions that pass String objects (or structs/
    //   classes containing nested Strings) as parameter or function results,
    //   you will need to add the library MEMMGR.LIB to both the DLL project and
    //   any other projects that use the DLL.  You will also need to use MEMMGR.LIB
    //   if any other projects which use the DLL will be performing new or delete
    //   operations on any non-TObject-derived classes which are exported from the
    //   DLL. Adding MEMMGR.LIB to your project will change the DLL and its calling
    //   EXE's to use the BORLNDMM.DLL as their memory manager.  In these cases,
    //   the file BORLNDMM.DLL should be deployed along with your DLL.
    //
    //   To avoid using BORLNDMM.DLL, pass string information using "char *" or
    //   ShortString parameters.
    //
    //   If your DLL uses the dynamic version of the RTL, you do not need to
    //   explicitly add MEMMGR.LIB as this will be done implicitly for you
    //---------------------------------------------------------------------------
    
    #pragma argsused
    int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
    {
     switch (reason){
    
    	 case DLL_PROCESS_ATTACH: FStringList = new TStringList();
    	 case DLL_PROCESS_DETACH: delete FStringList;
    	 default                : break;}
    
     return 1;
    }
    //---------------------------------------------------------------------------
    
    extern "C" __declspec(dllexport) TStringList* ausgabe(int x, int y, int z)
    {
     FStringList->Add(IntToStr(x));
     FStringList->Add(IntToStr(y));
     FStringList->Add(IntToStr(z));
     FStringList->Add("Auf Wiedersehn");
     return FStringList;
    }
    

    Der Aufruf mit "Hauptprogramm" erfolgt folgendermaßen:

    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
     typedef TStringList* __stdcall TAusgabe(int x, int y, int z);
    
     HINSTANCE h        = LoadLibrary("H:\\DLL\\test.dll");
     TAusgabe* ausgabe  = (TAusgabe*)GetProcAddress(h,"_ausgabe");
     TStringList* Ffile = ausgabe(10,10,10);
     Ffile->SaveToFile("H:\\test.txt");
     FreeLibrary(h);
    }
    

    Gibt es hier irgendeinen Fehler? Vorallem in der .dll?
    Bekomme keine Fehlermeldung. Die .dll wird nur manchmal nicht freigegeben. 😞

    Grüße
    Stefan



  • Sorry, ich sollte noch erwähnen, dass die Textdatei mit den richtigen Daten abgespeichert wird!



  • Hallo

    Warum der globale TStringList-Pointer in der DLL?
    Und nirgendwo in deinem Programm wird eine Instanz für den Pointer erstellt, vom Löschen ganz zu schweigen. Korrekt sähe der Code so aus :

    // DLL ohne globalen Pointer!
    extern "C" __declspec(dllexport) TStringList* ausgabe(int x, int y, int z)
    {
      TStringList* FStringList = new TStringList();
      FStringList->Add(IntToStr(x));
      FStringList->Add(IntToStr(y));
      FStringList->Add(IntToStr(z));
      FStringList->Add("Auf Wiedersehn");
      return FStringList;
    }
    extern "C" __declspec(dllexport) void loeschen(TStringList* FStringList)
    {
      delete FStringList;
    }
    
    // Anwendung
    
    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
      typedef TStringList* __stdcall TAusgabe(int x, int y, int z);
      typedef void __stdcall TLoeschen(TStringList* FStringList);
    
      HINSTANCE h        = LoadLibrary("H:\\DLL\\test.dll");
      TAusgabe* ausgabe  = (TAusgabe*)GetProcAddress(h,"_ausgabe");
      TLoeschen* loeschen  = (TLoeschen*)GetProcAddress(h,"_loeschen");
      TStringList* Ffile = ausgabe(10,10,10);
      Ffile->SaveToFile("H:\\test.txt");
      loeschen(Ffile);
      FreeLibrary(h);
    }
    

    Beachte das eine Instanz immer in dem Modul (Programm oder DLL) gelöscht werden sollte wo es erstellt wurde
    Um die eigentlich unnötige Lösch-Funktion zu vermeiden kannst du die StringList ja in der Anwendung erzeugen lassen und der Funktion ausgabe als Parameter übegreben. Wenn du dann als Parametertyp nicht TStringList sondern TStrings nimmst kannst du außerdem auch mehr übergeben, zum Beispiel TListBox::Items.

    Und wenn du das dynamisch Linken nicht wirklich brauchst empfehle ich dir statisch zu linken.

    bis bald
    akari



  • Hallo akari,

    danke für deine Antwort. Die Instanz wird natürlich erzeugt und der delete Befehl auch korrekt aufgerufen. Ich habe nur schlampig den Code präsentiert. 😞

    Wie ich den Pointer der TStringList erstelle und dann auch wieder korrekt lösche war glaube ich das eigentliche Problem. Das hab ich doch sehr umständlich gemacht.

    Werde mir deinen Vorschlag zu Herzen nehmen, und den Pointer einfach mitübergeben.

    Danke
    Stefan



  • So, seitdem ich heute wieder an meinem Projekt habe weiterarbeiten können, ist mir der nächste Fehler in die Quere gekommen.

    In der DLL steht ja deutlich beschrieben, was zu tun ist, falls man Strings übergeben oder zurückgeben möchte. Darunter fallen natürlich auch die TStringList's.

    Habe 'MEMMGR.LIB' in das DLL Projekt und in das Projekt, das die .dll lädt, eingefügt und neu erzeugt, sowie die 'BORLNDMM.DLL' in das Verzeichnis der Release Version meines Hauptprogramms reingelegt.

    Funktioniert alles einwandfrei, bis der übergebene TStringList Pointer im Hauptprogramm gelöscht werden soll (delete FStringList); Der delete Aufruf erfolgt nach Freigabe der DLL.

    Wenn ich die Dll dynamisch linke funktioniert es wunderbar, wie von Borland beschrieben. Nur die andere Varainte funktioniert leider nicht. 😞

    Was kann man denn da tun. Habe ich etwas falsch befolgt?

    MFG
    StefanN



  • Damit keine Missverständnisse aufkommen:

    Mit dynamisch linken meinte ich eingentlich:

    //   If your DLL uses the dynamic version of the RTL, you do not need to
    //   explicitly add MEMMGR.LIB as this will be done implicitly for you
    

    *Sorry*



  • Das delete kann eigentlich nicht nach der Freigabe der dll erfolgen.
    Wenn ich dich richtig verstanden habe, erzeugst du die StringList in der dll. Dann musst du sie auch, wie von akari bereits beschrieben, in der dll mit der von akari beschriebenen Funktion gelöscht werden. Der Speicher gehört ja schließlich der dll.



  • Hallo Braunstein,

    danke für deine Antwort. Ich habe das ganze jetzt noch mal getestet und es funktioniert soweit.

    Ich erzeuge die TStringList nicht mehr innerhalb der .dll sondern habe den Vorschlag von akari übernommen und den Pointer der TStringList mitübergeben. Sie wird also im Hauptprogramm erzeugt und nach Freigabe der .dll wieder mit delete gelöscht.

    Da ich nicht unbedingt wahnsinnig wahnsinnig viel Ahnung habe vom Programmieren (da gibt es hier sicherlich massenweise mehr Leute mit mehr Sachverstand als ich ihn habe) kann ich euch auch nicht recht erklären, warum der Fehler aufgetreten ist, ich kann nur beschreiben wo und wie der Fehler aufgetreten ist:

    Also wie gesagt, das Laden der .dll und die Auswertung der Funktion innerhalb der .dll funktioniert einwandfrei.

    Im Hauptprogramm laufen zwei Thread's. Im Hauptthread, bevor der zweite gestartet wird, wird auch die .dll eingeladen und handle zugewiesen.
    Nachdem man den zweiten Thread gestartet hat, wird am Ende die Funktion innerhalb der .dll aufgerufen und ausgewertet. Ihr wird der Pointer der mit new erzeugten TStringList übergeben. Sie ist auch gleichzeitig der Rückgabewert.

    Diese TStringList wird benötigt, um eine Ausgabe in eine Textdatei zu erreichen. Dafür nutze ich eine Funktion innerhalb des zweiten Thread's ::speichern(). In dieser Funktion öffnet sich ein Speicherdialog, wo man an Ende mit TStringList::SaveToFile das ganze abspeichert. Dann ist die Funktion ::speichern() erledigt und noch einige Anweisungen werden innerhalb der ::Execute() Funktion des zweiten Thread's getätigt. Dort stand auch der delete Befehl für die TStringList und FreeLibrary(handle).

    Das wurde auch scheinbar gemacht, aber wenn ich das Hauptprogramm schließen wollte, kam eine EAcessViolation ...

    Wenn ich den delete Befehl für die TStringList und FreeLibrary(handle) innerhalb der Funktion ::speichern() angebe, funktioniert das ganze.

    Nachtrag: Die Funktion ::speichern() wird mit Synchronize(&speichern) aufgerufen.

    Ich hoffe ich hab nicht zu viel Käse geschrieben. Das Problem ist ja für mich jedenfalls augenscheinlich gelöst, auch wenn ich das ganze nicht verstehe. 😞

    Vielleicht hat ja jemand eine plausible Erklärung für mich.

    Danke
    StefanN


Anmelden zum Antworten