AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Delphi [Artikel]TPersistent is im Stream
Tutorial durchsuchen
Ansicht
Themen-Optionen

[Artikel]TPersistent is im Stream

Ein Tutorial von Ghostwalker · begonnen am 1. Jul 2007 · letzter Beitrag vom 3. Jul 2007
Antwort Antwort
Seite 1 von 2  1 2      
Ghostwalker
Registriert seit: 16. Jun 2003
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

Delphi-Quellcode:
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. 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 ).

Delphi-Quellcode:
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.

Delphi-Quellcode:
  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 <> NIL) then
        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 <> NIL) then
         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).

Delphi-Quellcode:
  TMeinPrunktstueck = class(TPersistent)
    PRIVATE
       fStr : String;
       fInt : Integer;
       fid : string;
       farr : array[0..9] of 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 )

Delphi-Quellcode:
Procedure TMeinPrunktstueck.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:

Delphi-Quellcode:
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.

Delphi-Quellcode:
procedure TForm21.LoadObjectFromStream;
begin
  if (Prunkstueck1 = NIL) then
    Prunkstueck1 := TMeinPrunkstueck.create;
  ReadPersistentFromStream(AFilestream,Prunkstueck1);   
end;

Procedure TForm21.SaveObjectToStream;
begin
  if (Prunktstueck1 <> NIL) then
    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.
Angehängte Dateien
Dateityp: zip persistsaverdemo_132.zip (221,6 KB, 44x aufgerufen)
e=mc² or energy = milk * coffee²
 
Benutzerbild von Luckie
Luckie

 
Delphi 2006 Professional
 
#2
  Alt 1. Jul 2007, 23:12
Kleiner Schönheistfehler: Wenn im Englischen ein Wort auf -y endet wird es in der Mehrzahl mit angehängten Mehrzahl-s zu -ie:
SaveProperties müsste es deshalb korrekterweise heissen.
Michael
  Mit Zitat antworten Zitat
Ghostwalker

 
Delphi 10.3 Rio
 
#3
  Alt 1. Jul 2007, 23:16
In der Tat. Mein Englisch ist wohl etwas eingerostet....
Uwe
  Mit Zitat antworten Zitat
Muetze1
 
#4
  Alt 1. Jul 2007, 23:43
... und die Abfrage im Finally Block, ob writer <> nil ist, kannst du dir sparen. Gleichwohl ist FreeAndNil() unnötig, da es eine lokale Variable ist und dies die letzte Anweisung. Ein einfaches .Free reicht in finally Block damit vollkommen aus.
  Mit Zitat antworten Zitat
Ghostwalker

 
Delphi 10.3 Rio
 
#5
  Alt 2. Jul 2007, 05:13
Leider nicht, die Abfrage ist (zumindest unter Turbo Delphi) manchmal notwendig. Ich hatte schon ein paar mal den Fall, das er in den Finally-Block ging, ohne einen gültigen Zeiger auf das Objekt zu haben, was dann zu einem netten Crash führte. Dummerweise tritt das nur sporadisch auf.

Ein free würde sicher ausreichen, FreeAndNil ist bei mir zur Gewohnheit geworden Aber darum gehts ja auch garnicht.
Uwe
  Mit Zitat antworten Zitat
Ghostwalker

 
Delphi 10.3 Rio
 
#6
  Alt 2. Jul 2007, 05:30
Ich hab noch eine paar Korrekturen an den Methodennamen der Writer und Reader-Klasse vorgenommen. Sollte ja schließlich konsistent sein
Uwe
  Mit Zitat antworten Zitat
Basilikum

 
Delphi 7 Professional
 
#7
  Alt 2. Jul 2007, 07:45
Zitat von Ghostwalker:
Leider nicht, die Abfrage ist (zumindest unter Turbo Delphi) manchmal notwendig. Ich hatte schon ein paar mal den Fall, das er in den Finally-Block ging, ohne einen gültigen Zeiger auf das Objekt zu haben, was dann zu einem netten Crash führte. Dummerweise tritt das nur sporadisch auf.

Ein free würde sicher ausreichen, FreeAndNil ist bei mir zur Gewohnheit geworden Aber darum gehts ja auch garnicht.
ich würde das mit dem Try-Finally so schreiben:
Delphi-Quellcode:
begin
  result := FALSE;

  writer := TWriterEx.Create(AStream,1024); // wenn es hier schon kracht, gibts noch nichts zum Aufräumen
  try
    Writer.SaveProperties(AObject);
    writer.FlushBuffer;

    result := TRUE;
  finally
    freeandnil(writer); // hier gibt es jedoch sicher etwas zum Aufräumen
  end;
end;
  Mit Zitat antworten Zitat
Ghostwalker

 
Delphi 10.3 Rio
 
#8
  Alt 2. Jul 2007, 08:57
Es bleibt jedem selbst überlassen, wie er das nun schreibt und handelt Bitte zurück zum Thema
Uwe
  Mit Zitat antworten Zitat
Hawkeye219

 
Delphi 2010 Professional
 
#9
  Alt 2. Jul 2007, 09:15
Hallo Ghostwalker,

warum hast du ReadPersistentToStream und WritePersistentToStream als Funktion implementiert? In welchem Fall erwartest du beim Aufruf den Rückgabewert False?

Gruß Hawkeye
  Mit Zitat antworten Zitat
Ghostwalker

 
Delphi 10.3 Rio
 
#10
  Alt 2. Jul 2007, 09:36
Funktionen hab ich deshalb genommen, weil sie schneller getippt werden, als eine komplette Klasse. Klar kann man das auch in eine eigene Klasse einbauen.

False wird dann zurück gegeben wenn bei einer der Aktionen im Try-Block ein Fehler auftritt. In einer "sauberen" Implementierung sollte natürlich schon eine etwas spezifischere Fehlerauswertung stattfinden.
Uwe
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 02:43 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz