Motivation
Diese
Unit soll Records oder auch Arrays mit dynamischen Inhalt speichern. Ich weis, dass man dafür eher Klassen mit published Properties nehmen sollte. Aber machnmal ist eine Klasse auch etwas Overkill.
Ich wollte auch schon lange mal diese
Unit schreiben. Schon allein, um zu testen, ob es denn funktioniert. In meinen zahlreichen Tests hat es funktioniert.
Was kann es
Was soll es können
Der Inhalt folgender Bsp.-Deklarationen kann mit einem Funktionsaufruf in einen Stream (oder
nonVCL-
Unit: in einen String) gespeichert werden und wieder zurückgeschrieben werden.
Delphi-Quellcode:
//Dies sind nur Beispiele
//für die nonVCL-Variante bitte in der entspr. Unit nachlesen
type Tenum=(a,b,c,d);
//Nur zur Definition,
type Tset=set
of TEnum;
//kann und muss nicht mit dieser Unit gespeichert werden
type tx=record
i:integer;
s:
string;
end;
type Tmyrecord=packed
record
info:pointer;
f:
function:boolean
of object;
v1:variant;
v2:
array[1..2]
of variant;
v:
array of variant;
a:integer;
s:Tset;
a2:integer;
z:
array of tx;
x1,x2:
string;
i:integer;
d:double;
p:pointer;
t:
array of string;
q:
array[1..4]
of string;
nx:tx;
end;
type Tsa=array[1..3]
of Tmyrecord;
type Tda=array
of Tmyrecord;
type Tdx=array
of tx;
type TmyF=function:boolean
of object;
//nur zur Deklaration
type TmyInnerClass=class
InS:
string;
end;
type TMyClass=class(TMyInnerClass)
public
another_i:integer;
private
fi:integer;
fx:tmyF;
fs:
string;
fs2:
string;
frec:Tmyrecord;
fc:char;
fdx:Tdx;
published
// property kann aber ist hierfür irrelevant
end;
Wie man sieht, sind auch Klassen möglich. Dabei werden nicht die published properties beachtet,
sondern alle Variablen (inkl. aus abgeleiteten Klassen). Für mich war eigentlich nur das Record notwendig. Aber damit brauchte ich auch Variant, sowie statische und dynamische Arrays. Deswegen sind diese auch implementiert. Die Klassen sind noch aus eigenem Spieltrieb entstanden. Ob das mit den Klassen, notwendig ist, weis ich nicht. Vor allem sollte man da mächtig aufpassen, besonders wenn man die übergeordneten Klassen nicht selber geschrieben hat (siehe unten: Pointer-Variablen).
Wie funktioiert es
Die
Unit bietet nach außen zwei Funktionen: SaveToStream und LoadfromStream
Aufgerufen/benutzt werden sie so:
Delphi-Quellcode:
procedure saveRecord(rec:TMyRecord);
var stream:Tfilestream;
begin
stream:=TFileStream.create('Test.dat',fmcreate);
SaveToStream(rec,Stream,TYPEINFO(TMyRecord));
//hier können auch noch andere Records/Arrays/Klassen direkt angefügt werden
stream.free;
end;
procedure SaveArray(Arr:Tda);
var stream:Tfilestream;
begin
stream:=TFileStream.create('Test.dat',fmcreate);
SaveToStream(Arr,Stream,TYPEINFO(Tda));
stream.free;
end;
procedure SaveClass(const AObject:TObject);
var stream:Tfilestream;
begin
stream:=TFileStream.create('Test.dat',fmcreate);
SaveToStream(Aobject,Stream,Aobject.ClassInfo);
stream.free;
end;
//Wenn man den Urpsrung des Records nicht mehr kennt (ähnlich wie bei SaveClass)
//kann man sich mit einer Art info-Variable vom Typ Pointer am Anfang des
//Records behilflich sein.
//Ich habe das schonmal vorbereitet ;-)
procedure initRecord(var rec:tmyrecord);
begin
//wichtig ist, dass man irgendwo den info-Pointer setzt
rec.info:=typeinfo(Tmyrecord);
end;
procedure saveUnknwonRecord(rec:ppointer);
var Stream:TfileStream;
begin
Stream:=TFilestream.create('test.dat',fmcreate);
savetoStream(rec^,stream,rec^);
Stream.free;
end;
Was habe ich gemacht?
Da mir die
Unit TypInfo nur begrenzt weitergeholfen hat, habe ich einfach mal geschaut, was der Compiler so macht und mich versucht danach zu richten. Dementsprechend ist das auch auf den Compiler von Delphi 7 zugeschnitten. Aber ich denke / ich hoffe dass es auch in anderen die Versionen geht, und der Aufbau der
RTTI nicht geändert wurden. (Seit Delphi 4 sind alle Typen bekannt)
Als Basis brauche ich deswegen auch neben der Adresse des records (oder Klasse, etc) noch den Pointer zur
RTTI, den man mit TYPEINFO(TMyVariablenType) oder mit TObject.ClassInfo bekommt. Dort stehen dann u.a. eben diese wichtigen Informationen:
- Größe des Records
- Wo stehen dynamsiche Komponenten & Pointer zur
RTTI dieser dynamischen Komponente
So kann ich mich dann durch alle Variablen durchhangeln (wenn ein Array wiedrum Strings ode Records beinhaltet,...)
Interessant ist das Zurückschreiben des Streams (LoadFromStream). Da ich hier nicht nur den Inhalt des Records schreiben muss, sondern auch aufpassen, dass der alte gelöscht wird, und der neue Inhalt den richtigen Referenzzähler bekommt. Und Typen wie WideString und Variant sollen auch "
WINAPI-kompatibel" sein.
(Bei letzterem war besonders spannend, ein Array in einem Variant unterzubringen - Stichwort: SafeArray).
Was kann diese Unit nicht / Worauf ist zu achten
1. Besonders bei Klassen ist es wichtig zu beachten, dass nicht nur die Properties, sondern alle Variablen abgespeichert werden. Und beim Laden aus dem Stream, werden diese auch wieder zurückgeschrieben. Dabei könnten alle Pointer umgeschrieben werden, sodass sie ungültig werden. Dazu zählen neben den untypisierten Pointern (pointer) auch pcardinal,pchar,... alle Klassen und Interfaces. Diese zeigen dann nach dem Überschrieben durch LoadFromStream nicht nur ins Nirvana, sondern es bleiben auch Speicherlöcher, wenn diese Variablen die einzige Referenz auf reservierten Speicher waren. Der Grund liegt darin, dass zu pointern und Klassen keine Infos in der
RTTI liegen.
Dasselbe gilt für diese Typen, wenn sie in Records oder Arrays enthalten sind.
2. Diese
Unit kann generell keine Interfaces handhaben (noch nicht!)
In erster Instanz ist es wichtig, dass diese
Unit Records mit Strings und Arrays speichern kann. Das war die ursprüngliche Idee, und das werde ich wahrscheinlich auch demnächst brauchen.
So, ich hoffe ich habe nix vergessen.
PS: Ich danke denen, die die Funktionen mal überprüfen.
Edit1: Mein Beispielprojekt zum Rumspielen zusätzlich angehängt
Edit2: Beim Zusammenstellen der ZIP habe ich etwas geschlafen
Edit3+4: Neu Versionen hochgeladen (Typecasting für D6)
Edit5: Jetzt dürften auch Klassen funktionieren die nicht direkt von TObject abgeleitet sind
Edit6: Da hat ich gestern bei den Klassen etwas zu viel geändert
Edit7+8: kleine Anpassungen
Edit9:
nonVCL-
Unit ergänzt
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.