RSS
 

Objekte serealisieren und deserialisieren

Veröffentlicht am Juni 22, 2008 um 12:05 pm

22 Jun

Schönen guten Tag liebe Delphi Gemeinde!
Was in vielen “modernen” Sprachen bereits gang und gäbe ist und zum guten Ton gehört haben wir nicht zur Verfügung, das serealisieren und deserialisieren von Objekten. Mit hilfe der RTTI (Run Time Type Info) und einigen Einschränkungen gibt es jedoch auch für uns die Möglichkeit derartige Mechanismen zu schaffen. Im folgendem möchte ich gerne meinen Lösungsansatz vorstellen, der mir und vielleicht sogar dem ein oder anderem Arbeit erspart oder zumindest Hilfestellung gibt.

Da ich in der Entwicklung alle meine Objekte mit dieser Funktion ausstatten möchte, muss ich mir eine eigene Classroot schaffen. Ich habe mich an dieser Stelle für den Typ TPersistent als Vaterklasse entschieden, da mir dieser die Methode Assign() zur Verfügung stellt.

type
  TeqObject = class( TPersistent )

In meiner eigenen Entwicklung leite ich nun alle meine benötigten Klassen von TeqObject ab.
Ein einfaches Beispiel soll die Anwendung und den Nutzen demonstrieren.

type TMitarbeiter = class( TeqObject )
  private
    M_Vorname : String;
    M_Nachname : String;
    M_Alter : Integer;
  published
    property Vorname : String read M_Vorname write M_Vorname;
    property Nachname : String read M_Nachname write M_Nachname;
    property Alter : Integer read M_Alter write M_Alter;
end;
 
procedure ErzeugeMitarbeiter;
var
  mitarbeiter : TMitarbeiter;
begin
  mitarbeiter := TMitarbeiter.Create;
  with mitarbeiter do begin
    Vorname  := 'Pascal';
    Nachname := 'Potrafke';
    Alter    := 24;
    SaveAsXML( 'mitarbeiter.xml' );
  end;
  FreeAndNil( mitarbeiter );
end;

Was habe ich nun gemacht? Ich habe ein Klasse erstellt, von dieser ein Objekt instanziiert und dieses einfach als XML gespeichert. Das ergebnis sieht wie folgt aus.

  24
  Potrafke
  Pascal

Die Delphi RTTI erlaubt es nur auf Eigenschaften zuzugreifen die im published Block definiert sind. Es werden also nicht alle Member Eigenschaften serialisiert. Das ist auch der Grund dafür, dass in dem Beispiel nur die Properties in der XML Datei zu sehen sind.
Was serialisiert wurde kann natürlich auch wieder deserialisiert werden. Dafür gibt es das obligatorische Gegenstück zur allen Save() Methoden in Delphi, nämlich die Load() Methoden.

mitarbeiter.LoadFromXML( 'mitarbeiter.xml' );

Soweit so gut, allerdings gibt es auch die ein oder andere Einschränkung. Im Detail bedeutet das, dass nicht für alle Typen von Borland RTTI Informationen zur Laufzeit erzeugt werden.
Dazu gehören tkRecord, tkArray, tkDynArray (bedingt) und Pointer jeglicher Art.

    tkArray: Delphi brauch hier keine Informationen, da normale (statische) Arrays kein Cleanup brauchen.
    tkDynArray: Für tkDynArray wird zwar eine RTTI Information zur Laufzeit erzeugt, diese dient Delphi jedoch nur um bei dem Speicher Management die Infos zu bekommen wieviele Einträge es von welchem Typ aufräumen muss.

Ein kurzer Ausschnitt aus der TypeInfo.pas (Delphi 2007 Version 11) soll die fehlenden Implementationen zeigen.

  TTypeData = packed record
    case TTypeKind of
      tkUnknown, tkLString, tkWString, tkVariant: ();
      tkInteger, tkChar, tkEnumeration, tkSet, tkWChar: (
        OrdType: TOrdType;
        case TTypeKind of
          tkInteger, tkChar, tkEnumeration, tkWChar: (
            MinValue: Longint;
            MaxValue: Longint;
            case TTypeKind of
              tkInteger, tkChar, tkWChar: ();
              tkEnumeration: (
                BaseType: PPTypeInfo;
                NameList: ShortStringBase;
                EnumUnitName: ShortStringBase));
          tkSet: (
            CompType: PPTypeInfo));
      tkFloat: (
        FloatType: TFloatType);
      tkString: (
        MaxLength: Byte);
      tkClass: (
        ClassType: TClass;
        ParentInfo: PPTypeInfo;
        PropCount: SmallInt;
        UnitName: ShortStringBase;
       {PropData: TPropData});
      tkMethod: (
        MethodKind: TMethodKind;
        ParamCount: Byte;
        ParamList: array[0..1023] of Char
       {ParamList: array[1..ParamCount] of
          record
            Flags: TParamFlags;
            ParamName: ShortString;
            TypeName: ShortString;
          end;
        ResultType: ShortString});
      tkInterface: (
        IntfParent : PPTypeInfo; { ancestor }
        IntfFlags : TIntfFlagsBase;
        Guid : TGUID;
        IntfUnit : ShortStringBase;
       {PropData: TPropData});
      tkInt64: (
        MinInt64Value, MaxInt64Value: Int64);
      tkDynArray: (
        elSize: Longint;
        elType: PPTypeInfo;       // nil if type does not require cleanup
        varType: Integer;         // Ole Automation varType equivalent
        elType2: PPTypeInfo;      // independent of cleanup
        DynUnitName: ShortStringBase);
  end;

Das hat zur Folge, das wenn man dieses Feature verwenden möchte, sich von Records und (dynamischen) Arrays verabschieden muss.
Anstelle von Records werden nun Klassen verwendet. Was bereits beim Mitarbeiter Beispiel gemacht wurde. Denn für solch einfache Strukturen hätte es eigentlich auch ein normaler Record getan.
Dennoch denke ich das dieser geringe Mehraufwand bei der Definition in hinsicht auf das (de)serialisieren kaum ins Gewicht fällt.
(Dynamische) Arrays werden nun durch eine weitere Classroot ersetzt.

type TDyneqObjectList = array of TeqObject;
 
type
  TeqObjectList = class( TeqObject )
  private
    M_List : TDyneqObjectList;
...
...
...

Mit dieser Listenklassen können Array Strukturen abgebildet werden und das (de)serialisieren Feature weiterhin bequem verwendet werden. Folgendes Beispiel soll die Listenklasse demonstrieren.

type TUnternehmen = class( TeqObject )
  private
    M_Firmenname : String;
    M_Internetadresse : String;
    M_Mitarbeiter : TeqObjectList;
  public
    constructor Create; override;
  published
    property Firmenname : String read M_Firmenname write M_Firmenname;
    property Internetadresse : String read M_Internetadresse write M_Internetadresse;
    property Mitarbeiter : TeqObjectList read M_Mitarbeiter write M_Mitarbeiter;
end;
 
type TMitarbeiter = class( TeqObject )
  private
    M_Vorname : String;
    M_Nachname : String;
    M_Alter : Integer;
  published
    property Vorname : String read M_Vorname write M_Vorname;
    property Nachname : String read M_Nachname write M_Nachname;
    property Alter : Integer read M_Alter write M_Alter;
end;
 
constructor TUnternehmen.Create;
begin
  inherited Create;
 
  Self.M_Mitarbeiter := TeqObjectList.Create;
end;
 
procedure ErzeugeUnternehmen;
var
  unternehmen : TUnternehmen;
begin
  unternehmen := TUnternehmen.Create;
  with unternehmen do begin
    Firmenname      := 'EQUITANIA Software GmbH';
    Internetadresse := 'http://www.equitania.de';
    with ( Mitarbeiter.Item[ Mitarbeiter.Add( TMitarbeiter.Create ) ] as TMitarbeiter ) do begin
      Vorname  := 'Pascal';
      Nachname := 'Potrafke';
      Alter    := 24;
    end;
    with ( Mitarbeiter.Item[ Mitarbeiter.Add( TMitarbeiter.Create ) ] as TMitarbeiter ) do begin
      Vorname  := 'Paul';
      Nachname := 'Exler';
      Alter    := 24;
    end;
    SaveAsXML( 'unternehmen.xml' );
  end;
end;

Die List funktioniert wie eine StringList und besitzt Methode wie Add(), Remove(), Item, etc.
Das Ergebnis sieht nun wie folgt aus.

  EQUITANIA Software GmbH
  http://www.equitania.de
 
      24
      Potrafke
      Pascal
 
      24
      Exler
      Paul

Von der Komplexität der Klassenstrukturen gibt es keinerlei Einschränkungen.
Auch das Einlesen dieses XML Gebildes ist kein Problem. Einzige Schwierigkeit war hier, das beim einlesen die Objekte IN der Liste von dem jeweiligen Typ instanziiert werden müssen damit zur Laufzeit drauf zurückgegriffen werden kann. Aber auch darum muss man sich nicht mehr kümmern.
Was man jedoch derzeit noch von Hand machen musst ist das registrieren der Klassen. Ohne dem werden bzw. können die Objekte nicht instanziiert werden.

initialization
  RegisterClasses( [ TUnternehmen, TMitarbeiter ] );

So, nun wünsche ich viel Spass beim ausprobieren.
Eines möchte ich noch sagen. Der Code kann Fehler enthalten. Daher ist die Benutzung auf eigene Gefahr. Verbesserungsvorschläge und Anregungen gerne gesehen.
Sourcecode zum Download

 
Keine Kommentare

Geposted in Delphi

 

Tags:

Hinterlassen Sie eine Nachricht