![]() |
Referzen in ein Stream speichern
Hallo zusammen
Ich habe wieder einmal eine Frage für die Expterten hier. Wie der Titel schon sagt, möchte ich Referenzen von Objekten in ein Stream speichern. Geht das? Falls ja, bitte wie? Zum besseren Verständnis des Problems habe ich ein Programmgerüst der Objekte erstellt die gespeichert werden sollen. TPoint ist ein Punkt mit zwei Koordinaten TKante stellt die Verbingung zwischen zwei Punkten dar Daraus entsteht schliessliche ein Netz oder ein Graph der gespeichert werden soll.
Delphi-Quellcode:
Vielen Dank für Eure TippsTPoint = Class mx,my:Integer; constructor Create(x1,y1:Integer); Constructor Save(S:Stream); Constructor Load(s:TStream); End; TKante = Class mP1,mP2:TPoint; constructor Create(P1,P2:TPoint); Constructor Save(S:Stream); Constructor Load(s:TStream); End; TGraph = class PunkteListe:TList; KantenList:TList; constructor Create(); end; constructor TPoint.create(x1,y1:Integer); Begin mx:=x1; my:=y1; End; Constructor TPoint.Save(S:Stream); Begin s.Write(mx,sizeof(mx)); s.Write(my,sizeof(my)); End; Constructor TPoint.Load(s:TStream); Begin s.read(mx,sizeof(mx)); s.read(my,sizeof(my)); End; constructor TKante.create(P1,P2:TPoint); Begin mP1:=P1; mP2:=P2; End; Constructor TKante.Save(S:Stream); Begin ??? End; Constructor TKante.Load(s:TStream); Begin ??? End; constructor TGraph.Create(); var P1,P2:TPoint; K:TKante; Begin PunkteListe:=TList.Create; // erzeuge 10 zufällige Punkte for i:=0 TO 9 Do Begin P:=TPoint.create(Random(100),Random(100)); PunkteListe.Add(p); End; Erzeuge 4 zufällige Kanten For j:=0 TO 4 Do Begin Kantenliste:=TKante.Create(l[Random(9)],L[Random(9)); End; End; constructor TGraph.LoadFromstream(); Begin // lade Punkte ?? // lade Kanten ?? End; Geri |
DP-Maintenance
Dieses Thema wurde von "Dax" von "Open-Source" nach "Sonstige Fragen zu Delphi" verschoben.
Das ist kein OS-Code.. |
Re: Referzen in ein Stream speichern
Das bezeichnet man als Objektpersistenz.
Da gibt es die dpCollection (mal suchen im Forum) und JEDI Obiwan, ein Objektpersistenzframework. |
Re: Referzen in ein Stream speichern
Hallo Manuel
Vielen Dank für den hilfreichen Hinweis. Ich habe mir dpCollection nun einmal herunter geladen. Mir ist aber einfach nicht klar wie ich sie auf meinen Fall anwenden muss. Hast du hieb bitte evtl. auch noch so einen guten Tipp, oder ein einfaches Beispiel in dem die Technik gezeigt wird? Vielen Dank nochmals Geri |
Re: Referzen in ein Stream speichern
Nein, ich habe sie noch nie verwendet. Einfach weiter suchen.
|
Re: Referzen in ein Stream speichern
Damit wird das ganze ganz schnell ganz einfach:
![]() |
Re: Referzen in ein Stream speichern
Hallo Benjamin
Vielen Dank für Deine Mühe. Auf der Suche habe ich auch noch diesen Link gefunden: ![]() Ganz schlau bin ich aber trotzdem noch nicht geworden. Insbesondere weil ich Einträge in einem Tlist-Objekt speichern möchte. Beste Grüsse Geri |
Re: Referzen in ein Stream speichern
Hi,
du kannst das auch selber machen. Der Aufbau deines Stream sieht immer so aus: Klassenname als String Daten zu diesem Objekt Klassenname als String Daten zu diesem Objekt Dh. du speicherst in deinem Stream auch die Klassenname -> Self.ClassName als ShortString -> String der nur 255 Zeichen speichern kann, dafür in String[0] die Länge. Fehlt noch der Punkt wie man von einem solchen String zurück zu einer Klasse kommt die man instanzieren kann. Dazu gehts du exakt so vor wie die VCL. Diese verwaltet eine TList -> RegisteredClasses und zwei Prozeduren -> RegisterClasses(Classes: array of TClass) und Funktion GetClass(const ClassName: String): TClass; In deinem Code rufst du nun RegisterClasses([TPoint, TKante, TFlaeche]) in der Initialization Sektion auf. Deine Stream-Leses-Routine liest erstmal einen ShortString aus dem Stream. Dann wird mit GetClass(NameAusDemStream).Create eine Instance erzeugt. Dieser Instanze übergibst du die Kontrolle des Streams damit sie nun ihre Daten auslesen kann. Alle deine Klassen sollten von eniner Basisklasse abgeleitet sein, zb. TMyBaseClass.
Delphi-Quellcode:
Ist natürlich nur ein grobes Beispiel und man kann noch einiges verbessern. Statt zb. den langen Klasssennamen zu speichern köntest du auch zu jeder Klasse eine eigene ID erzeugen und diese dann als Byte,Word oder LongWord abspeichern. Das macht deinen Stream kompakter, besonders falls du tausende solcher Objekte speichern möchtest.type TMyBaseClass = class protected procedure LoadFromStream(Stream: TStream); virtual; end; TPoint = class(TMyBaseClass); TKante = class(TMyBaseClass); procedure LoadObjects(List: TList; Stream: TStream); function ReadClassName: ShortString; begin Stream.Read(Result[0], SizeOf(Result[0])); Stream.Read(Result[1], Ord(Result[0])); end; var Instance: TMyBaseClass; begin while Stream.Position < Stream.Size do begin Instance := (GetClass(ReadClassName) as TMyBaseClass).Create; Instance.LoadFromStream(Stream); List.Add(Instance); end; end; procedure SaveObjects(List: TList; Stream: TStream); var I: Integer; S: ShortString; begin for I := 0 to List.Count -1 do with List[I] as TMyBaseClass do begin S := ClassName; Stream.Write(S[0], Ord(S[0]) +1); SaveToStream(Stream); end; end; initialization RegisterClasses([TPoint, TKante, TFlaeche,...]); end. Bei einer meiner Klassen (hierarisch, sortierter Node Baum) mache ich es so das ich als erstes alle Klassennamen der verwendeten Objekte in den Stream speichere. Diese Liste dient nun als Lookuptabelle um einen Klassennamen=Klasse eine ID zu geben. Diese ID ist nichts anderes als der Index in diese Liste. Man speichert also zb. einen Baum aus 10000 Objekten mit 10 unterschiedlichen Klassen so das man als erstes eine sortierte Liste aus Strings=Klassenamen in den Stream speichert. Das sind also 10 Strings. Die nachfolgenden Klassen haben dann eine 1 Byte ID, der Index in diese Liste an der der Klassenname steht. Somit sparst du viel Speicherplatz, weist von Anfang an welche Klassen im Stream gespeichert sind, und zur Laufzeit kannst du die Klasse einfach in dieser Liste direkt nachschlagen, ein Objeckt wird ja über seinen Byte-Index aus dem Stream identifiziert. Die Verweise auf dpCollection sind gut gemeint, aber ich meine das du mit wenigen Zeilen Source selber schneller bist und auch was dabei lernst. Gruß Hagen |
Re: Referzen in ein Stream speichern
Hallo Hagen
Vielen Dank für Dein tolles (kurzes und sehr gut nachvollziehbares) Beispiel und die detaillierte Beschreibung!! Super! Ich denke, nun ist mir klar, wie ich eine TListe speicher. Wie schreibe ich aber das Objekt Kante in ein Stream. Sind ja nur die Pointer auf die Punkte gespeichert, die in einer anderen Liste gespeichert sind.
Delphi-Quellcode:
Wenn ich Deinen Code compiliere, dann erhalte ich ein paar Meldungen
TGraph = class
PunkteListe:TList; KantenList:TList; constructor Create(); end; Procedure TPoint.Store(s:TStream) Begin S.Write(x, SizeOf(x)); S.Write(y, SizeOf(y)); End; Procedure TPoint.Load(s:TStream) Begin S.Read(x, SizeOf(x)); S.Read(y, SizeOf(y)); End; Procedure TPoint.Load(s:TStream) Begin S.Read(x, SizeOf(x)); S.Read(y, SizeOf(y)); End; Procedure TKante.Store(s:TStream) Begin S.Write( ...schreibe Referenz von P1 in das Stream??? S.Write( ...schreibe Referenz von P2 in das Stream??? End; Procedure TKante.Store(s:TStream) Begin S.Read( ...lies Referenz von P1 in das Stream??? S.Read( ...lies Referenz von P2 in das Stream??? End; constructor TGraph.Create(); var P1,P2:TPoint; K:TKante; Begin PunkteListe:=TList.Create; // erzeuge 10 zufällige Punkte for i:=0 TO 9 Do Begin P:=TPoint.create(Random(100),Random(100)); PunkteListe.Add(p); End; // Erzeuge 4 zufällige Kanten For j:=0 TO 4 Do Begin Kantenliste:=TKante.Create(l[Random(9)],L[Random(9)); End; End;
Delphi-Quellcode:
Vielen Dank nochmals und beste Grüsse
...
Instance := (GetClass(ReadClassName) as TMyBaseClass).Create; //[Fehler] uHagen.pas(50): E2015 Operator ist auf diesen Operandentyp nicht anwendbar ... with List[I] as TMyBaseClass do // Operator nicht anwendbar SaveToStream(Stream); // wennn ich richtig verstanden habe, dann werden hier z.B. die Koordinaten für einen Punkt gespeichert. Wie speichere ich aber bitte die Kantenliste? Geri |
Re: Referzen in ein Stream speichern
Ok, zuerst zu den Fehler:
Mein obiges Beispiel ist nicht getestet sondern eben nur ein Beispiel das mal eben reingehämmert habe. Solche Feinheiten sind somit deine Sache ;) Zum Problem der verlinkten Objekte: Es gibt mehrere konzeptionelle Ansatzpunkt. Einmal könntest du in TMyBaseClass.SaveToStream() nur den Klassennamen abspeichern. Alle davon abgeleiteten Klasse müsen dann intern in ihrer überschriebenen Methode .SaveToStream() als erstes inherited SaveToStream() aufrufen. Das ist sowieso eine gute Idee. Nachteilig dabei ist der Punkt das wenn wir zb. von TPoint eine TPoint3D ableiten nun im Stream auch der Klassenname von TPoint drinnen stehen würde. Also wird man mit 2 Methoden arbeiten müssen. .SaveToStream() als statische Methode in TMyBaseClass und .SaveData(Stream) als virtuelle Methode die TPoint etc. pp. überschreiben müssen. TMyBaseClass.SaveToStream() schreibt erstmal den Klassennamen in den Stream und ruft danach .SaveData(Stream) auf. Methode .SaveData() speichert die Daten des jeweiligen Objektes. Sollte diese Klasse zb. TKante sein so ruft sie auch noch FStartPoint.SaveToStream() und FStopPoint.SaveToStream() auf. Bei .LoadFromStream() muß man dann einige Änderungen machen. Man könte .LoadFromStream() so deklarieren
Delphi-Quellcode:
Diese Funktion lädt erstmal den Klassennamen aus TStream, erzeugt dann in Result eine Instanz von dieser Klasse und ruft Result.ReadData(Stream) auf. Man hat also nur in TMyBaseClass() eine Klassenmethode .LoadFromStream() und in den davon abgeleiteten Klassen überschreibt man dann .ReadData() um die eigentlichen Daten zu laden. Dein TKante Objet würde dann so arbeiten:
class function TMyBaseClass.LoadFromStream(Stream: TStream): TMyBaseClass;
Delphi-Quellcode:
.LoadFromStream() ist dann quasi ein Constructor und der Typcast mit "as TPoint" stellt sicher das keine falsch TMyBaseClass in deinen Feldern landet.
procedure TMyBaseClass.SaveToStream(Stream: TStream); static;
begin Stream.WriteString(ClassName); SaveData(Stream); end; procedure TMyBaseClass.SaveData(Stream: TStream); dynamic; begin // nothing todo end; class function TMyBaseClass.LoadFromStream(Stream: TStream): TMyBaseClass; var Name: String; Class: TClass; begin Name := Stream.ReadString; Class := GetClass(Name); if Class = nil then raise Exception.CreateFmt('Klasse "%" ist nicht registriert.', [Name]); if not (Class is TMyBaseClass) then raise Exception.CreateFmt('Klasse "%s" ist nicht von TBaseClass abgeleitet', [Name]); Result := TMyBaseClass(Class).Create; // .Create in TMyBaseClass muß virtuell deklariert sein !! Result.ReadData(Stream); end; procedure TMyBaseClass.ReadData(Stream: TStream); dynamic; begin end; prcoedure TKante.SaveData(Stream: TStream); override; begin Stream.Write(MeineDaten,...); FStartPoint.SaveToStream(Stream); FStopPoint.SaveToStream(Stream); end; procedure TKante.ReadData(Stream: TStream); override; begin Stream.Read(MeineDaten, ...); FStartPoint := TMyBaseClass.LoadFromStream(Stream) as TPoint; FStopPoint := TMyBaseClass.LoadFromStream(Stream) as TPoint; end; Oben sprach ich meine TNode Objekte an. Diese sind ebenfalls von TObjet abgeleitet. Das bedeutet das wir nun nichtmehr das Streaming-System der VCL benutzen können wie bei TComponent Klassen gewohnt. Denn diese machen das exakt genauso. Schue dir mal eine DFM Datei als Text an. Um denoch meine TNode Objekte so abspeichern zu können wie die VCL habe ich eine TNodeStreamingComponent von TComponent abgeleitet. Beim Laden und Speichern wird also ein TNode Objekt innerhalb dieser Klasse gekapselt als Property. Das hat den riesigen Vorteil das man in den eigenen Objekten mit published Properties arbeiten kann und das VCL Streaming System diese Properties speichert und lädt. Das ist so wie bei TFont, TBrush, TPen Objekten die als Properties einer TComponent published wurden. Eine solche Klasse kann ansich nicht mit dem VCL Streaming gespeichert werden, sondern immer nur als Eigenschaft einer Komponente. Der Vorteil ist dann:
Delphi-Quellcode:
Das reicht schon aus damit mein TNodeStreamingComponent, das eine TMyNode KLasse in sich kapselt, per VCL System die beiden Properties X und Y selbständig speichern und laden kann. Man muß also nicht mehr selber ständig diese Properties speichern und laden, sondern die bloße Deklaration als published read/write Properties reicht schon aus.
type
TmyNode = class(Tnode) private FX,FY: Integer; published property X: Integer read FX write FX; property Y: Integer read FY write FY; end; Ich weis, ich mache dich heis und lasse dich ohne Sourcen im Regen stehen ;) Allerdings greift mein "Trick" in die Untiefen des VCL Streamings + TComponnet ein, und das müsstes du dann auch verstanden haben. Hier habe ich leider nicht die Zeit, den Platz und das Recht alles ganz genau aufzuzeigen. (Recht deshalb weil es ein kommerzielles Produkt ist). Versichern kann ich dir aber das es über diesen Trick definitiv funktioniert. Immerhin der originale Erstsource lief schon in Delphi 1 und wurde mit der Zeit bis nach Delphi 7 portiert, OHNE das es Inkompatibilitäten in den gespeicherten Streams gab. Es geht also. Gruß hagen |
Alle Zeitangaben in WEZ +1. Es ist jetzt 08:25 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz