Autor Beitrag
Ghostwalker
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 17

Windows XP Prof.
Turbo Delphi 2006, Delphi 7 Prof.
BeitragVerfasst: Mo 02.07.07 05:27 
Vorwort

Das hier gezeigte Vorgehen, um Objekte zu speichern, ist sicher nicht das Optimum. Aber es ist ein guter Startpunkt für weitere Entwicklungen. Da ich kein großer Redner bin, gehts aber auch gleich los :)

Vorarbeiten

Zuerstmal müssen wir gleich eine Hürde umgehen. Nämlich die, das die beiden Routinen ReadProperty und WriterProperties von TReader bzw. TWriter protected sind. Damit haben wir erstmal keine Chance diese Routinen zu nutzen um unsere Objekte (bzw. deren Eigenschaften) zu speichern.

Aber Delphi wäre nicht Delphi, und OOP nicht OOP, wenn es dafür keine Lösung gäbe. Wir leiten uns einfach eine eigene TReader und TWriter-Klasse ab :)

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
TYPE
  TReaderEx = class(TReader)
  PUBLIC
     procedure LoadProperty(Instance:TPersistent); //Anderer Name um Konflikte zu vermeiden.
  end.
  
  TWriterEx = class(TWriter)
  PUBLIC
    Procedure SaveProperties(Instance:TPersistent); //Anderer Name um Konflikte zu vermeiden.
  end.
  
Procedure TReaderEx.LoadProperty(Instance:TPersistent);
begin
  ReadProperty(instance);
end;  

Procedure TWriterEx.SaveProperties(Instance:TPersisten);
begin
  WriteProperties(Instance);
end;


So..das wars schon................oder doch nicht ?

Nein...nicht ganz. Es würden zwar alle Eigenschaften geschrieben, aber nur eine gelesen werden (kann man ganz gut an der Namensgebung der orginal Prozeduren erkennen). Tja..und wie lösen wir das Problemchen? Nun..einfach mit einem Listchen und einem Schleifchen. :mrgreen: Schließlich braucht der Streamingmechanismus eine Kennung, wo die Eigenschaften eines Objekts aufhören und die des nächsten anfangen. Ein Blick in die Sourcen der VCL gibt die Lösung Preis (was Codegear fabriziert, funktioniert ja auch :) ).

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
Procedure TReaderEx.LoadProperty(Instance:TPersistent);
begin
  While not EndOfList do ReadProperty(instance);
  ReadListEnd;
end;  

Procedure TWriterEx.SaveProperties(Instance:TPersisten);
begin
  WriteProperties(Instance);
  WriteListEnd;
end;


Das wars schon.

Die Funktionen

Da TStream und andere abeleitete Klassen nichts von unseren Erweiterungen wissen, müssen wir einen kleinen Umweg wählen, um schlußendlich die Objekte zu speichern. Also bauen wir uns fix zwei Funktionen die uns die gewünschten Objekte in einen Stream schreiben.

ausblenden volle Höhe 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:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
  function WritePersistentToStream(Astream:TStream;AObject:TPersistent):boolean;
  function ReadPersistentFromStream(AStream:TStream;AObject:TPersistent):boolean;

Implementation
  
function WritePersistentToStream(Astream:TStream;AObject:TPersistent):boolean;
var
  writer : TWriterEx;

begin
  result := FALSE;
  writer := NIL;
  try
    writer := TWriterEx.Create(AStream,1024);
    Writer.SaveProperties(AObject);
    writer.FlushBuffer;
    freeandnil(writer);
    result := TRUE;
  except
    on e:exception do
    begin
      if (writer <> NILthen
        FreeAndNil(Writer);
    end;
  end;
end;
  
function ReadPersistentFromStream(AStream:TStream;AObject:TPersistent):boolean;
var
  reader : TReaderEx;

begin
  reader := NIL;
  result := FALSE;
  try
    reader := TReaderEx.Create(AStream,1024);
    reader.LoadProperty(AObject);
    FreeAndNil(Reader);
    result := TRUE;
  except
    on e:exception do
    begin
       if (reader <> NILthen
         FreeAndNil(reader);
    end;
  end;
end;


Zuerst erzeugen wir uns also eine Instanz von unserer Reader/Writer-Klasse, und verknüpfen diese mit dem gewünschten Stream. Dann Speichern/Lesen wir die Eigenschaften unseres Objektes. Unsere Instanzen geben wir natürlich wieder frei.Den Rest erledigt der VCL-Streaming-Mechanismus mit Hilfe der RTTI :)

Das Prunkstück (unser Objekt)

Um das ganze nun auch mal in Aktion zu bekommen, brauchen wir natürlich auch noch ein Objekt, das wir speichern bzw. laden wollen. Damit das ganze auch funktioniert, müssen wir unser Objekt (bzw. die Objektklasse) von TPersistent ableiten (bereits abgeleitete Klassen von TPersistent sollten auch als Basis funktionieren).

ausblenden 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:
  TMeinPrunkstueck = class(TPersistent)
    PRIVATE
       fStr : String;
       fInt : Integer;
       fid : string;
       farr : array[0..9of String;
       function GetItem(index: integer): string;
       procedure SetItem(index: integer; const Value: string);

    PROTECTED
       Procedure ReadItems(Reader:TReader);
       Procedure WriteItems(Writer:TWriter);
    PUBLIC
       property Items[index:integer]:string read GetItem write SetItem;
       Constructor Create;
       Destructor Destroy;override;

       Procedure Assign(Source:TPersistent);override;
       Procedure DefineProperties(Filer:TFiler);override;

    PUBLISHED
       property Astring:String read fstr write fstr;
       property AInteger:Integer read fint write fint;
       property ID:string read fid write fid;
  end;


Isse nicht schön..die Klasse :)

Nun...standardmäßig werden von der VCL ja nur Published-Eigenschaften geschrieben und gelesen. Manche Eigenschaften können wir aber nicht als Published deklarieren (wie hier ein Array). Aber auch dafür gibts eine Lösung. Mit Hilfe der DefineProperties-Methode sowie ReadItems und Writeitems, speichern wir auch unser Array ab. Theoretisch ließe sich auch irgendwas anderes damit im Stream speichern. Aber hier will ich erstmal das Prinzip verdeutlichen.

Mit Hilfe von DefineProperties kann man sog. Pseudo-Properties in das Streamingsystem einfügen. Sogar die VCL macht davon gebrauch (So merkt sich Delphi die Position von nicht-visuellen Komponenten :) )

ausblenden Delphi-Quelltext
1:
2:
3:
4:
Procedure TMeinPrunkstueck.DefineProperties(Filer:TFiler);
begin
  Filer.DefineProperty('ITEMS',ReadItems,WriteItems,true);
end;


Damit sagen wir dem System, das unsere Klasse eine Pseudo-Eigenschaft "ITEMS" hat, die mit Hilfe der beiden Methoden ReadItems und Writeitems gelesen bzw. geschrieben werden können. Außerdem sagen wir dem System, das immer Daten für diese Eigenschaft vorhanden sind.

Nun noch schnell die Methoden zum Lesen und Schreiben implementiert:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
procedure TMeinPrunkstueck.ReadItems(Reader: TReader);
var
  I : integer;

begin
  reader.ReadListBegin;
  for I := 0 to 9 do
    farr[i] := reader.ReadString;
  reader.ReadListEnd;
end;

procedure TMeinPrunkstueck.WriteItems(Writer: TWriter);
var
  I : integer;

begin
  writer.WriteListBegin;
  for I := 0 to 9 do
    writer.WriteString(farr[i]);
  writer.WriteListEnd;  
end;


fertig. :)

Das Muster

Ab jetzt können wir mit unseren beiden Funktionen ReadPersistentFromStream und WritePersistentToStream unser Prunkstück lesen und schreiben. Und zwar egal in was, solange es von TStream abgeleitet ist. Ob das ganze eine Datei, ein Archiv oder gar eine Netzwerkverbindung ist, ist dem System egal. Hauptsache TStream.

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
procedure TForm21.LoadObjectFromStream;
begin
  if (Prunkstueck1 = NILthen
    Prunkstueck1 := TMeinPrunkstueck.create;
  ReadPersistentFromStream(AFilestream,Prunkstueck1);  
end;

Procedure TForm21.SaveObjectToStream;
begin
  if (Prunktstueck1 <> NILthen
    writepersistenttosteam(AFilestream,Prunkstueck1);
end;


WICHTIGER HINWEIS

Wenn ihr mehrer Objekte in einem Stream speichern bzw. lesen wollt, müsst ihr auf die Reihenfolge achten, in der die einzelnen Objekte gelesen und gespeichert werden !

Schlußwort

Die beschrieben Technik ist sicher noch nicht das Optimum. Deshalb freu ich mich über Anregungen und Hinweise :)

Ein komplettes Beispiel ist in Vorbereitung und wird demnächst hier angehangen.

Moderiert von user profile iconTino: Überflüssige Zeilenumbrüche entfernt.
Einloggen, um Attachments anzusehen!
_________________
Gruß Ghostwalker
Es gibt keine Probleme, nur noch nicht gefundenen Lösungen


Zuletzt bearbeitet von Ghostwalker am Mo 02.07.07 10:20, insgesamt 1-mal bearbeitet
BenBE
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: Mo 02.07.07 09:32 
Kleiner Hinweis zu deinen TReaderEx- und TWriterEx-Klassen:
Du kannst auch einfach die besagten Routinen als Public und Override in den abgeleiteten Klassen deklarieren und dann in der Implementation über Inherited auf diese zugreifen. Ds ist IMHO der sauberere Weg.

@CodeGear: Was die schreiben, geht für den allgemeinen Fall; für alles andere ... 2ct

Bei TPrunktstueck ist ein T zu viel im NAmen ;-)

Ansonsten sehe ich auf Anhieb nicht ;-)

Obwohl: Gab's da nicht auch ReadComponent und WriteComponent ;-)???

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
Ghostwalker Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 17

Windows XP Prof.
Turbo Delphi 2006, Delphi 7 Prof.
BeitragVerfasst: Mo 02.07.07 10:18 
user profile iconBenBE hat folgendes geschrieben:
Kleiner Hinweis zu deinen TReaderEx- und TWriterEx-Klassen:
Du kannst auch einfach die besagten Routinen als Public und Override in den abgeleiteten Klassen deklarieren und dann in der Implementation über Inherited auf diese zugreifen. Ds ist IMHO der sauberere Weg.


Richtig. Ob das nun sauberer ist oder nicht, da ließe sich Streiten. :) Ich denke so wird
das Prinzip ganz gut verdeutlicht :)

user profile iconBenBE hat folgendes geschrieben:

Obwohl: Gab's da nicht auch ReadComponent und WriteComponent ;-)???


Das ist schon richtig. Aber dazu müßtest du dein Datenobjekt von TComponent ableiten und würdest einiges an Code und Daten mitführen, der eigentlich nicht benötigt wird.


Den Beitrag hab ich noch ein bischen Überarbeitet und ein Demo angehängt :)

_________________
Gruß Ghostwalker
Es gibt keine Probleme, nur noch nicht gefundenen Lösungen