Search code examples
delphiconfiguration-filesrtti

What is the best way to serialize Delphi application configuration?


I will answer this question myself, but feel free to provide your answers if you are faster than me or if you don't like my solution. I just came up with this idea and would like to have some opinions on that.

Goal: a configuration class that is readable (like an INI-file) but without having to write (and adapt after a new configuration item has been added) the load and save methods.

I want to create a class like

TMyConfiguration = class (TConfiguration)
  ...
  property ShowFlags : Boolean read FShowFlags write FShowFlags;
  property NumFlags : Integer read FNumFlags write FNumFlags;
end;

Calling TMyConfiguration.Save (inherited from TConfiguration) should create a file like

[Options]
ShowFlags=1
NumFlags=42

Question: What is the best way to do this?


Solution

  • This is my proposed solution.

    I have a base class

    TConfiguration = class
    protected
      type
        TCustomSaveMethod = function  (Self : TObject; P : Pointer) : String;
        TCustomLoadMethod = procedure (Self : TObject; const Str : String);
    public
      procedure Save (const FileName : String);
      procedure Load (const FileName : String);
    end;
    

    The Load methods look like this (Save method accordingly):

    procedure TConfiguration.Load (const FileName : String);
    const
      PropNotFound = '_PROP_NOT_FOUND_';
    var
      IniFile : TIniFile;
      Count : Integer;
      List : PPropList;
      TypeName, PropName, InputString, MethodName : String;
      LoadMethod : TCustomLoadMethod;
    begin
      IniFile := TIniFile.Create (FileName);
      try
        Count := GetPropList (Self.ClassInfo, tkProperties, nil) ;
        GetMem (List, Count * SizeOf (PPropInfo)) ;
        try
          GetPropList (Self.ClassInfo, tkProperties, List);
          for I := 0 to Count-1 do
            begin
            TypeName  := String (List [I]^.PropType^.Name);
            PropName  := String (List [I]^.Name);
            InputString := IniFile.ReadString ('Options', PropName, PropNotFound);
            if (InputString = PropNotFound) then
              Continue;
            MethodName := 'Load' + TypeName;
            LoadMethod := Self.MethodAddress (MethodName);
            if not Assigned (LoadMethod) then
              raise EConfigLoadError.Create ('No load method for custom type ' + TypeName);
            LoadMethod (Self, InputString);
            end;
        finally
          FreeMem (List, Count * SizeOf (PPropInfo));
        end;
      finally
        FreeAndNil (IniFile);
      end;
    

    The base class could provide load and save methods for the delphi default types. I can then create a configuration for my application like this:

    TMyConfiguration = class (TConfiguration)
    ...
    published
      function  SaveTObject (P : Pointer) : String;
      procedure LoadTObject (const Str : String);
    published
      property BoolOption : Boolean read FBoolOption write FBoolOption;
      property ObjOption : TObject read FObjOption write FObjOption;
    end;
    

    Example of a custom save method:

    function TMyConfiguration.SaveTObject (P : Pointer) : String;
    var
      Obj : TObject;
    begin
      Obj := TObject (P);
      Result := Obj.ClassName;  // does not make sense; only example;
    end;