Hallo Sir Thornberry!
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