What is the best way to serialize Delphi application configuration?

狂风中的少年 提交于 2019-12-03 07:04:26

问题


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?


回答1:


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;       



回答2:


I use XML for all my application as means of configuration. It is:

  • flexible
  • future feature proof
  • easy to read with any text reader
  • very easy to extend in application. No class modifications needed

I have an XML library that makes it extremely easy to read or modify configuration, without even having to watch for missing values. Now you can also map the XML to a class inside application for faster access if speed is the issue, or certain values are read constantly.

I find other configuration methods far less optional:

  • Ini file: no in depth structure, far less flexible
  • registry: just keep away from that.



回答3:


My preferred method is to create an interface in my global interfaces unit:

type
  IConfiguration = interface
    ['{95F70366-19D4-4B45-AEB9-8E1B74697AEA}']
    procedure SetConfigValue(const Section, Name,Value:String);
    function GetConfigValue(const Section, Name:string):string;
  end;

This interface is then "exposed" in my main form:

type
  tMainForm = class(TForm,IConfiguration)
  ...
  end;

Most of the time the actual implementation is not in the main form, its just a place holder and I use the implements keyword to redirect the interface to another object owned by the main form. The point of this is that the responsibility of configuration is delegated. Each unit doesn't care if the configuration is stored in a table, ini file, xml file, or even (gasp) the registry. What this DOES allow me to do in ANY unit which uses the global interfaces unit is make a call like the following:

var
  Config : IConfiguration;
  Value : string;
begin
  if Supports(Application.MainForm,IConfiguration,Config) then
    value := Config.GetConfiguration('section','name');
  ...      
end;

All that is needed is adding FORMS and my global interfaces unit to the unit I'm working on. And because it doesn't USE the mainform, if I decide to later reuse this for another project, I don't have to do any further changes....it just works, even if the configuration storage scheme is completely different.

My general preference is to create a table (if I'm dealing with a database application) or an XML file. If it is a multi-user database application, then I will create two tables. One for global configuration, and another for user configuration.




回答4:


Basically you are asking for a solution to serialize a given object (in your case a configurations to ini files). There are ready made components for that and you can start looking here and here.




回答5:


Sometime ago I wrote small unit for same task - to save/load the configuration of application in xml-file.

Check the Obj2XML.pas unit in our freeware SMComponent library: http://www.scalabium.com/download/smcmpnt.zip




回答6:


This would be for Java.

I like to use the java.util.Properties class for reading in config files or properties files. What I like is that you put your file with lines in the same way you showed above (key=value). Also, it uses a # (pound sign) for a line thats a comment, kind of like a lot of scripting languages.

So you could use:

ShowFlags=true
# this line is a comment    
NumFlags=42

etc

Then you just have code like:

Properties props = new Properties();
props.load(new FileInputStream(PROPERTIES_FILENAME));
String value = props.getProperty("ShowFlags");
boolean showFlags = Boolean.parseBoolean(value);

Easy as that.




回答7:


Nicks answer (using Java Properties) has a point: this simple way to read and pass configuration around between parts of the application does not introduce dependencies on a special configuration class. A simple key/value list can reduce the dependencies between application modules and make code reuse easier.

In Delphi, a simple TStrings-based configuration is an easy way to implement a configuration. Example:

mail.smtp.host=192.168.10.8    
mail.smtp.user=joe    
mail.smtp.pass=*******


来源:https://stackoverflow.com/questions/1293504/what-is-the-best-way-to-serialize-delphi-application-configuration

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!