Wann sollte ich try-catch-Blöcke einsetzen?



  • akari schrieb:

    Absicherungswürdig sind zunächst alle Instanzen die du mit new anlegst ... Denn sollte während der Bearbeitung eine Exception auftreten, wird die Funktion vor dem delete verlassen.

    Warum werde ich überhaupt von der VCL gezwungen die StringListe mit new anzulegen, warum kann ich die nicht einfach als Variable anlegen??? - das nervt mich!

    akari schrieb:

    ... wie du das absichern kannst hat ja witte mit dem std::auto_ptr schon gezeigt.

    Irgendwie komm' ich mit diesem auto_ptr-Ding nicht ganz klar... was muss ich dafür einbinden?

    //  TStringList *EADataList= new TStringList;                 // ALT: StringList-Komponente instanziieren
        std::auto_ptr<TStringList> EADataList(new TStringList);   // NEU
    //  delete EADataList;                                        // ALT: StringList-Instanz freigeben
    

    führt zu:

    [C++Fehler] EAShow.cpp(215): 'auto_ptr' is not a member of 'std'.

    Und wo bekomme ich mehr Hintergrundwissen zu std::auto_ptr? Alles was ich weiß ist: Ich instanziiere TStringList mittels Konstruktor von etwas, das ich als lokale Variable anlegen kann, damit meine StringList beim Verlassen des scope (egal wodurch der scope verlassen wird) automatisch zerstört wird... Ich meine: Selbst wenn ich das jetzt nutze, weiß ich nicht wirklich was ich da tue, weil mir dieses auto_ptr-Ding so fremd ist! 😞

    Danke euch

    MfG



  • Kolumbus schrieb:

    Warum werde ich überhaupt von der VCL gezwungen die StringListe mit new anzulegen, warum kann ich die nicht einfach als Variable anlegen??? - das nervt mich!

    Weil sie nicht als RAII-Klasse abgesichert ist, deshalb wurde empfohlen, sie in den auto_ptr einzulegen. Du kannst Dir auch std::vector<AnsiString> anschauen, dort benötigst Du keinen auuto_ptr. Der kann aber nicht so bequem in eine Daten schreiben, Du müßtest dass dann selber implementieren. Da es aber eine Logdatei ist und diese mal sehr groß werden kann, musst Du es aber früher oder später sowieso tun weil Du sie dann nicht mehr komplett in den Speicher laden kannst.

    akari schrieb:

    ... wie du das absichern kannst hat ja witte mit dem std::auto_ptr schon gezeigt.

    Ich reiche die Lorbeeren an audacia weiter...

    Kolumbus schrieb:

    Irgendwie komm' ich mit diesem auto_ptr-Ding nicht ganz klar... was muss ich dafür einbinden?

    <memory>

    Kolumbus schrieb:

    Und wo bekomme ich mehr Hintergrundwissen zu std::auto_ptr? Alles was ich weiß ist: Ich instanziiere TStringList mittels Konstruktor von etwas, das ich als lokale Variable anlegen kann, damit meine StringList beim Verlassen des scope (egal wodurch der scope verlassen wird) automatisch zerstört wird... Ich meine: Selbst wenn ich das jetzt nutze, weiß ich nicht wirklich was ich da tue, weil mir dieses auto_ptr-Ding so fremd ist! 😞

    Du hast recht, es ist wirklich sehr gefährlich das Teil zu verwenden, wenn man es nicht kennt. Es hat ein obskures Kopierverhalten, kennt kein delete[], ist damit für Arrays nicht geeignet, ist nicht reflexiv, also auch nicht als STL-Containerelement geeignet.
    Du könntest Dir mal das erste Trainingsbuch von Scott Myers anschauen, aber lies es erst an, er verlangt bereits Grundwissen.



  • Edit: Doppelpost wegen Lag



  • Das gelagge treibt mich schon den ganzen Tag in den Wahnsinn... 😡

    witte schrieb:

    Weil sie [die StringList] nicht als RAII-Klasse abgesichert ist, deshalb wurde empfohlen, sie in den auto_ptr einzulegen.

    Ich habe gelesen, dass das mit allen VCL-Klassen so ist... Warum? Das ist doch unpraktisch!?!

    witte schrieb:

    Du kannst Dir auch std::vector<AnsiString> anschauen, dort benötigst Du keinen auuto_ptr. Der kann aber nicht so bequem in eine Daten schreiben, Du müßtest dass dann selber implementieren. Da es aber eine Logdatei ist und diese mal sehr groß werden kann, musst Du es aber früher oder später sowieso tun weil Du sie dann nicht mehr komplett in den Speicher laden kannst.

    Das ist ein interessanter Punkt. Ich würde die Datei sowieso gerne in der Größe begrenzen, so auf 1MB am Besten.... 1MB reicht aus, weil eh nur manuell (ButtonKlick) geloggt wird und bei jedem Log-Vorgang wächst die Datei um maximal 550 Byte. D.h. 1MB reicht aus, weil der Benutzer dann ca. 1900 Anzeigen speichern kann. Wie setze ich die Begrenzung sinnvoll um?

    witte schrieb:

    Kolumbus schrieb:

    Irgendwie komm' ich mit diesem auto_ptr-Ding nicht ganz klar... was muss ich dafür einbinden?

    <memory>

    Danke 🙂 Wie hätte ich das alleine finden können?

    witte schrieb:

    Du hast recht, es ist wirklich sehr gefährlich das Teil zu verwenden, wenn man es nicht kennt. Es hat ein obskures Kopierverhalten, kennt kein delete[], ist damit für Arrays nicht geeignet, ist nicht reflexiv, also auch nicht als STL-Containerelement geeignet.

    Ja, das habe ich teilweise heute schon gelesen... Aber in meinem gezeigten Beispiel ist das ja egal, oder? Da zählt ja nur, dass der Speicher von StringList bei einer Exception vom Destruktor des auto_ptr freigegeben wird, richtig?

    witte schrieb:

    Du könntest Dir mal das erste Trainingsbuch von Scott Myers anschauen, aber lies es erst an, er verlangt bereits Grundwissen.

    Wo kann ich das anlesen?

    =============================================================================
    So, nun mal weiter im Text: sollte ich im vorhin gezeigten Code noch irgendwas exception-sicher machen?

    MfG



  • Kolumbus schrieb:

    Warum werde ich überhaupt von der VCL gezwungen die StringListe mit new anzulegen, warum kann ich die nicht einfach als Variable anlegen??? - das nervt mich!
    ...
    Ich habe gelesen, dass das mit allen VCL-Klassen so ist... Warum? Das ist doch unpraktisch!?!

    Alle Klassen, die von TObject ableiten, werden vom Compiler in vielerlei Hinsicht als Delphi-Klassen behandelt. Konkret heißt das:

    • Das Delphi-VMT-Layout wird verwendet, nicht das C++-VMT-Layout.
    • Da das VMT-Layout von Delphi nicht dafür ausgelegt ist, können VCL-Klassen nicht mehrere Basisklassen haben. (Allerdings kann eine VCL-Klasse beliebig viele Interfaces implementieren.)
    • Für die Klasse wird Delphi-RTTI erzeugt; der VMT-Zeiger der Objekte verweist auf diese. (Der Compiler erzeugt zwar bei Bedarf auch C++-RTTI für Delphi-Klassen, doch wird diese nicht von der Delphi-VMT referenziert, so daß man nur über den statischen Typ des Objektes, also mit typeid (<Klassenname>), nicht über eine Objektinstanz darankommen kann. Das ist die Ursache für dieses Problem.)
    • Wie in Delphi sind statische virtuelle Funktionen sowie virtuelle Konstruktoren möglich.
    • Die Klasse erbt von TObject auch die Methode Free(), die die Freigabe des Objektes bewirkt. Dies erfordert, daß das Objekt auf dem Heap alloziert wird. Durch diese strikte Vorgabe ist es in Delphi leicht, ein Objekt freizugeben, da es nur diese eine Möglichkeit gibt, und alle Klassen, die andere VCL-Klassen besitzen (z.B. Komponenten), geben sie auch auf diesem Wege frei. In C++ wäre die Situation nicht so einfach: das Objekt könnte statisch, auf dem Stack oder auf dem Heap alloziert worden sein. Einem C++-Destruktoren muß dementsprechend auch mitgeteilt werden, ob er auch den Speicher freigeben (wenn das Objekt auf dem Heap alloziert wurde) oder das Objekt nur destruieren soll (wenn es in statischem Speicher oder auf dem Stack liegt - oder auch, wenn der Destruktor von dem Destruktor einer abgeleiteten Klasse aufgerufen wird). Diese Information wird dem Destruktor gewöhnlich über einen unsichtbaren zusätzlichen Destruktor-Parameter übergeben (was man im Disassembler gut nachvollziehen kann).
    • In Delphi kopieren Anweisungen wie
    procedure foo;
    var
      sl1, sl2: TStringList;  
    begin
      sl1 := TStringList.Create;
      sl2 := sl1; // diese hier
      ...
    

    stets nur die Objektreferenz; in C++ entspräche das einer Kopie des Zeigers. Es gibt für Delphi-Objekte keine intrinsische Kopier- und Zuweisungssemantik (erst ab TPersistent gibt es die Assign()-Methode). Man hätte also nicht die gewohnten Vorteile von stackbasierten VCL-Objekten: da sie keinen Kopierkonstruktor haben, sind sie nicht in Containern oder als Funktionsrückgabewerte verwendbar.

    • Es gibt eine einzige Ausnahme, deren Existenz ein Zugeständnis an die C++-Konventionen ist: es ist möglich, in einer throw-Anweisung eine von Sysutils::Exception abgeleitete Klasse mit der in C++ üblichen Syntax für die Erzeugung temporärer Objekte zu erstellen:
    throw Exception ("Something went wrong!");
    

    Dies erzeugt aber nicht wirklich ein stackbasiertes Objekt; vielmehr erkennt der Compiler die Situation und erzeugt denselben Code wie für

    throw new Exception ("Something went wrong!");
    

    .
    (Tatsächlich unterscheidet sich der Code ein wenig hinsichtlich der an __ThrowExceptionLDTC übergebenen RTTI, was vereinzelt für Lecks sorgen kann. Dies ist einer der Bereiche, die in C++Builder 2009 gründlich überarbeitet wurden; so gut wie alle Probleme beim Exception-Handling sind nun behoben.)

    Kolumbus schrieb:

    Und wo bekomme ich mehr Hintergrundwissen zu std::auto_ptr?

    Das hier erklärt Notwendigkeit, Verwendung und Einschränkungen von std::auto_ptr recht gut.

    Kolumbus schrieb:

    Wie hätte ich das alleine finden können?

    Bei Google nach auto_ptr suchen. Erster Treffer.

    Kolumbus schrieb:

    So, nun mal weiter im Text: sollte ich im vorhin gezeigten Code noch irgendwas exception-sicher machen?

    Spontan fällt mir nur das noch auf:
    Deine Verwendung von FileExists() ist, wie witte weiter oben bereits erwähnte, eine Race-Condition (jemand könnte die Datei löschen, nachdem FileExists() erfolgreich zurückkehrt und bevor LoadFromFile() aufgerufen wird). Dann wirft LoadFromFile() eine Exception, der Benutzer bekommt die Fehlermeldung und der Rest von BtnSaveClick() wird nicht ausgeführt, obwohl du eigentlich in diesem Falle einfach eine neue Log-Datei schreiben wolltest. Besser wäre das:

    try
        {
            EADataList->LoadFromFile (DataFile);
        }
            /*
             *  Der einzige Fall, den wir hier berücksichtigen wollen, ist
             *  die Möglichkeit, daß die Datei gar nicht existiert. Wir sehen
             *  also in der Implementation von TStrings::LoadFromFile nach.
             *  TStrings ist in Classes.hpp deklariert, die Implementation ist
             *  also in Classes.pas (in $(BDS)\source\Win32\rtl\common).
             *  TStrings.LoadFromFile ruft seinerseits TFileStream.Create auf,
             *  das wir ebenfalls in Classes.pas finden. Der Definition von
             *  TFileStream.Create entnehmen wir, daß eine Exception vom Typ
             *  EFOpenError geworfen wird, falls die Datei nicht existiert.
             *  Dies ist der Fehler, mit dem wir rechnen, und diese und nur
             *  diese Exception schlucken wir an dieser Stelle stillschweigend.
             */
        catch (EFOpenError&)
        {} // Falls nicht vorhanden, beginnen wir einfach mit einer leeren StringList.
    


  • Bevor ich zu den anderen Punkten meine Fragen stelle, eine Frage zum letzten Punkt des letzten Post von audacia:

    Ich habe 2 Versionen des Borland Builder: einmal C++Builder3 (damit arbeite ich derzeit) und einmal BDS2006. Im Verzeichnis des Builder3 lässt sich keine Classes.pas finden, dort existiert nichtmal das Verzeichnis (BDS)\\source\\Win32\\rtl\\common*. Allerdings gibt es die Datei *Classes.hpp* im Verzeichnis *(BDS)\Include\VCL. Eine Classes.pas findet sich im Ordner $(BDS)\source\Win32\rtl\common des BDS2006... Wird die dann evtl. genutzt, oder gibt es für den Builder 3 nur die Classes.hpp? Falls unter diesen Umständen nur die Classes.hpp relevant ist, habe ich dort die Implementationen von TStringList und TStrings gefunden, allerdings nicht die Implementation von TStrings.LoadFromFile, sondern nur deren Deklaration... Ich wollte mir gerne anschauen, wie das aussieht mit den exceptions bei TFileStream.Create, aber hänge jetzt an diesem Punkt.

    Edit: So sieht die Implementation von TStrings in der Classes.hpp aus:

    class DELPHICLASS TReader;
    class DELPHICLASS TWriter;
    class DELPHICLASS TStream;
    class PASCALIMPLEMENTATION TStrings : public Classes::TPersistent 
    {
    	typedef Classes::TPersistent inherited;
    
    private:
    	//...
    
    protected:
    	virtual void __fastcall DefineProperties(TFiler* Filer);
    	void __fastcall Error(const System::AnsiString Msg, int Data);
    	//...
    
    public:
    	//...
    	virtual void __fastcall LoadFromFile(const System::AnsiString FileName);
    	//...
    public:
    	/* TObject.Create */ __fastcall TStrings(void) : Classes::TPersistent() { }
    
    };
    


  • Die vom Delphi-Compiler generierten Headerdateien enthalten nur Deklarationen, keine Implementationen (wenn man vom manuellen Konstruktor-Forwarding und dergleichen absieht).

    Wie es beim C++Builder 3 ist, weiß ich nicht - der Quelltext ist auch erst ab der Professional-Edition mitgeliefert -, aber bei meinem C++Builder 6 liegt die Datei Classes.pas unter $(BCB)\Source\vcl. Mit ein bißchen Kreativität kommt man da auch von selbst drauf 😉



  • audacia schrieb:

    Die vom Delphi-Compiler generierten Headerdateien enthalten nur Deklarationen, keine Implementationen (wenn man vom manuellen Konstruktor-Forwarding und dergleichen absieht).

    Mein Fehler...

    audacia schrieb:

    Wie es beim C++Builder 3 ist, weiß ich nicht - der Quelltext ist auch erst ab der Professional-Edition mitgeliefert -, aber bei meinem C++Builder 6 liegt die Datei Classes.pas unter $(BCB)\Source\vcl. Mit ein bißchen Kreativität kommt man da auch von selbst drauf 😉

    Ich war dermaßen kreativ, dass ich im WinExplorer die Suchfunktion genutzt habe, um nach Classes.* zu suchen, bevor ich meine Frage gepostet habe 😉
    Es gibt sie nur im Ordner des BDS2006! Kann es sein, dass C++Builder3 die Classes.pas aus dem BDS2006-Ordner nutzt, weil BDS2006 VOR Builder3 installiert wurde?

    Edit: Ich gehe mal davon aus, dass es so ist wie ich vermute und die Classes.pas aus dem BDS2006 genutzt wird, weil es ja keine Andere auf meinem Rechner gibt. Ich habe da mal reingeschaut und TFileStream.Create gefunden:

    constructor TFileStream.Create(const AFileName: string; Mode: Word; Rights: Cardinal);
    begin
      if Mode = fmCreate then
      begin
        inherited Create(FileCreate(AFileName, Rights));
        if FHandle < 0 then
          raise EFCreateError.CreateResFmt(@SFCreateErrorEx, [ExpandFileName(AFileName), SysErrorMessage(GetLastError)]);
      end
      else
      begin
        inherited Create(FileOpen(AFileName, Mode));
        if FHandle < 0 then
          raise EFOpenError.CreateResFmt(@SFOpenErrorEx, [ExpandFileName(AFileName), SysErrorMessage(GetLastError)]);
      end;
      FFileName := AFileName;
    end;
    

    Wenn ich das richtig sehe, ist die zweite exception die, von der audacia sprach. Gut, hab' ich das auch mal gesehen und wenn ich mal eine exception suche, hab' ich zumindest eine Idee, wo die zu finden ist. 🙂



  • Kolumbus schrieb:

    Ich war dermaßen kreativ, dass ich im WinExplorer die Suchfunktion genutzt habe, um nach Classes.* zu suchen, bevor ich meine Frage gepostet habe 😉
    Es gibt sie nur im Ordner des BDS2006! Kann es sein, dass C++Builder3 die Classes.pas aus dem BDS2006-Ordner nutzt, weil BDS2006 VOR Builder3 installiert wurde?

    Nein.
    Die Datei wird, solange du nicht den VCL-Quelltext debuggen willst, ohnehin nicht von der IDE benötigt; gewöhnlich werden deine Anwendungen mit den bereits kompilierten .lib- oder .bpi-Dateien (in diesem Fall rtl.lib bzw. rtl.bpi) gelinkt.
    Weshalb die Datei beim C++Builder 3 nicht vorhanden ist, weiß ich nicht. Hast du die Standard-Edition, hast du beim Installieren die entsprechende Option nicht deaktiviert? Gibt es überhaupt das Verzeichnis $(BCB)\source?



  • Den Ordner Source gibt es im Builder3, ja:
    Ordner Source - Bild1
    Ordner Source - Bild2

    Die Builder-Version ist Standard, ja:
    Builder-Version - Bild

    Ich möchte meinen, ich habe bei der Installation nichts deaktiviert.

    Edit: Meine Lösung sieht jetzt vorläufig so aus:

    // Speichern der aktuell angezeigten Daten
    void __fastcall TFormEAShow::BtnSaveClick(TObject *Sender)
    {
    // ... Verzeichnis wird gesetzt oder Fkt abgebrochen ...
    //  TStringList *EADataList= new TStringList;                    // Alt
        std::auto_ptr<TStringList> EADataList(new TStringList);      // Neu !!! Speicherreservierung wird auch nach exception im Destruktor von auto_ptr freigegeben !!!
    // ... StringList-Einstellungen ...
        AnsiString DataFile= FormMain->SerNr + ".txt";               // Variable mit Name der Speicherdatei (<Seriennummer>.txt)
        AnsiString Hinweis= "Hinweis: ...blabla... \r\n";            // Hinweistext am Anfang einer neuen Datei
        if(FileExists(DataFile))                                     // wenn Datei existiert:
        {
            try                                                      // Neu
            { EADataList->LoadFromFile(DataFile); }                  // Neu
            catch(EFOpenError&)                                      // Neu
            { FormMain->Report(2, 2, 0); return; }                   // Neu Abbruch mit Fehlermeldung
    //      EADataList->LoadFromFile(DataFile);                      // Alt
        }
        else EADataList->Append(Hinweis);                            // wenn neue Datei: Hinweis am Dateianfang
        // ... Holen, Formatieren und Einfügen der Dateieinträge ...
        try                                                          // Neu
        { EADataList->SaveToFile(DataFile); }                        // Neu StringList-Inhalt in Datei speichern
        catch(EFOpenError&)                                          // Neu
        { FormMain->Report(2, 2, 0); return; }                       // Neu Abbruch mit Fehlermeldung
    //  EADataList->SaveToFile(DataFile);                            // Alt
    //  delete EADataList;                                           // Alt StringList-Instanz freigeben
    }
    //----------------------------------------------------------------------------
    

    Für SaveToFile habe ich auch die exception abgefangen, da SaveToFile auch FileStream.Create aufruft - oder brauche ich das nicht, selbst wenn die Datei bereits existiert? Ich dachte mir, wenn die Datei existiert, wird sie ja überschrieben, was unter Umständen auch schiefgehen kann!?!



  • Kolumbus schrieb:

    Die Builder-Version ist Standard

    Das dürfte der Grund dafür sein, daß ein wesentlicher Teil des Verzeichnisinhaltes fehlt.



  • Ok, wenigstens konnte ich mir die Classes.pas aus dem BDS2006 anschauen... Hatte ja denselben Effekt!

    Man beachte mein Edit des letzten Posts... !!!!

    Edit: Dann werde ich mich jetzt mal an den auto_ptr-link von audacia machen 🙂
    Ich habe bei google gestern nach "auto_ptr benutzen" gesucht 🙄



  • Edit 2: Vielen herzlichen Dank audacia - die Seite ist super!!!! 🙂

    PS: Wünsche schonmal Allen ein schönes WE!

    Edit: Man sollte "Editieren" drücken, wenn man editieren will! 🙄



  • Kolumbus schrieb:

    Edit: Meine Lösung sieht jetzt vorläufig so aus:

    // Speichern der aktuell angezeigten Daten
    void __fastcall TFormEAShow::BtnSaveClick(TObject *Sender)
    {
    // ... Verzeichnis wird gesetzt oder Fkt abgebrochen ...
    //  TStringList *EADataList= new TStringList;                    // Alt
        std::auto_ptr<TStringList> EADataList(new TStringList);      // Neu !!! Speicherreservierung wird auch nach exception im Destruktor von auto_ptr freigegeben !!!
    // ... StringList-Einstellungen ...
        AnsiString DataFile= FormMain->SerNr + ".txt";               // Variable mit Name der Speicherdatei (<Seriennummer>.txt)
        AnsiString Hinweis= "Hinweis: ...blabla... \r\n";            // Hinweistext am Anfang einer neuen Datei
        if(FileExists(DataFile))                                     // wenn Datei existiert:
        {
            try                                                      // Neu
            { EADataList->LoadFromFile(DataFile); }                  // Neu
            catch(EFOpenError&)                                      // Neu
            { FormMain->Report(2, 2, 0); return; }                   // Neu Abbruch mit Fehlermeldung
    //      EADataList->LoadFromFile(DataFile);                      // Alt
        }
        else EADataList->Append(Hinweis);                            // wenn neue Datei: Hinweis am Dateianfang
        // ... Holen, Formatieren und Einfügen der Dateieinträge ...
        try                                                          // Neu
        { EADataList->SaveToFile(DataFile); }                        // Neu StringList-Inhalt in Datei speichern
        catch(EFOpenError&)                                          // Neu
        { FormMain->Report(2, 2, 0); return; }                       // Neu Abbruch mit Fehlermeldung
    //  EADataList->SaveToFile(DataFile);                            // Alt
    //  delete EADataList;                                           // Alt StringList-Instanz freigeben
    }
    //----------------------------------------------------------------------------
    

    Für SaveToFile habe ich auch die exception abgefangen, da SaveToFile auch FileStream.Create aufruft - oder brauche ich das nicht, selbst wenn die Datei bereits existiert? Ich dachte mir, wenn die Datei existiert, wird sie ja überschrieben, was unter Umständen auch schiefgehen kann!?!

    Zunächst verhält sich dein Code immer noch inkonsistent hinsichtlich der von witte erwähnten Race-Situation: Wenn FileExists() erfolgreich ist, danach die Datei von einem anderen Prozeß oder Thread gelöscht wird und dann LoadFromFile() fehlschlägt, bekommt der Benutzer eine Fehlermeldung (oder was auch immer Report() bewirkt), wenn die Datei aber von vorneherein nicht existiert, funktioniert alles. Wenn ich dich richtig verstanden hatte, möchtest du doch aber die Log-Datei, sofern sie nicht existiert, neu anlegen.

    Um das zu beheben, entferne die Überprüfung mit FileExists(), fange Exceptions vom Typ EFOpenError ab und laß den catch-Handler einfach leer.

    Die Exceptions, die SaveToFile() wirft, brauchst du wie fast alle Exceptions gewöhnlich nicht zu behandeln; läuft dort etwas falsch, so fängt der erwähnte VCL-Exception-Handler die Ausnahme und zeigt dem Benutzer eine Fehlermeldung an.
    Wenn du deine Fehlermeldungen gerne auf andere Weise protokolliert hättest, ist es viel einfacher, das mit TApplicationEvents::OnException für alle Exceptions zu tun und nicht alles mögliche in try/catch-Blocks zu verschachteln, denn das ist lückenhaft und über die Maßen fehleranfällig. (Beispielsweise wirft SaveToFile() bestimmt keine EFOpenError-Exception 😉 )



  • audacia schrieb:

    ...Wenn FileExists() erfolgreich ist, danach die Datei von einem anderen Prozeß oder Thread gelöscht wird und dann LoadFromFile() fehlschlägt, bekommt der Benutzer eine Fehlermeldung (oder was auch immer Report() bewirkt), wenn die Datei aber von vorneherein nicht existiert, funktioniert alles. ...

    Ja, genau so soll es sein und so ist es ja jetzt.

    audacia schrieb:

    ...Wenn ich dich richtig verstanden hatte, möchtest du doch aber die Log-Datei, sofern sie nicht existiert, neu anlegen.

    Ja richtig. Jedoch nicht im Zweifelsfall! Und der liegt vor, wenn die Datei zwar existiert dann aber doch nicht geöffnet werden kann. Ansonsten wird die Datei ja angelegt, wenn FileExist sie nicht findet (sprich: Wenn sie ohne Zweifel nicht existiert).

    audacia schrieb:

    Um das zu beheben, entferne die Überprüfung mit FileExists(), fange Exceptions vom Typ EFOpenError ab und laß den catch-Handler einfach leer.

    Nein. So wie es jetzt ist kann ich zwei Fälle unterscheiden: Entweder (1) die Datei existiert garnicht oder (2) sie hat zum Zeitpunkt der Prüfung (FileExist) existiert, lässt sich jedoch nicht öffnen.
    EFOpenError wird doch auch ausgelöst, wenn die Datei zum Bsp. gerade geöffnet ist - in diesem Fall möchte ich bestimmt keine Neue anlegen!!!

    audacia schrieb:

    ... Die Exceptions, die SaveToFile() wirft, brauchst du wie fast alle Exceptions gewöhnlich nicht zu behandeln; läuft dort etwas falsch, so fängt der erwähnte VCL-Exception-Handler die Ausnahme und zeigt dem Benutzer eine Fehlermeldung an. ...

    ??? Dann verstehe ich die ganze Aufregung nicht... SaveToFile muss hier eine existierende Datei öffnen und Daten anhängen - warum soll da kein EFOpenError möglich sein? Genau genommen führt SaveToFile, wenn die Datei bereits existiert hat, anfänglich doch dieselben Schritte durch wie LoadFromFile!?!



  • Kolumbus schrieb:

    Nein. So wie es jetzt ist kann ich zwei Fälle unterscheiden: Entweder (1) die Datei existiert garnicht oder (2) sie hat zum Zeitpunkt der Prüfung (FileExist) existiert, lässt sich jedoch nicht öffnen.
    EFOpenError wird doch auch ausgelöst, wenn die Datei zum Bsp. gerade geöffnet ist - in diesem Fall möchte ich bestimmt keine Neue anlegen!!!

    Okay, wenn das deine Absicht ist, ist deine Lösung in Ordnung.

    Kolumbus schrieb:

    audacia schrieb:

    ... Die Exceptions, die SaveToFile() wirft, brauchst du wie fast alle Exceptions gewöhnlich nicht zu behandeln; läuft dort etwas falsch, so fängt der erwähnte VCL-Exception-Handler die Ausnahme und zeigt dem Benutzer eine Fehlermeldung an. ...

    ??? Dann verstehe ich die ganze Aufregung nicht... SaveToFile muss hier eine existierende Datei öffnen und Daten anhängen - warum soll da kein EFOpenError möglich sein?

    Weil TStrings.SaveToFile einen Stream folgendermaßen benutzt:

    procedure TStrings.SaveToFile(const FileName: string);
    var
      Stream: TStream;
    begin
      Stream := TFileStream.Create(FileName, fmCreate);
      try
        SaveToStream(Stream);
      finally
        Stream.Free;
      end;
    end;
    

    Und in TFileStream.Create steht das:

    constructor TFileStream.Create(const AFileName: string; Mode: Word; Rights: Cardinal);
    begin
      if Mode = fmCreate then
      begin
        inherited Create(FileCreate(AFileName, Rights));
        if FHandle < 0 then
          raise EFCreateError.CreateResFmt(@SFCreateErrorEx, [ExpandFileName(AFileName), SysErrorMessage(GetLastError)]);
      end
      else
      ...
    

    Und dies nur zur Begründung, warum SaveToFile keinen EFOpenError wirft. Darüber hinaus brauchst du, wie ich zu erläutern versuchte, die Exception, die SaveToFile() wirft, gar nicht explizit zu fangen, wenn es dir ausreicht, daß der Benutzer im Falle einer Exception einfach eine Fehlermeldung bekommt.



  • Ok, ist angekommen. Also lasse ich bei SaveToFile den catch-Block leer, es sei denn ich möchte eigene Fehlermeldungen haben. Dann muss ich allerdings EFCreateError abfangen. Danke für den Hinweis.

    Allerdings habe ich gerade ein Problem: Ich habe eine existierende Datei geöffnet und lasse dann das Programm laufen. Wenn ich an der entsprechenden Stelle "Speichern" drücke, wird weder der Eine noch der Andere catch-Block angesprungen, die Datei wird jedoch auch nicht aktualisiert (wie auch, sie ist ja grad im Texteditor geöffnet)!?? Müsste nicht eigentlich eine Ausnahme auftreten? Warum tritt keine Ausnahme auf???



  • Kolumbus schrieb:

    Allerdings habe ich gerade ein Problem: Ich habe eine existierende Datei geöffnet und lasse dann das Programm laufen.

    Geöffnet womit? Die meisten Editoren, auch Notepad, sperren geöffnete Dateien nicht, so daß du sie dennoch zum Schreiben öffnen kannst.



  • audacia schrieb:

    Geöffnet womit?

    Notepad.

    audacia schrieb:

    Die meisten Editoren, auch Notepad, sperren geöffnete Dateien nicht, so daß du sie dennoch zum Schreiben öffnen kannst.

    Ok. Wenn ich die Datei hinterher schliesse und wieder öffne, steht auch der neue Eintrag drin. 🙄

    Ich hab jetzt mal 1, 2 Ausnahmen herbeigeführt - es funktioniert Alles zufriedenstellend. 👍

    Edit: Jetzt mal Hand auf's Herz: Wie wahrscheinlich ist denn eigentlich der Fall, dass eine Datei, die ich Bruchteile von Sekunden vorher auf Existenz überprüft habe, plötzlich nicht mehr vorhanden ist?



  • Kolumbus schrieb:

    Edit: Jetzt mal Hand auf's Herz: Wie wahrscheinlich ist denn eigentlich der Fall, dass eine Datei, die ich Bruchteile von Sekunden vorher auf Existenz überprüft habe, plötzlich nicht mehr vorhanden ist?

    Es geht ums Prinzip 😉

    Nein, im Ernst, für deine Logdatei ist die gegenwärtige Lösung natürlich völlig ausreichend, auch weil das schlimmste, was passieren könnte, eine irrtümliche Fehlermeldung an den Benutzer wäre. Aber grundsätzlich sollte man das Problem der Race-Conditions nicht unterschätzen. Natürlich ist die Wahrscheinlichkeit relativ klein, daß zwei Threads im exakt gleichen Moment eine Variable verändern, auf die der Zugriff nicht atomar erfolgt; dennoch sollte dieser Zugriff serialisiert werden. Frei nach Charles Petzold: Diese kleinen Nachlässigkeiten haben es gewöhnlich an sich, daß sie garantiert sämtliche Betatest-Zyklen überstehen, dafür aber während der Produktpräsentation das Programm auf ominöse Art zum Absturz bringen 😉


Anmelden zum Antworten