Entwickler-Ecke

Dateizugriff - Verschiedene Typen in einer Datei speichern


catweasel - Do 10.01.13 21:25
Titel: Verschiedene Typen in einer Datei speichern
Hi,

Ich habe folgendes Problem:

Ich möchte verschieden Datentypen in einer Datei speichern.
Bevor jetzt aber Vorshläge zum Thema "Records" kommen: Das nützt mir nichts.

Nehmen wir mal folgende Typen:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
type
  TData=record
    Foo : integer;
    Bar : integer;
  end;

type
  TDataMap = array of array of TData;

type
  TDataFile = file of TData;


type
  TDataClas=class(TObject)
  private
    FData : TDataMap;
    FMapSize : TPoint;
    FMapName : string;
  public
    procedure LoadFromFile(filename:string);
    procedure SaveToFile(filename:string);
  end;

Ich könnte nun ein TDataFile abspeihern und tricksen indem ich die Grösse des Arrays in einem "Header record" packe und zuerst abspeichere. Die Datei hätte dann einen Record mehr als DataMap Elemente hat, aber das würde funktionieren. Aber wie kann ich nun den Daten und den Namen in einer Datei abspeichern?
In diesem Fall kann ich kein Datenfeld als Header missbrauchen (Foo und Bar sind beides Integer). Nur wegen dem Namen ein Datenfeld hinzuzufügen ist "schlecht" weil ich den Namen ja nur einmal pro Datei habe und nicht für jedes Datenfeld. Ich kann auch keinen Record definieren der Daten und den Namen enthält und dann einen Record abspeichern, da TDataMap keine feste Länge hat und daher tabu ist.

Wie speichert man gemischte Daten :?:

Wäre Dankbar für etwas Aufklärung :roll:

Cheers,
Catweasel


rushifell - Do 10.01.13 21:45

So ganz hab ich Dein Problem nicht verstanden. Hier eine Möglichkeit:

Quelltext
1:
2:
3:
4:
5:
Datentyp (Konstante Länge, z.B. 1 Byte)
Länge Beschreibung (Konstante Länge, z.B. 2 Bytes)
Beschreibung (variable Länge)
Länge der Daten (Konstante Länge, z.B. 4 Bytes)
Daten (variable Länge)

Je nach Datentyp kannst Du den Aufbau der Folgedaten anders gestalten. Prüfsummen zum Schützen vor Manipulation sind auch denkbar.

Gruß


catweasel - Do 10.01.13 22:31

user profile iconrushifell hat folgendes geschrieben Zum zitierten Posting springen:
So ganz hab ich Dein Problem nicht verstanden. Hier eine Möglichkeit:

Quelltext
1:
2:
3:
4:
5:
Datentyp (Konstante Länge, z.B. 1 Byte)
Länge Beschreibung (Konstante Länge, z.B. 2 Bytes)
Beschreibung (variable Länge)
Länge der Daten (Konstante Länge, z.B. 4 Bytes)
Daten (variable Länge)

Je nach Datentyp kannst Du den Aufbau der Folgedaten anders gestalten. Prüfsummen zum Schützen vor Manipulation sind auch denkbar.

Gruß


Hi,

Hmm, ich hab deine Antwort nicht verstanden.
Wie würde das denn praktisch aussehen wenn ich einen String einen Integer und ein dynamisches array von records speichern will?

Sähe die Datei dann so aus?
(Je ein Byte pro Zeile)

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
42        // 42 steht für den typ "string"
6         // Wenn der String z.B. 'Delphi' lautet 
D         // Die Daten des Strings
e
l
p
h
i

Was meinst du mit "Beschreibung"?

Das läuft also darauf hinaus eine Datei byteweise zusammen und auseinander zu pfriemeln und Datentyoen wie integer, extended und andere mehrbyte Typen aus einzelnen gelesenen Bytes schneidern?
Oder hab ich da was falsch verstanden?

Cheers,
Catweasel


rushifell - Do 10.01.13 22:46

catweasel hat folgendes geschrieben:
Sähe die Datei dann so aus?

Im Grunde ja. Mit Datentyp meinte ich jedoch den Aufbau, der Folgedaten. Sorry, hab mich da etwas ungünstig ausgedrückt.

catweasel hat folgendes geschrieben:

Wie würde das denn praktisch aussehen wenn ich einen String einen Integer und ein dynamisches array von records speichern will?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
Var w:Word;
    S:String;
    i:Integer;
    LengthDynArray:Cardinal;
...
Stream.Read(w,Sizeof(Word); //Gibt länge des Strings zurück
Stream.Read(S,w);
ShowMessage(S); //String anzeigen

Stream.Read(i,4); //32 Bit Integer

ShowMessage(IntToStr(i)); //Integer anzeigen

Stream.Read(LengthDynArray,4);
Stream.Read(DynArray,LengthDynArray);


Nur vom Prinzip her.


jaenicke - Do 10.01.13 22:48

Wobei ich einfach eine Klasse nehmen würde, dieser die Methoden LoadFromStream und SaveToStream, dazu noch eine Klasse für die gesamte Datei, ebenfalls mit diesen Methoden und dazu LoadFromFile und SaveToFile. Dann ist das alles wunderbar gekapselt. Ein Beispiel müsste ich mal schreiben...


Lemmy - Do 10.01.13 23:01

Hallo,

was spricht eigentlich gegen eine XML Datei?

Grüße


catweasel - Do 10.01.13 23:09

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Wobei ich einfach eine Klasse nehmen würde, dieser die Methoden LoadFromStream und SaveToStream, dazu noch eine Klasse für die gesamte Datei, ebenfalls mit diesen Methoden und dazu LoadFromFile und SaveToFile. Dann ist das alles wunderbar gekapselt. Ein Beispiel müsste ich mal schreiben...


Hab ich doch vor.
Guck mal die TDataClass Definition am Threadanfang an :)

Cheers,
Catweasel


catweasel - Do 10.01.13 23:17

user profile iconrushifell hat folgendes geschrieben Zum zitierten Posting springen:
catweasel hat folgendes geschrieben:
Sähe die Datei dann so aus?

Im Grunde ja. Mit Datentyp meinte ich jedoch den Aufbau, der Folgedaten. Sorry, hab mich da etwas ungünstig ausgedrückt.

catweasel hat folgendes geschrieben:

Wie würde das denn praktisch aussehen wenn ich einen String einen Integer und ein dynamisches array von records speichern will?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
Var w:Word;
    S:String;
    i:Integer;
    LengthDynArray:Cardinal;
...
Stream.Read(w,Sizeof(Word); //Gibt länge des Strings zurück
Stream.Read(S,w);
ShowMessage(S); //String anzeigen

Stream.Read(i,4); //32 Bit Integer

ShowMessage(IntToStr(i)); //Integer anzeigen

Stream.Read(LengthDynArray,4);
Stream.Read(DynArray,LengthDynArray);


Nur vom Prinzip her.


Ah ok. Mit FileStreams hatte ich bisher kaum gearbeitet. Immer nur mit typisierten Dateien (im rudimentärsten Fall vom Typ Byte). Eine Frage habe ich aber noch zu:

Delphi-Quelltext
1:
2:
Stream.Read(LengthDynArray,4);
Stream.Read(DynArray,LengthDynArray);

Erst wird die Länge als 4 Byte Cardinal eingelesen. Das check ich noch
Aber muss ich da nicht dann noch ein Setlength(DynArray,LengthDynArray); einfügen bevor ich die Daten lese?

Ich werde mich auf jeden Fall mal mit Streams auseinandersetzen.
Danke für den Tip :)

Cheers,
Catweasel


Lannes - Fr 11.01.13 01:07

Hallo,


user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:

Ich werde mich auf jeden Fall mal mit Streams auseinandersetzen.


dann darf ich dir das gute Tutorial von user profile iconBenBE empfehlen:

Wie man Strings speichert und liest ... [http://www.entwickler-ecke.de/viewtopic.php?t=67955]


rushifell - Fr 11.01.13 18:57

Für XML gibts doch bestimmt gute Libraries!?

catweasel hat folgendes geschrieben:
Aber muss ich da nicht dann noch ein Setlength(DynArray,LengthDynArray); einfügen bevor ich die Daten lese?


Ich nutze immer einen Buffer mit konstanter Größe, lese vom Stream in den Buffer und übergebe dann die Daten an das Dynamische Array. Sollte wahrscheinlich auch direkt gehen, hatte aber damit schon öfter Probleme.

Bei den Arrays musst Du unbedingt auf den Typ achten:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
Var ByteArray:Array[1..2of Byte;
    IntegerArray:Array[1..2of Integer;
begin
ShowMessage(InttoStr(SizeOf(ByteArray)));    //2*1 Byte, also 2 Bytes lesen/schreiben
ShowMessage(InttoStr(SizeOf(IntegerArray))); //2*4 Bytes, also 8 Bytes lesen/schreiben
end;


Beim Schreiben und Lesen von Strings solltest Du Dir Gedanken machen, ob Du Unicode unterstützen möchtest (würde ich generell empfehlen).

Ich nutze übrigens gerne die TFastFileStream-Klasse von Flamefire:
http://www.entwickler-ecke.de/viewtopic.php?t=100088

Gruß


jaenicke - Fr 11.01.13 21:25

Mit einer MMF wie sie auch in der Klasse verwendet wird kann man auch direkt als Array auf die Daten zugreifen. Da Windows diese automatisch in den RAM spiegelt, geht das extrem schnell.

Das Prinzip siehst du in dieser Unit von mir sehr gut, bei der das ganze Stream drum herum noch nicht drin ist. Deshalb ist die zum Verständnis vermutlich besser.
http://www.entwickler-ecke.de/viewtopic.php?p=607865


catweasel - Sa 12.01.13 12:36

user profile iconrushifell hat folgendes geschrieben Zum zitierten Posting springen:

Ich nutze immer einen Buffer mit konstanter Größe, lese vom Stream in den Buffer und übergebe dann die Daten an das Dynamische Array. Sollte wahrscheinlich auch direkt gehen, hatte aber damit schon öfter Probleme.

Gruß


Hi,

Das mit den FileStreams hab ich das jetzt geblickt :)
Aber ein Problem hab ich noch:

Ich habe eine Klasse (TBaseClass) geschrieben welche zwei Felder einführt: "Name" und "Description" (beides Strings).
Davon abgeleitet ist TChildClassA, welche zwei zusätzliche Felder "Index" (Int64) und "Color" (TColor) einführt.

Die TBaseClass.LoadFromFile bzw. SaveToFile funktioniert ganz gut wenn ich den FileStream nach dem Lesen/Schreiben in der Prozedur wieder freigebe. (Ansonsten bleibt die Datei ja "in Verwendung").

Jetzt möchte ich TChildClassA auch LoadFromFile/SaveToFile Prozeduren geben. Dazu möchte ich aber die "Lese/Schreib-arbeit" für die Felder von TBaseClass nicht duplizieren.
Ich hatte mir gedacht ich ruf einfach die Parent Prozedur über inherited auf, speichere die zusätzlichen Felder hintendrann. Ob nun TBaseClass oder TChildClassA den "Sack zu" machen muss wollte ich per: if self is TChildClassA then FDiskFile.Free; testen.
Ich bekomme aber nur Fehler. Keine AccessViolation, sondern eine "External Exception". :shock: Einzelschritt debugging deutet an das das Freigeben des Streams irgendwie scheitert. Aber nur in TChildClassA....

Ich hab mal das Projekt hier eigefügt.
Wenn man das FDiskFile.Free in TBaseClass nicht auskommentiert, dann funktionierts damit. TBaseClass Objekte lassen sich dann lesen und speichern.

Wo liegt der Fehler :?:

Cheers,
Catweasel


jfheins - Sa 12.01.13 13:51

In der VCL gibt es (fast) immer eine SaveToFile Methode und eine SaveToStream Methode.
Mache es doch genau so: Die SaveToFile öffnet einen Filestream, ruft die andere Methode auf und schließt diesen wieder.
Die SaveToStream Methode kannst du dann überschreiben und die alte mit inherited aufrufen ;-)


catweasel - Sa 12.01.13 13:57

user profile iconjfheins hat folgendes geschrieben Zum zitierten Posting springen:
In der VCL gibt es (fast) immer eine SaveToFile Methode und eine SaveToStream Methode.
Mache es doch genau so: Die SaveToFile öffnet einen Filestream, ruft die andere Methode auf und schließt diesen wieder.
Die SaveToStream Methode kannst du dann überschreiben und die alte mit inherited aufrufen ;-)


Hmmm....Exakt das habe ich versucht und genau das Schliessen klappt nicht.
Wo liegt der Fehler in meinem Quelltext?

Cheers,
Catweasel


jfheins - Sa 12.01.13 14:24

user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:

Hmmm....Exakt das habe ich versucht und genau das Schliessen klappt nicht.
Wo liegt der Fehler in meinem Quelltext?


Du machst es eben nicht exakt wie ich es vorgeschlagen hatte ;-) (SaveToStream <> SaveToFile)

Ich finde da nur:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
procedure TBaseClass.SaveToFile(filename: string);
var
Len : integer;
begin
FDiskFile := TFileStream.Create(filename,fmCreate);
Len := Length(FName);
FDiskFile.Write(Len,SizeOf(Len));
FDiskFile.Write(PChar(FName)^,Len);
Len := Length(FDescription);
FDiskFile.Write(Len,SizeOf(Len));
FDiskFile.Write(PChar(FDescription)^,Len);
if self is TBaseClass then FDiskFile.Free;
//FDiskFile.Free;
end;


Ich habe das hier gemeint:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
procedure TBaseClass.SaveToStream(AStream: TStream); // Natürlich virtual damit man überschreiben kann
var
Len : integer;
begin
Len := Length(FName);
AStream.Write(Len,SizeOf(Len));
AStream.Write(PChar(FName)^,Len);
Len := Length(FDescription);
AStream.Write(Len,SizeOf(Len));
AStream.Write(PChar(FDescription)^,Len);
end;

procedure TBaseClass.SaveToFile(filename: string);
begin
FDiskFile := TFileStream.Create(filename,fmCreate);
SaveToStream(FDiskFile);
FDiskFile.Free;
end;

Die SaveToStream-Methode kannst du dann in der Kind-Klasse überschrieben und aufrufen.


catweasel - Sa 12.01.13 14:44

user profile iconjfheins hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:

Hmmm....Exakt das habe ich versucht und genau das Schliessen klappt nicht.
Wo liegt der Fehler in meinem Quelltext?


Du machst es eben nicht exakt wie ich es vorgeschlagen hatte ;-) (SaveToStream <> SaveToFile)

Ich finde da nur:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
procedure TBaseClass.SaveToFile(filename: string);
var
Len : integer;
begin
FDiskFile := TFileStream.Create(filename,fmCreate);
Len := Length(FName);
FDiskFile.Write(Len,SizeOf(Len));
FDiskFile.Write(PChar(FName)^,Len);
Len := Length(FDescription);
FDiskFile.Write(Len,SizeOf(Len));
FDiskFile.Write(PChar(FDescription)^,Len);
if self is TBaseClass then FDiskFile.Free;
//FDiskFile.Free;
end;


Ich habe das hier gemeint:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
procedure TBaseClass.SaveToStream(AStream: TStream); // Natürlich virtual damit man überschreiben kann
var
Len : integer;
begin
Len := Length(FName);
AStream.Write(Len,SizeOf(Len));
AStream.Write(PChar(FName)^,Len);
Len := Length(FDescription);
AStream.Write(Len,SizeOf(Len));
AStream.Write(PChar(FDescription)^,Len);
end;

procedure TBaseClass.SaveToFile(filename: string);
begin
FDiskFile := TFileStream.Create(filename,fmCreate);
SaveToStream(FDiskFile);
FDiskFile.Free;
end;

Die SaveToStream-Methode kannst du dann in der Kind-Klasse überschrieben und aufrufen.



Ah Ok :)
Ich werd das heute Abend mal testen.
Hab ich das richtig verstanden: Die SaveToFile Prozedur ist nur in der BAsisKlasse implementiert und die jeweilige KindKlasse ruft als erstes inherited SaveToStream(AStream) auf?
Warum muss AStream hier nicht als var Parameter übergeben werden?
So etwa: procedure TBaseClass.SaveToStream(var AStream:TStream);

Und dann könnte FDiskFile in der SaveToFile Prozedur auch eine lokale Variable sein. Dann brauche ich gar kein FileStream als Klassenfeld?
Mit dem Überschreiben von Prozeduren hab ich noch nicht so viel Erfahrung (mach ich sonst kaum).
Danke für die Tips :)

Cheers,
Catweasel


WasWeißDennIch - Sa 12.01.13 14:48

Objektinstanzen muss man i.d.R. nicht als Var-Parameter übergeben, da es sich intern um Pointer handelt. Und in den allermeisten Fällen möchte man ja nicht die Instanz an sich ändern, sondern deren Properties.


catweasel - So 13.01.13 14:07

Hi,

vielen Dank an Alle die mit ihren tollen Tips geholfen haben. Echt Super :)
Schaut euch aber nch mal den Sourcecode an und sagt mir ob es da noch formal etwas zu verbessern gibt.
Irgendein Feld oder eine Methode die öffentlicher ist als nötig?

Cheers,
Catweasel


Keldorn - So 13.01.13 19:07

Hallo

würde das ganze noch um die TWriter und TReader ergänzen


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
procedure TBaseClass.SaveToStream(AStream: TStream); // Natürlich virtual damit man überschreiben kann
Var Writer:Twriter;
  begin
    writer:= Twriter.create(AStream,4096);
    try
      begin
        //version schreiben
        writer.writeInteger(aktuelleVersionsNummer);
        writer.writeString(...);
        writer.writeBoolean(...);
        writer.writefloat(...);
        writer.writeDate(...);       
      end;
    finally
      writer.free;
    end;
end;

auslesen mit Treader genau andersrum.
Brauchst halt nicht für jeden Datentyp dir extra was einfallen lassen, das ist schon da.
Außerdem ließt es sich meiner Meinung nach besser, da es alles Einzeiler sind. Es wird wahrscheinlich auch zukunftssicherer sein, grade, wenn Du strings schreibst.

Gruß Frank


catweasel - So 13.01.13 19:36

user profile iconKeldorn hat folgendes geschrieben Zum zitierten Posting springen:
Hallo

würde das ganze noch um die TWriter und TReader ergänzen


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
procedure TBaseClass.SaveToStream(AStream: TStream); // Natürlich virtual damit man überschreiben kann
Var Writer:Twriter;
  begin
    writer:= Twriter.create(AStream,4096);
    try
      begin
        //version schreiben
        writer.writeInteger(aktuelleVersionsNummer);
        writer.writeString(...);
        writer.writeBoolean(...);
        writer.writefloat(...);
        writer.writeDate(...);       
      end;
    finally
      writer.free;
    end;
end;

auslesen mit Treader genau andersrum.
Brauchst halt nicht für jeden Datentyp dir extra was einfallen lassen, das ist schon da.
Außerdem ließt es sich meiner Meinung nach besser, da es alles Einzeiler sind. Es wird wahrscheinlich auch zukunftssicherer sein, grade, wenn Du strings schreibst.

Gruß Frank


Wow. TReader/TWriter kannte ich auch noch nicht. Ich hab da mal ne Frage zu. Als ich auf der Embacaero Website mir ein paar Sachen dazu durchgelesen habe: http://docwiki.embarcadero.com/Libraries/XE2/en/System.Classes.TWriter bin ich etwas verwirrt. Dort steht das man TReader/TWriter nicht direkt erzeugen soll weil das sowieso schon Bestandteil der TSTream Klasse ist.

Hab ich das was falsch verstanden? Ich finde uach kaum "Tutorials" zu TREader/TWriter...
Und was ist die Obergrenze für die Buffergröße?. Hab dazu auch nichts gefunden.

Cheers,
Catweasel


Martok - So 13.01.13 20:23

user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:
Als ich auf der Embacaero Website mir ein paar Sachen dazu durchgelesen habe: http://docwiki.embarcadero.com/Libraries/XE2/en/System.Classes.TWriter bin ich etwas verwirrt. Dort steht das man TReader/TWriter nicht direkt erzeugen soll weil das sowieso schon Bestandteil der TSTream Klasse ist.
Und bei den passenden TStream-Funktionen steht dann, dass man die auch nicht nutzen soll. Halte ich für Blödsinn; das steht aber allgemein bei vielem dran, was auch nur entfernt mit Component-Streaming zu tun hat.

Ein Hinweis noch: TReader/Writer verwenden zusätzlich zu den Daten jeweils noch eine 1-Byte-Kennung, die den Typ der Daten angibt. Damit hast du gleich eine Art Konsistenzprüfung, brauchst aber auch mehr Platz: WriteInteger() schreibt eben nicht 4 Byte, sondern 5.

user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:
Und was ist die Obergrenze für die Buffergröße?. Hab dazu auch nichts gefunden.
Bis der RAM voll ist ;-)
Sinnvollerweise sollte man das an 4K-Schritten ausrichten, aber kommt natürlich auch drauf an wie viel du schreiben willst. Wenn deine ganze Datei nachher 10MByte groß wird lohnt sich ein großer Buffer (Windows nimmt gern 64k) eher als wenn die Datei sowieso nur 100 Byte wird.


catweasel - Sa 19.01.13 00:31

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:
Als ich auf der Embacaero Website mir ein paar Sachen dazu durchgelesen habe: http://docwiki.embarcadero.com/Libraries/XE2/en/System.Classes.TWriter bin ich etwas verwirrt. Dort steht das man TReader/TWriter nicht direkt erzeugen soll weil das sowieso schon Bestandteil der TSTream Klasse ist.
Und bei den passenden TStream-Funktionen steht dann, dass man die auch nicht nutzen soll. Halte ich für Blödsinn; das steht aber allgemein bei vielem dran, was auch nur entfernt mit Component-Streaming zu tun hat.

Ein Hinweis noch: TReader/Writer verwenden zusätzlich zu den Daten jeweils noch eine 1-Byte-Kennung, die den Typ der Daten angibt. Damit hast du gleich eine Art Konsistenzprüfung, brauchst aber auch mehr Platz: WriteInteger() schreibt eben nicht 4 Byte, sondern 5.

user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:
Und was ist die Obergrenze für die Buffergröße?. Hab dazu auch nichts gefunden.
Bis der RAM voll ist ;-)
Sinnvollerweise sollte man das an 4K-Schritten ausrichten, aber kommt natürlich auch drauf an wie viel du schreiben willst. Wenn deine ganze Datei nachher 10MByte groß wird lohnt sich ein großer Buffer (Windows nimmt gern 64k) eher als wenn die Datei sowieso nur 100 Byte wird.


Also nachdem das mit den ersten Gehversuchen mit Streams ja ganz gut geklappt hat habe ich versucht das in mein aktuelles Projekt einfliessen zu lassen.

Und schon gibts Probleme. :? Es soll eine Liste von Objekten gelesen/gespeichert werden.

Ich habe den Quellcode mal beigefügt. Das Problem sieht so aus:
-Nur der erste "Record" wird gelesen, und dort auch nicht alle Felder.
-Alle weiteren Records sind leer (bis auf die Defaultwerte.

Es gibt keine Access Violations oder andere Fehler.
Die Anzahl der Records stimmt, nur wo sind die Daten :?:

Ich hab den Programmablauf mitverfolgt. Da scheint irgendwie nichts zu fehlen. Trotzdem wird nix geladen/gespeichert.

Wäre echt toll wenn mir da jemand noch einmal weiterhelfen könnte. :eyes:


Cheers,
Catweasel

PS: Nicht vom Arbeitstitel stören lassen. Ich bastel an einem Spiel im Stil von Civilization. Bis ich fertig bin wird mir sicher noch ein besserer Titel einfallen :wink:


Keldorn - Sa 19.01.13 10:23

Hallo,

kurz angeschaut, fang erstmal bei den Stringlisten an.

kurzes Demo hingeklatscht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
Var stream:Tstream;
    sl:Tstrings;
begin
  stream:=TFileStream.Create('D:\test.dat',fmcreate);
  sl:=TStringList.Create;
  sl.Add('eintrag 1');
  sl.Add('eintrag 2');
  sl.SaveToStream(stream);
  sl.Clear;
  sl.Add('eintrag Neu');
  sl.SaveToStream(stream);
  stream.free;
  sl.Free;

  stream:=TFileStream.Create('D:\test.dat',fmOpenRead);
  sl:=TStringList.Create;
  sl.LoadFromStream(stream);
  Memo1.Lines.Assign(sl);
  Memo1.Lines.Add('=========');
  sl.LoadFromStream(stream);
  Memo1.Lines.AddStrings(sl);
  sl.Free;
  stream.Free;
end;

==> eintrag neu wird nach dem Laden der 1. stringliste zugeordnet, das 2. loadfromstream führt zu nix.

probier das einfach aus. Das Problem ist sl.loadfromstream gugg dir mal den soruce dazu an. Tstrings.savetostream und loadfromstream ist die Größe egal. Savetostream speichert keine "Größe" der stringlist und loadfromstream liest alles von der aktuellen StreamPosition bis zum ende. Wenn du danach noch was auslesen willst, steht nix mehr im stream drin ;-).

Nimm nicht sl.savetostream sondern speichere z.B. den sl.text als string in den stream.

Gruß Frank


catweasel - Sa 19.01.13 12:58

user profile iconKeldorn hat folgendes geschrieben Zum zitierten Posting springen:
Hallo,

kurz angeschaut, fang erstmal bei den Stringlisten an.

kurzes Demo hingeklatscht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
Var stream:Tstream;
    sl:Tstrings;
begin
  stream:=TFileStream.Create('D:\test.dat',fmcreate);
  sl:=TStringList.Create;
  sl.Add('eintrag 1');
  sl.Add('eintrag 2');
  sl.SaveToStream(stream);
  sl.Clear;
  sl.Add('eintrag Neu');
  sl.SaveToStream(stream);
  stream.free;
  sl.Free;

  stream:=TFileStream.Create('D:\test.dat',fmOpenRead);
  sl:=TStringList.Create;
  sl.LoadFromStream(stream);
  Memo1.Lines.Assign(sl);
  Memo1.Lines.Add('=========');
  sl.LoadFromStream(stream);
  Memo1.Lines.AddStrings(sl);
  sl.Free;
  stream.Free;
end;

==> eintrag neu wird nach dem Laden der 1. stringliste zugeordnet, das 2. loadfromstream führt zu nix.

probier das einfach aus. Das Problem ist sl.loadfromstream gugg dir mal den soruce dazu an. Tstrings.savetostream und loadfromstream ist die Größe egal. Savetostream speichert keine "Größe" der stringlist und loadfromstream liest alles von der aktuellen StreamPosition bis zum ende. Wenn du danach noch was auslesen willst, steht nix mehr im stream drin ;-).

Nimm nicht sl.savetostream sondern speichere z.B. den sl.text als string in den stream.

Gruß Frank


Danke für den Tip. :) Ich dachte die TStringList.SaveToStream() wäre so bequem das die das alles mit der Grösse und so regelt. Ich habe das jetzt der TNameList Klasse hinzugefügt. Mit der Text Eigenschaft der Stringlist wollte ich jetzt nicht experimentieren da ich da schlechte Erfahrungen mit dem Delimiter gemacht habe.

Nun bekomme ich aber einen anderen Error.. "Bitmap is not valid". :?
Gibt es bei der TBitmap.SaveToStream etwas zu beachten, oder wo liegt der Fehlr jetzt :?:

Cheers,
Catweasel


Keldorn - Sa 19.01.13 13:15

du hast doch die Enterprise, also auch die Sourcen. gugg doch einfach nach ;-).

bitmap.loadfromstream:

Delphi-Quelltext
1:
ReadStream(Stream, Stream.Size - Stream.Position);                    


gleiches Problem.

evtl Abhilfe:
beim Speichern und Laden über einen memorystream gehen.
- memorystream erstellen
- Bitmap in den memstream reinspeichern
- in den richtigen stream die größe des memstreams speichern
- dann den memstream

beim Auslesen dann genau andersrum: nur die entsprechenden Bytes aus stream in den memstream auslesen und die Bitmap dann aus dem Memstream laden

Gruß Frank


catweasel - Sa 19.01.13 13:18

user profile iconKeldorn hat folgendes geschrieben Zum zitierten Posting springen:
du hast doch die Enterprise, also auch die Sourcen. gugg doch einfach nach ;-).

bitmap.loadfromstream:

Delphi-Quelltext
1:
ReadStream(Stream, Stream.Size - Stream.Position);                    


gleiches Problem.

evtl Abhilfe:
beim Speichern und Laden über einen memorystream gehen.
- memorystream erstellen
- Bitmap in den memstream reinspeichern
- in den richtigen stream die größe des memstreams speichern
- dann den memstream

beim Auslesen dann genau andersrum: nur die entsprechenden Bytes aus stream in den memstream auslesen und die Bitmap dann aus dem Memstream laden

Gruß Frank


Werd ich gleich mal testen... Und ich muss mein Profil mal updaten... Ich benutz zur Zeit Delphi 7 Second Edition :)

Cheers,
Catweasel


catweasel - Sa 19.01.13 13:38

Hi,


Hab das jetzt mal versucht. Das sieht so aus:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
procedure TIconItem.LoadFromStream(AStream: TFileStream);
var
MemStream : TMemoryStream;
Size : int64;
begin
  inherited;
MemStream := TMemoryStream.Create;
Size := AStream.Read(Size,SizeOf(Size));
MemStream.Read(AStream,Size);
FIcon.LoadFromStream(Memstream);
MemStream.Free;
end;

procedure TIconItem.SaveToStream(AStream: TFileStream);
var
MemStream : TMemoryStream;
Size : int64;
begin
  inherited;
MemStream := TMemoryStream.Create;
FIcon.SaveToStream(MemStream);
Size := MemStream.Size;
AStream.Write(Size,SizeOf(Size));
AStream.Write(MemStream,Size);
MemStream.Free;
end;


Leider funktioniert es nicht :? Es werden nur wirre Daten eingelesen und das Programm "hängt sich auf" weil einige Listengrössen mit 7777864 eingelesen werden, etc.
Muss ich beim kopieren von Stream zu Stream nochwas beachten :?:

Vielen Dank für die Hilfe

Cheers,
Catweasel


jfheins - Sa 19.01.13 14:00

Hi,
ja kein Wunder dass das nicht klappt :twisted:

Der Memorystrem ist ja ein Objekt, d.h. hier:

Delphi-Quelltext
1:
AStream.Write(MemStream,Size);                    

Schreibst du die Adresse des Objekts und noch ein paar Mülldaten in deinen Stream. Um an die tatsächlichen daten zu kommen hilft dir die Hilfe:
http://docwiki.embarcadero.com/Libraries/XE2/en/System.Classes.TCustomMemoryStream.Memory
also muss der Code irgendwie so lauten:

Delphi-Quelltext
1:
AStream.Write(MemStream.Memory,Size);                    

Ich glaube aber du musst den Pointer dann noch dereferenzieren - einfach mal ausprobieren :wink:


catweasel - Sa 19.01.13 14:21

user profile iconjfheins hat folgendes geschrieben Zum zitierten Posting springen:
Hi,
ja kein Wunder dass das nicht klappt :twisted:

Der Memorystrem ist ja ein Objekt, d.h. hier:

Delphi-Quelltext
1:
AStream.Write(MemStream,Size);                    

Schreibst du die Adresse des Objekts und noch ein paar Mülldaten in deinen Stream. Um an die tatsächlichen daten zu kommen hilft dir die Hilfe:
http://docwiki.embarcadero.com/Libraries/XE2/en/System.Classes.TCustomMemoryStream.Memory
also muss der Code irgendwie so lauten:

Delphi-Quelltext
1:
AStream.Write(MemStream.Memory,Size);                    

Ich glaube aber du musst den Pointer dann noch dereferenzieren - einfach mal ausprobieren :wink:


JA. Ich hatte verdrängt das Objekte "nur" Referenzen sind :oops:
Hmmm. Aber auf was muss ich das dereferenzieren. Ich habe mit Pointern kaum Erfahrung.
TMemoryStream.Memory ist vom Typ Pointer. Aber dem FileStream fehlt das. Wie soll ich dann den Punkt zum lesen referenzieren. Vielleicht habe ich auch nur etwas mit den Pointern nicht verstanden.... Momentan komme ich hier echt nicht weiter :?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
procedure TIconItem.LoadFromStream(AStream: TFileStream);
var
MemStream : TMemoryStream;
p : Pointer;
Size : int64;
begin
  inherited;
MemStream := TMemoryStream.Create;
Size := AStream.Read(Size,SizeOf(Size));
p := AStream.Memory;     // FileStream kennt kein Memory
MemStream.Read(p^,Size);
FIcon.LoadFromStream(Memstream.Memory);
MemStream.Free;
end;

procedure TIconItem.SaveToStream(AStream: TFileStream);
var
MemStream : TMemoryStream;
p : Pointer;
Size : int64;
begin
  inherited;
MemStream := TMemoryStream.Create;
FIcon.SaveToStream(MemStream);
Size := MemStream.Size;
p := MemStream.Memory;
AStream.Write(Size,SizeOf(Size));
AStream.Write(p^,Size);
MemStream.Free;
end;


Echst super Eure Hilfe :D
Ich habe gerade gesehen es gibt eine CopyFrom() Prozedur. Lässt sich damit etwas anfangen?


Cheers,
Catweasel


catweasel - Sa 19.01.13 19:11

Also, ich habe mir jetzt das ganze nochmal durch den Kopf gehen lassen und beschlossen die Pixel eizeln in den Stream zu packen. Das klappt auch ganz gut. Da ich mich mit Zeigern nicht so auskenne kann mir vielleicht noch jemand einen Tip geben. :)

Meine neue Bitmapklasse sieht so aus:
(nur die Streaming Methoden)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
procedure TStreamBitmap.LoadFromStream(AStream: TFileStream);
var
x,y,w,h : longint;
Data : TColor;
begin
AStream.Read(w,SizeOf(w));
Width := w;
AStream.Read(h,SizeOf(h));
Height := h;
for y := 0 to h-1 do
  for x := 0 to w-1 do
    begin
    AStream.Read(Data,SizeOf(Data));
    Canvas.Pixels[x,y] := Data;
    end;
end;

procedure TStreamBitmap.SaveToStream(AStream: TFileStream);
var
x,y,w,h : longint;
Data : TColor;
begin
w := Width;
h := Height;
AStream.Write(w,SizeOf(w));
AStream.Write(h,SizeOf(h));
for y := 0 to h-1 do
  for x := 0 to w-1 do
    begin
    Data := Canvas.Pixels[x,y];
    AStream.Write(Data,SizeOf(Data));
    end;
end;


Es müsste doch jetzt irgendwie möglich sein das per Scanline zu optimieren. Scanline(0) liefert mir doch einen Zeiger auf den ersten Pixel, oder nicht?
Kann ich dann nicht ab da (With*Heigth*BytesProPixel) Bytes lesen/schreiben?
Wenn ich das versuche klapp nichts :?

Cheers,
Catweasel


Keldorn - So 20.01.13 11:02

Hallo,

warum machst du Dir es so kompliziert. Zugriff auf pixel ist eh so grottenlangsam.

such doch auch hier im Forum, wenn Du Beispiele brauchst z.B.:
http://www.entwickler-ecke.de/viewtopic.php?t=29461&highlight=stream+copyfrom&view=df
(Beitrag von Viper)

Gruß Frank