Einzelnen Beitrag anzeigen

Benutzerbild von sirius
sirius

Registriert seit: 3. Jan 2007
Ort: Dresden
3.443 Beiträge
 
Delphi 7 Enterprise
 
#1

Records / Arrays /(Klassen) mit dynamischen Inhalt speichern

  Alt 2. Jul 2007, 22:53
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
Angehängte Dateien
Dateityp: pas u_store_dynamic_params_117.pas (26,1 KB, 232x aufgerufen)
Dateityp: zip delphirtti_142.zip (10,8 KB, 171x aufgerufen)
Dateityp: pas u_store_dynamic_params_nonvcl_105.pas (28,2 KB, 121x aufgerufen)
Dieser Beitrag ist für Jugendliche unter 18 Jahren nicht geeignet.
  Mit Zitat antworten Zitat