Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Verständnisproblem Streams (https://www.delphipraxis.net/50496-verstaendnisproblem-streams.html)

Traudix 26. Jul 2005 18:50


Verständnisproblem Streams
 
Hallo!

Mir fällt auf, das eine Klasse, die einen Stream verwendet, nicht vom Programmierer bewußt registriert wird. In Turbo/Borland Pascal war das anders:

Delphi-Quellcode:
Unit StrmObj;

interface

type
 TStreamableObject = object(TObject)
   constructor Init;
   constructor Load(var S: TStream);
   procedure Store(var S: TStream);
 end;

//Dann stand sowas hier da:
RStreamableObject: TStreamRec = (
  ObjType: 100;
  VmtLink: ofs(TypeOf(TStreamableObject)^);
  Load: @TStreamableObject.Load;
  Store: @TStreamableObject.Store
);

//Nun gab es eine Registrier-Prozedur wie folgt

procedure RegisterStrmObj;

//Der Name setzte sich aus dem Wort Register + Name der Unit zusammen

implementation

procedure RegisterStrmObj;
begin
  RegisterType(RStreamableObject);
end;

end.
Nun meine Frage:

Ein(e) Objekt/(Klasse) nuß also für die Verwendung mit Streams registriert werden. Wie und wo erledigt Delphi diese Aufgabe?

Hätte man in Turbo/Borland Pascal auch im Objekt eine Methode definieren können, die das Objekt für den Stream registriert und die dann halt vom Konstruktor mit aufgerufen wird. Wenn nicht, warum nicht? Borland hat ja möglicherweise Gründe, warum die Streamregistrirung in TP/BP so und nicht anders realisiert wurde. Andererseits sehe ich bei den Delphi Streams nirgendwo eine Registrierung, die ich analog vornehmen müßte.

Auf eine Aufklärung freut sich

Traudix

SirThornberry 26. Jul 2005 19:41

Re: Verständnisproblem Streams
 
wozu willst du denn eine Streamklasse registrieren? Was ist der Nutzen des ganzen?
Heutzutage wird einfach von TStream abgeleitet und die abstracten Methoden implementiert.

Traudix 26. Jul 2005 22:05

Re: Verständnisproblem Streams
 
Hallo Sir Thornberry!

Zitat:

wozu willst du denn eine Streamklasse registrieren? Was ist der Nutzen des ganzen? ...
Ich will nichts registrieren, Ich will verstehen. Hab mir inzwischen mal in meiner D3 Pro den Quelltext der Units bissl angeguckt. Hab das paar Prozeduren mit so aufschlußreichen Namen wie
RegisterComponent, RegisterConponentClass,... usw. gesehen.

[OT]
Andererseits hab ich ein altes DOS Spiel, Könnte man doch portieren. Die Objects Unit aus Turbo Pascal, die es ja in Freepascal auch gibt, hab ich von dort übernommen und für Delphi übersetzt. Nun steht zum Beispiel die Frage, ob es sinnvoll ist, die Objekte dort um eine Streamregistrierungsmethode zu erweitern. Dann brauchte ich auch dort keine Registrierung mehr
im Hauptprogramm, vielleicht noch nicht mal mehr in abgeleiteten Objekten, falls ich das Teil mal erweitere, und in neuen Objekten keine neuen Daten zu speichern sind.
[/OT]

Zitat:

... Was ist der Nutzen des ganzen? ...
(A)
das ich hinterher den Aufbau der VCL besser verstehe. Sichwort FCL. Un da mitzumachen, sollte ich die Arbeitweise der VCL wenigstens annähernd verstehen.

(B)
kann ich damit wieder was lernen. Vielleicht gibt es ja ne noch bessere Lösung als Streams für den gleichen Zweck, für den sie heute verwendet werden. Man kann nie wissen. Dann ist es immerhin sinnvoll, zu verstehen, wie die bisherige Lösung funktioniert.

(C)
Es gibt nun mal die FreeClx. Und ich habe zudem ne Delphi 3 Pro, wo die VCL Quellen dabei sind.
Und da gucke ich mir die Quellen halt auch mal an und möchte dann auch verstehen, was dort drin passiert.


Traudix

SirThornberry 26. Jul 2005 22:22

Re: Verständnisproblem Streams
 
könntest du eventuell noch schreiben wozu damals Stream-Klassen registriert wurden?

RegisterComponents dient dazu eine Komponente in der IDE zu registrieren so das du Sie später einfach auf das Form packen kannst.

Ich weiß nicht in wie weit du dich bereits mit Delphi und damit auch Object-Pascal auskennst. Aber solche Dinge wie "RegisterComponents" sind dann erst später wichtig wenn du selbst Komponenten schreiben willst. Zu Anfang sollte es jedoch auch reichen, selbst geschriebene, Komponenten einfach dynamich zu erzeugen.

Zur Ausgangsfrage: Da ich nicht weiß wie es früher mit den Streamklassen war kann ich dir nicht so recht weiter helfen. Bei Delphi ist es so das mit RegisterComponents, Komponenten in der IDE "registriert" werden. Da alle Komponenten von TComponent abgeleitet sind (wie der Name ja schon sagt) haben diese auch einige GrundMethoden zum speichern einiger besonderer Properties. Einfache Integerproperties etc. werden später automatisch gespeichert (wenn sie registriert sind und auf das Form gepackt wurden).

Traudix 27. Jul 2005 22:38

Re: Verständnisproblem Streams
 
Hallo Sir Thornberry!

Zitat:

Zitat von Sir Thornberry
könntest du eventuell noch schreiben wozu damals Stream-Klassen registriert wurden?

Delphi-Quellcode:
//Der Registrierungsrecord war wie folgt definiert
type
 PStreamRec = ^TStreamRec;
 TStreamRec = Record
  //für jedes Objekt eindeutige Nummer
  //(war fehlerträchtig->(doppelte Nummern))
  ObjType: word;  
  //Adresse der VMT
  VmtLink: word;
  //Adresse von Load ->
  //für das Lesen der Daten vom Stream
  Load: Pointer;
  //Adresse von Store ->
  //Screiben in Stream
  Store: Pointer;
  //Nächster Registrierungsrecord
  Next: PStreamRec;
 end;

//Es wurde einfach eine Liste mit den Registrierungsdaten aufgebaut.

//Belegt wurden die Felder so, wie ich es im ersten Beitrag gezeigt habe.
Nicht die Stremklassen wurden registriert, sondern diejenigen Objekte, die man im Stream speichern wollte. Mein TStreamableObject sei ein solches Objekt, das ich auf einem Stream speichern will. Streamable (streamfähig, weiß, wie es sich in den Stream speichert und wieder aus ihm liest) habe ich hier als Name gewählt, um zu unterstreichen, das das Objekt eben fähig (engl. able) sein soll sich in einem Stream zu speichern und auch wieder gelesen zu werden.
Man mußte dann für eigene Objekte eigene Load und Store Methoden schreiben:

Delphi-Quellcode:
type
TStreamableObject = object(TObject)
   Datenfeld_1: Integer;
   Datenfeld_2: PString; //Zeiger auf einen String
   Datenfeld_3: PObject; //nur beispielhaft (In der Praxis irgendein sinnvoller Objektzeiger)
   constructor Init;
   constructor Load(var S: TStream);
   procedure Store(var S: TStream); virtual;
end;

//TObject war ganz einfach gestrickt, hatte keine Datenfelder und nur eine virtuelle Methode.
//Damit war garaniert das die VMT das erste Feld (Offset 0) eines jeden Objektes war, da alle
//Detenfelder erst DANACH folgten.
type
 TObject = Object
   constructor Init;          
  //Wie in Delphi auch-> sobald virtuelle Methoden-> Konstruktor
   procedure Free;            
  //nicht virtuell, für Vertändnis hier unwichtig
   destructor Done; virtual;  
  //Damit gibt es eine VMT
 end;

//Jedes Streamfähige Objekt mußte natürlich von TObject abgeleitet werden, damit das
//Konzept funktionierte.

constructor TStreamableObject.Init;
begin
  inherited Init;
  //die einenen Voreinstellungen
end;

constructor TStreamableObject.Load(var S: TStream);
begin
  //wenn direkt von TObject abgeleitet -> inherited Init;
  //wenn zwischen meinem Objekt und TObject weitere Klassen dazwischen, dann
  //inherited Load(S); //um die geerbten Datenfelder auch zu berücksichtigen
  //Vorausgesetzt, es gibt einen geerbten Load Constructor, ja, Load war ein
  //Constructor
  //Hier also, weil direkt von TObject abgeleitet:
  inherited Init;
  S.Read(Datenfeld_1);
  S.ReadStr(Datenfeld_2);       //Kommentar-> siehe Kommentare in der Store-Methode
  GetSubObjPtr(S, Datenfeld_3); //
end;

procedure TStreamableObject.Store(var S: TStream);
begin
   //Wenn direkt von TObject abgeleitet, wird keine Store Methode geerbt, weil in TObject
   //keine definiert war, sonst
   inherited Store(S);
   S.Write(Datenfeld_1, sizeof(Datenfeld_1);
   S.WriteStr(Datenfeld_2); //Spezielle Methode, die weiß, wie Zeiger sinnvoll und korrekt
   //im Stream abgelegt werden
   PutSubObjPtr(S, Datenfeld_3); //Spezielle Methode, die weiß, wie Objektzeiger im Stream
   //gespeichert werden  
end;

//Die Reihenfolge der Datenfelder muß beim Lesen und schreiben identisch sein

//Nun weiß das Objekt, wie die Daten im Stream gespeichert werden.

//Für das folgende Beispiel setze ich voraus, das ich ein Objekt TMyApplication
//definiert habe, in welchem mein Objekt vom Stream gelesen und zum Programmende
//auch wieder in den Stream geschrieben wird.

//In der Anwendung stand dann sowas hier:

constructor TMyApplication.Init;
begin
  RegisterStrmObj; //Hier wird mein Objekt registriert
  inherited Init;
  //...eigene Voreinstellungen, wie in Delphi auch...
end;

procedure TMyApplication.LoadThisObjectFromStream;
var
  ObjfromStream: TStreamableObject;
  Stream: TStream;
  StreamName: string;
begin
  StreamName := 'MyStream.stm';
  Stream.Init(StreamName,stOpenRead);

  ObjFromStream := PStreamableObject(Stream.Get);

  //Die Typumwandlung war nötig, weil der Stream Objekte oder Nachfahren vom Typ TObject
  //speicherte
  //Natürlich muß ich vorher den Typ PStreamableObject definiert haben als ^TStreamableObject;

  Stream.Done; //in Delphi Destroy
end;

//Analog konnte das Objekt gespeichert werden:

procedure TMyApplication.SaveThisObjectToStream;
var
  Stream: TStream;
  StreamName: string;
begin
  StreamName := 'MyStream.stm';
  Stream.Init(StreamName,stOpenRead);

  Stream.Put(MyObjectOnStream);

  //Die Typumwandlung war nötig, weil der Stream Objekte oder Nachfahren vom Typ TObject
  //speicherte
  //Natürlich muß ich vorher den Typ PStreamableObject definiert haben als ^TStreamableObject;

  Stream.Done; //in Delphi Destroy
end;
Damit das Objekt weiiß, wie Datenfelder, untergeordnete Objekte (wie zB. Buttons auf Form) gespeichert werden müssen, damit sie beim wiedereinlesen nicht nur auf irgendwelche Speicherplätze gebracht werden, sondern eindeutig auch dem richtigen Objekt mit der richteigen VMT zugeordnet werden können, war (ist garantiert auch in Delphi) diese Registrierung notwendig. Und wenn sie notwendig ist und unter Delphi aber nicht vom Programmierer vorgenommen wird, muß diese Registrierung irgendwo in den Tiefen der VCL versteckt sein. In TP/BP mußte ich für jedes Objekt einen StreamRec schreiben, der belegt war, wie im ersten Beitrag gezeigt. Die Zahl ist eine laufende Nummer, der VmtLink ist die Adresse der Vmt des zu speichernden Objektes, dann folgen die Adressen von Load und Store, den Methoden, die für das Lesen bzw. Schreiben zuständig sind. Zusätzlich mußte das Objekt mit der Prozedur:

RegisterType(RMyObjectWhichShouldWorkWithStream);

registriert werden. Deshalb die TStreamRec Konstante RStreamableObject, per Konvention, wie der Objekttyp, aber statt Präfix T wurde Präfix R davor gesetzt. Das wurde (dann auch per Konvention) so gemacht, das man eine Prozedur (KEINE METHODE) geschrieben hat:

procedure Register[gefolgt vom unitnamen]; ---> siehe erster Beitrag

Meine Beispielunit heißt StrmObj -> also

Delphi-Quellcode:
procedure RegisterStrmObj;
begin
  RegisterType(RStreamableObject);
end;
In der Anwendung wurde diese dann vom InitKonstruktor augerufen, womit das Objekt für die Zusammenarbeit mit dem Stream registriert, also vorbereitet war.

Ich bin sicher, das in Delphi die Klassen genauso für die Arbeit mit Streams registriert werden aber die Registrirung geschickt vor dem Programmierer verborgen wird und automatisch erledigt wird. Als erstes fallen mir da die Konstruktoren ein. Aber dann müßte ich ja bei Einführung neuer Datenfelder auch wieder selber Hand anlegen. Es muß also einen anderen Trick geben. Die Vorgehensweise in TP/BP war nämlich recht Fehlerträchtig, da eine Registrierung schnell vergessen wird. Delphi nimmt da ne ganze Menge Arbeit ab, aber was geht da in der VCL vor?

Also, nicht die Streams, sondern die Objekte, die mit Streams zusammenarbeiten sollten, wurden registriert, dem Stream bekannt gemacht.

Grüße von

Traudix

Robert_G 28. Jul 2005 00:43

Re: Verständnisproblem Streams
 
Das wird in D32 über RegisterClass und TWriter/TReader gelöst.
Du kannst dir als Beispiel den Code von der dpCollectiondpCollection ansehen.
Genauso, bzw. ähnlich, geht die IDE vor um DFM Daeienen zu erzeugen/laden und das Form/Frame/DataModule selbst benutzt es um sich zu initialisieren.

Edit: Wenn du mit FCL .Net meinst, ist das Registrieren an sich unnötig, da reicht es trivialen Klassen ein SerialzeableAttribute zu verpassen, bzw. wenn man selbst Hand anlegen will implementiert man ISerialzable.
Das Serialisieren erfolgt dann zum Beispiel über XmlSerializer, oder einem der Formatter (BinaryFormatter, SoapFormatter, ...).

Traudix 28. Jul 2005 18:09

Re: Verständnisproblem Streams
 
Hallo Robert_G!

Danke erst mal. Das dpCollection-Beispiel muss ich mir nun erst mal durcharbeiten. Ich habe VCL gemeint. Interessant ist, das bei FCL .NET nur ein Serializable-Attribut gesetzt werden muß, wenn es sich un triviale Klassen handelt. Aber wenn die Klassen nicht so trivial sind, muß also auch dort was implementiert werden. Aber das kommt später. Vorerst soll es nur um die VCL gehen. Erst mal diese begreifen.

Gruß

Traudix


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:39 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