序列化Delphi应用程序配置的最佳方法是什么?

时间:2009-08-18 12:24:01

标签: delphi configuration-files rtti

我会自己回答这个问题,但如果你比我快或者你不喜欢我的解决方案,请随时提供你的答案。我想出了这个想法,并希望对此有一些看法。

目标:一个可读的配置类(如INI文件),但无需编写(并在添加新配置项后进行调整)加载和保存方法。

我想创建一个像

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

调用TMyConfiguration.Save(继承自TConfiguration)应该创建一个像

这样的文件
[Options]
ShowFlags=1
NumFlags=42

问题:最好的方法是什么?

7 个答案:

答案 0 :(得分:7)

这是我提出的解决方案。

我有一个基类

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;

Load方法如下所示(相应地保存方法):

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;

基类可以为delphi默认类型提供加载和保存方法。然后,我可以为我的应用程序创建一个配置:

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;

自定义保存方法的示例:

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

答案 1 :(得分:6)

我将XML用于我的所有应用程序作为配置方法。它是:

  • 灵活
  • 未来功能证明
  • 任何文本阅读器都易于阅读
  • 在应用中非常容易扩展。无需修改课程

我有一个XML库,可以非常轻松地读取或修改配置,甚至无需查看缺失的值。现在,您还可以将XML映射到应用程序内的类,以便在速度成为问题时更快地访问,或者不断读取某些值。

我发现其他配置方法更不可选:

  • Ini档案:没有深度结构,灵活性差得多。
  • 注册表:远离那个。

答案 2 :(得分:3)

我首选的方法是在全局接口单元中创建一个接口:

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

然后以我的主要形式“暴露”此界面:

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

大多数情况下,实际实现不是主要形式,它只是一个占位符,我使用implements关键字将接口重定向到主窗体拥有的另一个对象。重点是配置的责任。每个单元都不关心配置是存储在表,ini文件,xml文件中,还是存储在注册表中。这使我能够在任何使用全局接口单元的单元中进行调用,如下所示:

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

所需要的只是将FORMS和我的全局接口单元添加到我正在处理的单元中。并且因为它不使用mainform,如果我决定稍后将其重新用于另一个项目,我不需要做任何进一步的更改....它只是工作,即使配置存储方案完全不同。 / p>

我的首选是创建一个表(如果我正在处理数据库应用程序)或XML文件。如果它是一个多用户数据库应用程序,那么我将创建两个表。一个用于全局配置,另一个用于用户配置。

答案 3 :(得分:1)

基本上你要求一个序列化给定对象的解决方案(在你的情况下是对ini文件的配置)。有现成的组件,您可以开始查看herehere

答案 4 :(得分:1)

前段时间我为同一个任务写了一个小单元 - 在xml文件中保存/加载应用程序的配置。

检查我们的免费软件SMComponent库中的Obj2XML.pas单元: http://www.scalabium.com/download/smcmpnt.zip

答案 5 :(得分:0)

这适用于Java。

我喜欢使用java.util.Properties类来读取配置文件或属性文件。我喜欢的是你用你上面显示的相同方式(key = value)放置你的文件。  此外,它使用#(井号)作为评论的行,有点像许多脚本语言。

所以你可以使用:

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

然后你只需要代码:

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

这很容易。

答案 6 :(得分:0)

Nicks回答(使用Java Properties)有一点:这种在应用程序各部分之间读取和传递配置的简单方法不会引入特殊配置类的依赖关系。简单的键/值列表可以减少应用程序模块之间的依赖关系,并使代码重用更容易。

在Delphi中,基于TStrings的简单配置是实现配置的简便方法。例如:

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