ii ii ppppp ii pp pp ii pp pp ii ppppp pp pp ip's Tutorial: Dateizugriff in Pascal und Delphi (Version 1.0) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Viele Leute haben mich schon gefragt, wie man denn eigentlich in Dateien schreiben und wieder aus ihnen lesen kann. Da es sehr lästig ist jedesmal wieder auf jede Frage einzelnd zu antworten und ich ab und zu Tutorials schreibe, habe ich mich dazu ent- schlossen dieses Tutrorial zu verfassen. Es wird erklären, wie man auf Dateien zugreift. Erzeugen, Öffnen, Schreiben und Lesen. Ich hoffe, dass dieses Tutorial euch helfen wird. 1.0 Einführung ~~~~~~~~~~ Grundlegend stehen uns 2 Möglichkeiten zur Verfügung in Dateien zu schreiben und aus ihnen wieder zu lesen. Einmal durch die modernen TFileStreams (nur Delphi) und dann die verschiedenen File-Type Methoden (Textfile, Typisiert und Untypisiert). Ich will in diesem Tutorial nur auf letztere eingehen (es hat den sowieso schon recht voluminösen Umfang des Tutorials gesprengt auch noch auf die Streams einzugehen). Ein Tutorial zu den FileStreams ist in arbeit und wird sehr warscheinlich in nächster Zeit auf meiner Homepage (http://ip-web.hn.org) zu finden sein. 1.1 Für was Dateien? ~~~~~~~~~~~~~~~~ Die Frage erscheint auf den ersten Blick sinnlos. Ganz klar, Dateien brauche ich um Daten zu speichern. Aber brauche ich eigentlich immer Dateien, oder sind manchmal nicht andere Informationshalter besser. Letztenendes werden alle Daten, die man längerfristig sichern will in Dateien gespeichert, aber oft gibt es eine bessere und leistungsfähigere Art sie zu speichern, als eine eigene Dateizugriffsroutine zu schreiben. So können (und sollten) zB Lagepunkte der Form, oder andere "Kleinigkeiten" eines Programms in speziell dazu vorgesehenen INI Dateien (bzw ab Win9x in der Registry) gesichert werden. Große Adressdatenbanken (der Name sagt es uns ja bereits) sollten lieber in dafür vorgesehenen Datenbanken gesichert werden. Meißt erspart das eine Menge arbeit und ist sogar schneller als die eigene Lösung. 1.2 Kurze Abwärtskompatibilitäts-Erklärung ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Die hier gezeigten Quelltexte wurden für Delphi geschrieben. Jedoch sind sie auch leicht nach Pascal portierbar. Es müssen lediglich die Befehle AssignFile durch ein einfaches Assign und CloseFile durch ein einfaches Close ersetzt werden und schon sind die Quelltexte größtenteils auch in Pascal einsetzbar. Natürlich gibt müssen die Standard Delphi Befehle die benutzt werden (zB ShowMessage) durch ein Pascal Equivalent ersetzt werden (zB WriteLn). 2.0 Jetzt gehts los! ~~~~~~~~~~~~~~~~ Ok, dann wollen wir mal loslegen. Zunächst wollen wir in eine einfache Textdatei schreiben, uns über eine typisierte an eine untypisierte Binäre heranwagen. Noch vorne weg: Dateien und ihre Handhabung) sind rel unberechnenbar. Exisitiert eine Datei bereits, habe ich die nötigen Rechte, hat eine Datei überhaupt einen Inhalt? All diese Fragen (und noch mehr) müsst ihr beim schreiben eurer Dateibehandlungsroutinen beachten und voraus sehen können. Also geht lieber auf Nummer sicher und packt eure Dateibehandlungsroutinen voll mit Fehlerbehandlungsroutinen. Ich werde nur kurz darauf eingehen (und meine Quelltexte sind um Gottes Willen auf keine Fall ausreichend vor Fehlern geschützt), deswegen solltet ihr euch diesem Thema nocheinmal seperat widmen. Es wäre äußerst schade, wenn Stunde (oder gar noch mehr) harter Arbeit eines Users eurer Programme verloren gehen, nur weil eure Dateizugrissroutine einen kleinen Bug hat. Aber genug geredet. Ihr woll Anfangen? Na dann los, wir haben einiges vor uns! 2.1.0 Textfile erstellen ~~~~~~~~~~~~~~~~~~ Das schreiben und lesen in eine Textdatei unterscheidet sich nur geringfügig von der Ein- und Ausgabe auf dem Bildschirm und der Tastatur in einer Konsolenapplikation. Als grundlegende Schreib- und Lesebefehle dienen Read- und WriteLn. Bevor jedoch in eine Datei geschrieben werden kann oder aus ihr gelesen werden kann muss sie zunächst erstellt werden. Um eine Datei erstellen zu können müssen wir zunächst einer Variable die Dateireferenz zuweisen. Danach können wir die Datei mit dem Befehl ReWrite erzeugen (bzw wenn sie bereits existiert neu anlegen). Das machen wir wie folgt: ---- var Datei: Textfile; begin //Dateireferenz festlegen AssignFile(Datei,'C:\Textdatei.txt'); //Datei anlegen bzw überschreiben ReWrite(Datei); //Datei schließen und speichern CloseFile(Datei); end; ---- Zunächst eruegen wir eine Variable vom Typ Textfile. Mit dieser Variable wird im Programm gearbeitet. Der Typ Textfile sagt unserem Programm, dass wir nur Schriftzeichen in unsere Datei schreiben wollen. Also tut das auch und versucht keine Integer- oder andere Werte darin zu speichern. Dafür werden wir nachher noch möglichkeiten kennen lernen. Der Befehl AssignFile legt die Dateireferenz fest. Die Variable 'Datei' wird somit praktisch wie ein Pointer auf eine Datei. Der Befehl ReWrite erzeugt nun die durch die Referenz fest- gelegte Datei oder überschreibt (löscht) diese, falls sie bereits existiert. Was wir im mom noch außer Acht lassen können, aber gleich sehr wichtig ist, ist, dass die Datei durch den Befehl auch gleich zum schreiben geöffnet wird. CloseFile schließt die Datei wieder und speichert ihren Inhalt. In Delphi Programmen solltet ihr Dateioperationen immer in Try/Finally Blöcken haben und im Finally die geöffneten Dateien mit CloseFile wieder schließen, damit es auch bei einem Fehler nicht zu einem unerwünschten Datenverlust kommt. 2.1.2 In eine Textfile schreiben ~~~~~~~~~~~~~~~~~~~~~~~~~~ Da uns die beste und schönste Datei nichts bringt, wenn wir darin keine Informationen speichern können, müssen wir auch in sie schreiben können. Wie bereits erwähnt, wird das in etwa so gemacht, wie wir auch in einer Konsolenapplikation auf dem Bildschirm ausgeben. Hier zunächst einmal der Quelltext: ---- //Dateireferenz erzeugen var Datei: Textfile; begin try //DateiRefernz festlegen AssignFile(Datei,'C:\Textdatei.txt'); //Datei erstellen bzw löschen ReWrite(Datei); //In die Datei die Zeile 'Hallo, Welt!' schreiben WriteLn(Datei,'Hallo, Welt!'); finally //Datei schließen und speichern CloseFile(Datei); end; end; ---- Wenn ihr nun die Datei öffnet (zb im Windows Editor), dann werdet ihr darin tatsächlich die Zeile 'Hallo, Welt!!' finden. Die ersten paar Befehle sollten nun klar sein. DOch was macht der neue Befehl WriteLn? Nun, der erste Parameter, der dem Befehl übergeben wird, ist die Dateireferenz. Der zweite ist der zu speichernde Text. Die Zeile wird nun in die leere Textdatei (das ist sie, durch den Befehl ReWrite) gespeichert. Am ende jeder Zeile wird durch den Befehl WriteLn ein Linefeed / Carriagereturn (#10#13) gesetzt, dass zu einem Zeilenumbruch führt. Eine Zeile bringt meistens nur recht wenig, deshalb wollen wir eine weitere Speichern. Das Problem, dass uns dabei entgegen kommt ist, dass wir die Datei im mom nur durch den Befehl ReWrite öffnen können, bei dem die Datei bekanntlich ja überschrieben wird. Darum lernen wir nun einen weiteren Befehl hinzu und zwar Append. Mit Append können wir die Datei öffnen, ohne dass sie überschrieben wird. Alles was in die Datei geschrieben wird, wird an das Ende der Datei angehängt. Das will ich mit folgendem Codeschnipsel mal demonstrieren: ---- var Datei: Textfile; begin try //Hierzu sind keine Erläuterungen mehr nötig AssignFile(Datei,'C:\Textdatei.txt'); ReWrite(Datei); //2 Zeilen direkt hintereinander in die Datei schreiben WriteLn(Datei,'Zeile 1'); WriteLn(Datei, Zeile 2'); //Datei schließen und speichern CloseFile(Datei); //Datei zum anhängen öffnen und in sie schreiben Append(Datei); WriteLn(Datei,'Zeile 3'); finally //Datei wieder schließen und speichern CloseFile(Datei); end; end; ---- Zunächst die altbekannten Dinge: Referenz fetslegen und die Datei öfnnen. Wie wir sehen können, können wir den WriteLn Befehl auch ohne Probleme 2 mal hintereinander verwenden. Die 2 Zeilen werden dann jede für sich in eine seperate Zeile der Datei geschrieben. Nachdem wir die Datei geschlossen (und gespeichert) haben öffnen wir sie ein weiteres mal, aber diesmal mit dem eben bereits erwähnten Befehl Append. Append öffnet die Datei im Schreibmodus, setzt den Zeiger aber an das ende der Datei und überschreibt nicht deren Inhalt. So ist es uns möglich in eine Datei zu schreiben, ohne ihren alten Inhalt zu löschen. Nachdem eine 3. Zeile in die Datei geschrieben worden ist, schließen (und speichern) wir die Datei wieder. War doch alles garnicht mal so schwer. Nun könnt ihr wie wild eure ganzen Daten (zB Spielstände oder Ähnliches) einfach in eine Datei speichern. Super, nicht? Mmhhh... ja, ihr habt recht... ihr wollt sie auch wieder laden. Klar! Daten, die man nicht laden kann bringen nichts, also auf ins nächste Kapitel: Das Laden aus Textfiles! 2.1.3 Laden aus Textfiles ~~~~~~~~~~~~~~~~~~~ Wie eben schon erwähnt bringen die besten und hübschesten Dateien recht wenig, wenn man nicht wieder aus ihnen laden kann. Das wollen wir aber ändern und hier lernen, wie man aus einer Textfile laden kann. Das Laden funktioniert im Printip genauso wie das schreiben und zwar mit dem Befehl ReadLn. Dieser Befehl schnappt sich eine Zeile aus einer Datei und schreibt diese in eine String Variabe. Damit wir aber überhaupt aus einer Datei lesen können müssen wir zunächst eine Möglichkeit finden, die Datei zu öffnen, das weder der Inhalt gelöscht wird (ReWrite), noch der Dateizeiger an das Ende der Datei gesetzt wird (Append). Dieses kleine Meisterwerk vollführt Reset. Mit Reset wird die Datei geöffnet, ihr Inhalt nicht gelöscht und der Dateizeiger an den Anfang der Datei gesetzt. So sollte das ganze aussehen: ---- var Datei: Textfile; //Buffer für das Lesen aus der Datei Buffrer: String; begin //Referenz setzen AssignFile(Datei,'C:\Textdatei.txt'); //Datei öffnen Reset(Datei); //Aus Datei lesen ReadLn(Datei, Buffer); //Den gelesenen Buffer ausgeben Memo1.Lines.Add(Buffer); end; ---- Zunächst vorne weg: Falls euer Programm abgestürzt ist, lest beruhigt weiter. Das ist gut möglich. Warum das geschieht und wie ihr es verhindern könnt werde ich euch gleich erläutern. Zunächst aber die Erklärung des Quelltextes: Als erstes Deklarieren wir eine String-Variable, die uns als Lese-Buffer dienen wird. Als nächstes setzen wir die Referenz der Textfile-Variable "Datei" und öffnen diese mit Reset(hier könnte unter Umständen ein Fehler auftreten!). Als nächstes lesen wir aus der Datei mit dem Befehl ReadLn, wobei diesem zunächst die Dateireferenz und als 2. Parameter ein Puffer- Variable, in der die eingelesene Zeile abgelegt werden soll, übergeben werden muss. In unserem Fall ist die Puffer-Variable die Variable namens "Buffer". Als letztes wollen wir die gelesene Zeile auch wieder ausgeben. Das habe ich nach Delphi-Manier in ein MemoFeld gemacht. Ihr könnt, falls ihr eine Konsolen Apllikation schreibt das auch einfach durch ein WriteLn(Buffer) ersetzen, um es auszugeben. Nun aber zum Fehler, der auftreten kann. Falls ihr keinen Fehler hattet, dann wollen wir jetzt einen erzeugen. Dazu ändert ihr den Namen der Datei-Referenz so ab, dass sie auf eine Datei zeigt, die nicht existiert (zB AssignFile(Datei,'$%"$%'); ). Wenn ihr euer Programm nun startet, wird es 100% zu einem Fehler kommen. Verständlich, denn es wird versucht eine Datei zu öffnen, die garnicht existiert. Doch es ist sehr unschön, dass dann auf Anhieb euer gesamtes Programm abstürzt. Wäre es nicht schöner eine hübsche kleine Fehlermeldung zu haben? Das ist kein Problem. Zunächst müssen wir aber das Programm so verändern, dass es seine interne I/O- (Input/Output -> Eingabe/Ausgabe) Fehlerüberprüfung abschaltet, damit wir nicht diese unschönen Fehlermeldungen und Abstürze bekommen. Das machen wir mit der Compilerdirektive {$I-}. Dieses Symbol in den Quelltext eingefügt deaktiviert die interne I/O-Fehlerkontrolle. Mit {$I+} wird diese wieder aktiviert. Natürlich müssen wir auch iergendwie überprüfen können, ob es überhaupt zu einem Fehler gekommen ist. Dafür stellt uns Pascal eine hübsche Funktion zur Verfügung (die übrigens in älteren Pascal-Versionen einen Bug hat): IOResult Diese Funktion liefert einen Fehlercode zurück, falls ein I/O- Fehler aufgetreten ist. Wenn es keinen Fehler gab liefert sie 0 zurück. Ihr wollt aber bestimmt sehen, wie das in der Praxis aussieht: ---- var Datei: Textfile; //Buffer für das Lesen aus der Datei Buffrer: String; //Buffer für den Fehlercode IOBuffer: Integer; begin //Referenz setzen AssignFile(Datei,'C:\Textdatei.txt'); //Integrierte I/O-Fehlerüberwachung abschalten {$I-} //Datei öffnen Reset(Datei); //Fehlercode abholen IOBuffer := IOResult; //I/O-Fehlerüberwachung wieder aktivieren (wichtig!) {$I+} //Überprüfen ob ein Fehler aufgetreten ist IF IOBuffer <> 0 then //Fehlermeldung ausgeben und Procedur verlassen begin ShowMessage('Fehler beim Laden der Datei!'); Exit; end; //Aus Datei lesen ReadLn(Datei, Buffer); //Den gelesenen Buffer ausgeben Memo1.Lines.Add(Buffer); end; ---- Falls ihr nun oben eine Datei angebt, die nicht existiert, dann wird es nichtmehr zu einem Abbruch eures Programms, sondern zu einer hübschen Fehlermeldung kommen. Für die Pascaller: Ihr könnt die Zeile ShowMessage auch einfach wieder durch ein WriteLn('Fehler beim Laden der Datei') ersetzen. Nun wollen wir aber erstmal auf den Quelltext eingehen: Zunächst daklarieren wir oben einen Buffer, in den wir das Ergebnis der IOResult Funktion schreiben können (nicht zwingend notwendig). Als nächstes wird die I/O-Fehlerüberprüfung ab- geschaltet. Das wird durch den Compielerbefehl {$I-} gemacht. Die nächste Zeile sollte bekannt sein: sie versucht die Datei zu öffnen. Nun holen wir den Fehlercode ab. Wir schreiben ihn in die Variable IOBuffer. An dieser Stelle aktivieren wir die I/O-Fehlerüberprüfung wieder. Sie sollte idR immer dann, wenn sie nicht dringend deaktiviert werden muss (zB zu eigenen Fehlerüberprüfung) aktiviert sein. Also nicht vergessen! Der Compilerbefehl dazu lautet {$I+}. So, nun kommt sind wir an der Stelle angelangt, an der wir überprüfen, welcher Fehlercode uns zurück gegeben wurde. Da 0 kein Fehler bedeutet überprüfen wir, ob der Rückgabewert abders ( <> ) als 0 ist. Wenn dies der Fall ist, liegt ein Fehler vor und unsere Nachricht wird ausgegeben und die Prozedur sofort verlassen. Fall ihr den Quelltext im Hauptteil einer Konsolen Applikation stehen habt, wird euer Programm (mit einer hübschen Fehlermeldung) beendet. Nun wisst ihr also auch, wie man aus einer Textfile lesen kann und das auch noch, mit Fehlerkontrolle. Garnicht mal so schlecht für den Anfang! Aber bis jetzt habt ihr ja nur eine Zeile gelesen. Ja, ihr vermutet richtig, wenn ihr denkt, dass ihr einfach mehrere ReadLn hintereinander setzen könnt... aber stellt euch mal vor, ihr wollt ein Programm schreiben, dass einen ganzen Text (von dem ihr nicht einmal wisst, wie lang er ist) laden wollt... das wird problematisch! Naja.. eigentlich nicht, denn es gibt eine Möglichkeit zu überprüfen, ob man am Ende einer Datei angelangt ist. Der Funktionsaufruf lautet EOf(Datei). Wenn man am Ende der Datei ist, wird True zurück gegeben, ansonsten False. Somit lässt sich ganz einfach eine Schleife schreiben, die alle Zeilen, bis hin zum Ende einer Datei ausließt. Hier mal das ganze in der Praxis: ---- var Datei: Textfile; //Buffer für das Lesen aus der Datei Buffrer: String; //Buffer für den Fehlercode IOBuffer: Integer; begin //Referenz setzen AssignFile(Datei,'C:\Textdatei.txt'); //Integrierte I/O-Fehlerüberwachung abschalten {$I-} //Datei öffnen Reset(Datei); //Fehlercode abholen IOBuffer := IOResult; //I/O-Fehlerüberwachung wieder aktivieren (wichtig!) {$I+} //Überprüfen ob ein Fehler aufgetreten ist IF IOBuffer <> 0 then //Fehlermeldung ausgeben und Procedur verlassen begin ShowMessage('Fehler beim Laden der Datei!'); Exit; end; //Schleife wiederholen, bis Ende der Datei erreicht ist while not EOf(Datei) do begin //Aus Datei lesen ReadLn(Datei, Buffer); //Den gelesenen Buffer ausgeben Memo1.Lines.Add(Buffer); end; end; ---- Eigentlich hat sich zum Vorgänger nicht viel verändert. Das einzigem was noch hinzu gekommen ist, ist die kleine while- Schleife. Wir wollen die Schleife mal Sinngemäß übersetzen: "Solange nicht das Ende der Datei mache..." Das ist auch schon der ganze Zauber! Nicht sonderlich schwer, oder? Naja... nach ein paar Testläufen werdet ihr es sicher ohne Probleme selbst hinbekommen :) Nun wißt ihr alles, was man wissen muss, um in Textfiles zu schreiben und aus ihnen wieder zu lesen. Im nächsten Kapitel werdet ihr erfahren, was Typisierte Dateien sind und wie man sie benutzt. 2.2.0 Typisierte Dateien ~~~~~~~~~~~~~~~~~~ Inzwischen könnt ihr also schon in Textdateien schreiben, aus ihnen lesen und sogar Fehler abfangen und behandeln. Aber ihr könnt halt leider nur in Textdateien schreiben. Stellt euch nun mal vor, ihr wolltet ein Adressbuch oder ein Spiel programmieren und müsstet alles als Texte speichern... das würde nicht so gut funktionieren. Deshalb gibt es in Pascal und in OP die möglichkeit Dateien zu typisieren. Ihr könnt ihnen jeglichen Datentyp zuweisen, sogar eigene Records! Wie das geht, erfahrt ihr nun! 2.2.1 Hä? Was für Typen? ~~~~~~~~~~~~~~~~~~ Gute frage... was genau sind typisierte Dateien denn eigentlich? Nun im Prinzip sind sie Dateien, in die ihr Typen (Variablen) schreiben könnt. Nehmen wir an, ihr habt eine typisierte Datei eines Integers. Nun könnt ihr in diese Datei Integer-Werte schreiben und wieder lesen. Im Prinzip war die Textdatei eine typisierte Datei eines Strings. Aber ihr könnt mit Typisierten Dateien noch viel mehr machen. Ihr konnt ihnen nämlich nich tnur die Standardtypen zuweisen, sondern auch selbst definierte Records. Damit erhaltet ihr ein recht mächtiges Werkzeug, was die Speicherung von Daten angeht. Ihr könnt dann zB ein Record, dass aus 2 Integers, 1 String[255] 1 Word und 4 Booleans besteht, mit einem einziegn Befehl in eine Datei schreiben und auch wieder auslesen. Damit könnt ihr zB ganze Spielstände speichern oder eine eigene Adress- verwaltung schreiben. Wie definiert man aber eine soclhe typisierte Datei. Im Pinzip ganz einfach: ---- //Eine typisierte Dateireferenz erzeugen var Datei: File of Type; ---- Was, das' alles? Ja, das ist alles! Type müsst ihr natürlich durch einen beliebigen Datentyp ersetzen (zB Integer). Wir wollen uns aber nicht lange mit solch abstrakten Kram aufhalten und uns lieber mit dem Schreiben in eine typisierte Datei beschäftigen. 2.2.2 Den Bleistift Spitzen... ~~~~~~~~~~~~~~~~~~~~~~~~ Nun wollen wir uns also auch schon an das Schreiben in eine typisierte Datei heranwagen. Ok, dann legen wir gleich mal richtig hart los: ---- //Typieisrte Dateireferenz auf eine Integer-Datei erzeugen var Datei: File of Integer; //Diese Variable werden wir in die Datei schreiben Wert: Integer; begin //Dateireferenz zuweisen AssignFile(Datei,'C:\Integerdatei.dat'); //Datei löschen/erstellen ReWrite(Datei); //Unserer Integervariable einen Wert zuweisen Wert := 1234; //Wir schreiben die Variable in die Datei Write(Datei,Wert); //Die Datei schließen und speichern CloseFile(Datei); end; ---- So sieht also der Quelltext aus, mit dem man in eine typisierte Datei schreibt. Mhh... sieht eigentlich fast aus wie der von den Textdateien... aber nur fast :) Bei Typisierten Dateien benutzen wir zum schreiben nicht mehr den Befehl WriteLn (Schreibe Zeile), sondern den "Schwester- befehl" Write. Write setzt im Gegensatz zu WriteLn keinen Zeilenumbruch an das ende des Geschriebenen und vor allem kann auch andere Datentypen als Strings schreiben. Wie ihr seht wird der Datentypbezeichner einer typisierten Datei aus 3 Wörtern zusammengesetzt: File, of und Type. Type ist dabei aber immer durch den erwünschten Datentyp zu ersetzen. Wenn ihr zB eine Datei erzeugen wollt, in die ihr Kommazahlen schreiben könnt, dann müsst ihr die Datei wie folgt deklarieren: ---- var Datei: File of Real; ---- Es ist jedoch berdammt wichtig, dass ihr in Typisierte Dateien auch nur Variablen des angegebenen Types schreiben könnt. Ihr könnt also in die Real-Datei keine Integer schreiben. Typisierte Dateien werden übrigens genauso geöffnet wie Textdateien und das gleiche gilt auch für die Fehlerbehandlung der typisierten Dateien. Nur die Deklaration und das schreiben (und lesen) weicht von den Textdateien ab. Für was aber den ganzen Mist? So toll ist es schließlich auch nicht, eine einzige (oder eine Reihe) einfacher Datentypen in eine Datei zu schreiben. Was soll ich mit 100 Integerwerten in einer Datei? Gut frage, das weiß ich auch nicht, aber typisierte Dateien haben noch einen anderen entscheidenden Vorteil: Man kann nicht Standard-Typen mit ihnen schreiben sondern auch abgeleitete Typen, bzw zusammengesetzte (zB Records). Da öffnet sich nun plötzlich ein riesiges Einsatzspektrum: -Spiele -Adresskarteien -Userverwaltung -usw Ihr könnt zB in eine Datei einen Record schreiben, der Infor- mationen zum momentanen Spielstand enthält. Das wollen wir auch einfach mal tun, um das ganze ein wenig zu demonstrieren: ---- //UNseren Recordtypen definieren type TSpielStand = record SpielerName: String[20]; Lebensenergie: Byte; Mana: Byte; Level: Byte; StarkeRuestung: Boolean; Begleittier: Boolean; end; //Die typisierte Datei deklarieren var Datei: File of TSpielStand; //Eine Variable des Record-Types erzeugen SpielStand: TSpielStand; begin //Die Referenz festlegen AssignFile(Datei,'C:\Spielstand.dat'); //Datei erzeugen/löschen ReWrite(Datei); //Dem Record Werte zuweisen with SpielStand do begin SpielerName := 'Heinz'; LebensEnergie := 100; Mana := 100; Level := 1; StarkeRuestung := False; BeGleitTier := True; end; //Den Record in die Datei schreiben Write(Datei, SpielStand); //Datei speichern und schließen CloseFile(Datei); end; Huch? Erscheint viel und unübersichtlich? Macht euch nix drauß, das meiste ist nur die Erzeugung und Zuweisung des Records. Ja, was geschiht denn hier nun im Detail? Zunächst wird der Recordtype definiert, damit wir später eine Instanz des Records bilden können. Als nächstes wird mit dem bereits bekannten File of Type eine Dateireferenz vom Typ unseres Record erzeugt (File of TSpielstand). Die nächsten paar Zeilen sollten keiner Erläuterung bedürftigen. Nach der Inititalisierung des Records mit Werten kommt nun aber die bereits kennengelernte Befehl Write, der einen Datensatz in eine Typisierte Datei schreibt. In unserem Fall wird unser Record in die Datei geschrieben. Letzten Endes wird die Datei geschlossen und gespeichert. Alles in allem garnicht so schwer, wenn man sich vor Augen hält, was man damit alles realisieren kann! 2.2.3 ...und die Brille putzen. ~~~~~~~~~~~~~~~~~~~~~~~~~ Na? Und, schon die erste Adressdatenbank geschrieben? Nein? Achso... ihr könnt die Daten ja noch garnicht lesen! Keine Angst, das habe ich nicht vergessen. Das lesen unterscheidet sich genauso vom lesen aus Textfiles, wie es das schreiben getan hat. Na... kommt ihr drauf, wie der Befehl heißt? Genau: Read. So sollte das in etwas aussehen: ---- //Typieisrte Dateireferenz auf eine Integer-Datei erzeugen var Datei: File of Integer; //In diese Variable werden wir aus der Datei lesen Wert,IOBuffer: Integer; begin //Dateireferenz zuweisen AssignFile(Datei,'C:\Integerdatei.dat'); //Fehlerüberprüfung deaktivieren {$I-} //Datei öffnen ReSet(Datei); //Feherlcode speichern IOBuffer := IOResult; //Fehlerüberprüfung wieder aktivieren {$I+} If IOBuffer <> 0 then begin ShowMessage('Fehler beim Laden der Datei!'); Exit; end; //Wir lesen aus der Datei Read(Datei,Wert); //Die Datei wieder schließen CloseFile(Datei); end; ---- Im Prinzip wieder das gleiche. Zunächst werden die nötigen Variablen angelegt, dann die Dateireferenz gesetzt und die Fehlerüberprüfung deaktiviert. Als nächstes wird die Datei mit derm Befehl Reset geöffnet und der Fehlercode in der Variable IOBuffer gespeichert. Falls ein Fehler aufgetreten ist (IOBuffer <> o) dann wird eine Nachricht ausgegeben und die Prozedir verlassen. Ansonsten wird nun aus der Datei gelesen. Es wird der Befehl Read benutzt, der genau einen Datensatz (in unserem Fall einen Integer) aus der Datei in die übergebene Variable schreibt. Als letztes wird die Datei wieder geschlossen. Ist doch eingentlich garnicht so viel anders. Mit Records sieht es genauso aus, deshalb werde ich darauf nicht noch detailierter eingehen. Ihr wollt es trotzdem? Nagut, dann sollt ihr wenigstens den Quelltext bekommen: ---- //Unseren Record definieren type TSpielStand = record SpielerName: String[20]; Lebensenergie: Byte; Mana: Byte; Level: Byte; StarkeRuestung: Boolean; Begleittier: Boolean; end; //Typieisrte Dateireferenz auf eine Integer-Datei erzeugen var Datei: File of Integer; //In diese Variable werden wir aus der Datei lesen Wert: TSpielstand; //Variable um den Fehlercode zu speichern IOBuffer: Integer; begin //Dateireferenz zuweisen AssignFile(Datei,'C:\Integerdatei.dat'); //Fehlerüberprüfung deaktivieren {$I-} //Datei öffnen ReSet(Datei); //Feherlcode speichern IOBuffer := IOResult; //Fehlerüberprüfung wieder aktivieren {$I+} If IOBuffer <> 0 then begin ShowMessage('Fehler beim Laden der Datei!'); Exit; end; //Wir lesen aus der Datei Read(Datei,Wert); //Die Datei wieder schließen CloseFile(Datei); end; ---- So... dann hätten wir auch dieses Kapitel abeschlossen. Das nächste folgt sogleich. Es wird euch beschreiben, wie ihr etwas unkontrollierter auf Dateien zugreifen könnt. Man könnte auch fast sagen etwas gröber oder ungefilterter. Damit stehen euch dann alle Türen zu Dateien offen. Anmerkung: Ich will hier noch auf eine weitere nützliche Funktion in Verbindung mit Typisierten Dateien aufmerksam machen: Seek. Mit Seek könnt ihr innerhalb einer Typisierten Datei einen bestimmten Offset (Punkt relativ zum Ursprung) ansteuern. Nehmen wir an, ihr hättet 10 Spielstände in der Datei gespeichert und wolltet den 5. Laden, dann müsstet ihr mit dem Befehl Seek(Datei,4) den 5. Datensatz ansteuern (es wird von 0 ab gezählt). 2.3.0 Untypisierte Dateien ~~~~~~~~~~~~~~~~~~~~ Untypisierte Dateien stellen einen Low-Level Zugriff auf Dateien dar. Es werden nicht auf Datentypen beschränkte Datensätze gelesen, sondern es werden in Blöcke gegliederte Datenblöcke mit bestimmter Blockgröße gelesen und geschrieben. Das ermöglicht zB Daten von verschiedenen Datentypen in eine Datei zu schreiben und zu lesen. 2.3.1 Ungespitzt schreiben... ~~~~~~~~~~~~~~~~~~~~~~~ Zunächst wollen wir in eine Untypisierte Datei schreiben. Wir machen es wie immer zu Beginn einfach und schreiben einen kleinen Integer in die Datei. Wir machen also nichts anderes als das was wir bei den Typisierten Dateien gemach haben, nur auf einem anderen Weg. Wie bereits erwähnt wird in Datenblöcken gelesen und geschrieben und daher hat der Schreib-Befehl auch seinen Wortlaut: BlockWrite Mit dem Befehl können wir einen Datenbuffer eines beliebigen Typs in eine Datei schreiben. Aber wir schauen uns das am besten ersteinmal an: ---- //Eine untypisierte Dateireferenz deklarieren var Datei: File; //Eine Variable die wir schreiben wollen Wert : Integer; begin //Altbekannte Refernzzuweisung AssignFile(Datei,'Integerdatei.dat'); //Das ebenso bekannte erstellen der Datei ReWrite(Datei); //Dem Integer einen Wert zuweisen Wert := 23; //Den Buffer "Wert" komplett in "Datei" schreiben BlockWrite(Datei,Wert,sizeOf(Integer)); //Datei schließen und speichern CloseFile(Datei); end; ---- Dieser Code unterscheided sich nun im Gegensatz zu den anderen wieder stärker. Zunächst einmal finden wir bei der Dekleration der Dateireferenz keinen Datentypen mehr. Die signalisiert, dass es sich um eine untypisierte Datei- referenz handelt. Die nächsten paar Zeilen sollten hingegen wieder verständlich sein. Der nächste Part, der nicht für alle offensichtlich sein sollte ist der BlockWrite Befehl. Zunächsteinmal der Funktionsprototyp: procedure BlockWrite(var f: File; var Buf; Count: Integer); Der Prototyp stimmt so nicht ganz, es wurde darauf verzichtet einen optionalen Parameter anzugeben, um es übersichtlicher zu machen. Man kann den Prototypen natürlich auch in der Online Hilfe Delphi's nachschlagen. Zunächsteinmal wollen wir aber klären, was die einzelnen Parameter zu bedeuten haben. Der Parameter F vom Typ File bezeichnet die Dateireferenz (in unserem Quelltext oben die Variable Datei). Der refernzierte Parameter Buf bezeichnet einen Block von Daten, die in die Datei geschrieben werden sollen und Count gibt an wieviele Bytes von Daten aus dem Block in die Datei geschrieben werden sollen. Das ganze auf unseren Quelltext übertragen bedeutet das: Es werden sizeOf(Integer) Bytes ( ein Integer hat auf einem 32 Bit System 4 Byte größe) aus dem DatenBlock Wert (vom Typ Integer) in die Datei der Refernz geschrieben. Ich werde auf ein weiterführendes Beispiel hier verzichten, denn wie eine weitere Behandlung aussieht, sollte durch die Inbetrachtziehung der anderen Beispiele klar sein. 2.3.2 ...und ohne Brille lesen. ~~~~~~~~~~~~~~~~~~~~~~~~~ Das Low-Level lesen unterscheidet sich nur geringfügig vom schreiben, wenn man die Fehlerbehandlung außer betracht zieht (auch wenn diese in den Schreibe-Methoden durchaus auch ihren Sinn erfüllen könnte). Wenn man es genau nimmt unterscheided sich (abgesehen von der Art, wie die Datei zu öffnen ist) nur der Befehlslaut. Sogar die Parameter bleiben die gleichen. Der Befehl zum untypisierten lesen lautet BlockRead und besitzt zu 100% den selben Prototypen wie BlockWrite. Trotz der nur geringfügigen Unterschiede zwischen Lese- und Schreibroutine will ich hier nicht auf ein Beispiel verzichten: ---- //Eine untypisierte Dateireferenz deklarieren var Datei: File; //Eine Variable die wir schreiben wollen Wert, IOBuffer : Integer; begin //Altbekannte Refernzzuweisung AssignFile(Datei,'Integerdatei.dat'); {$I-} //Das ebenso bekannte öffnen der Datei ReSet(Datei); //Fehlercode speichern IOBuffer := IOResult; {$I+} If IOBuffer <> 0 then begin ShowMessage('Fehler beim Laden der Datei!'); break; end; //Aus der Datei in Wert lesen BlockRead(Datei,Wert,sizeOf(Integer)); //Datei schließen und speichern CloseFile(Datei); end; ---- Der einzige Unterschied hier ist wirklich nur das Öffnen (mit Reset und nicht mit ReWrite) und der Befehl zum lesen (BlockRead und nicht wie beim schreiben BlockWrite). Auch hier verzichte ich auf ein komplexeres Beispiel. Ihr solltet selbst in der Lage sein dieses zu Entwickeln. 2.4 (Fast) Das Ende ~~~~~~~~~~~~~~~ Endlich :-) Ihr habt's durch. War ziehmlich lang, ich weiß, aber ich hoffe, es hat sich gelohnt. Ursprünglich standen an dieser Stelle die ersten Zeilen, die euch erklären sollen, wie man mit den Streams von Delphi umgeht, aber das sprengte dann doch den Umfang des Tutorials. Deswegen werdet ihr dies zu einem späteren Zeitpunkt un einem anderen Tutorial lernen können. 3.0 Anhang ~~~~~~ Falls ihr noch Fragen habt bezüglich meines Tutorials oder Fehler findet, mailt mir: iparanoid@gmx.de Ein paar Foren an denen ihr mich antreffen könnt: -http://www.root-shell-club.com -http://www.delphi-source.de/interakiv/forum.shtml -http://ip-web.hn.org/board Ihr findet dieses Tutorial unter http://ip-web.hn.org/filez/fileztut.txt als Textfile zum offline lesen. 3.1 Weitergabe- und Verarbeitungsbedingungen ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Da ich ein Freund von OpenSource bin, könnt ihr dieses Dokument nach Belieben weitergeben, vervielfältigen oder auch einzelne Textstücke herauskopieren, so lange mein Name (ip) und meine eMail Adresse (iparanoid@gmx.de) angegeben werden! Achso... und dieses Dokument darf unter keinen Umständen auf irgendeine Art kommerziell vertrieben werden! 3.2 THX & Greetz ~~~~~~~~~~~~ Langsam wird es schwierig irgendjemanden zu finden, bei dem man sich bedanken kann, da ich meine Tutorials in letzter Zeit absolut alleine schreibe :) Naja.. dann geht mein Dank eben nochmal an Phillip Frenzel, der mich dazu animierte, mein erstes Tutorial zu schreiben. Dann will ich noch das ganze Superstition Team grüßen ( http://www.superstition-projekt.de.vu ), dass mit in den letzten paar Wochen viel Freude (und Arbeit :) ) bereitet hat! Und natürlich noch Danke an all die Leute, die meine Tutorials gelesen haben und von denen ich so viele eMails bekommen habe! Spezieller Dank geht dann noch an Glückspils, der dieses Tut Kontrolle uind Probegelesen hat!