【问题标题】:What is the best way to serialize Delphi application configuration?序列化 Delphi 应用程序配置的最佳方法是什么?
【发布时间】:2010-11-20 13:54:38
【问题描述】:

我会自己回答这个问题,但如果你比我快或者你不喜欢我的解决方案,请随时提供你的答案。我只是想出了这个想法,想对此发表一些意见。

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

我想创建一个类

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

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

【问题讨论】:

    标签: delphi configuration-files rtti


    【解决方案1】:

    尼克斯的回答(使用 Java 属性)有一点:这种在应用程序的各个部分之间读取和传递配置的简单方法不会引入对特殊配置类的依赖关系。一个简单的key/value列表可以减少应用模块之间的依赖,让代码复用更容易。

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

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        【解决方案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 和我的全局接口单元添加到我正在处理的单元中。而且因为它不使用主窗体,所以如果我决定稍后将它重用于另一个项目,我不必做任何进一步的更改......它就可以工作,即使配置存储方案完全不同。

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

        【讨论】:

        • +1,因为我同意你写的大部分内容,差异主要是风格问题。但是目前的答案与这个问题没有太大关系,我理解为“编写一个能够从持久存储中加载自身并将自身存储到持久存储而不需要更改后代的基类的最佳方法是什么?课”。这至少是OP回答的问题,标题与问题文本不匹配。尽管如此,ssaster 应该听从你的建议,而不是硬编码一个 INI 文件解决方案。
        • 我希望我的配置设置花费尽可能少的时间,因为它会在每个应用程序中重复。虽然我在某种程度上喜欢你的方法,但对我来说 KISS 还不够。在大多数情况下,带有 XML 后端的简单全局单例和良好的访问接口就足够了。对于小型简单的应用程序来说绰绰有余。即使是完全面向数据库的应用程序仍然需要一个简单的配置文件来访问该数据:)
        • 我完全同意 mghie 的评论。您提出了一个使用接口和委托来替代全局变量的好方法,但您对如何执行序列化只字未提。从我的解决方案中提取 IPropertyStorer 以将配置与具体实现(INI 文件/XML/数据库)分开是没有问题的。但这并没有改变关于持久存储的要点。
        • @mghie:我更新了标题。我同意将 INI 文件的内容分解出来会很好。
        【解决方案4】:

        我在所有应用程序中都使用 XML 作为配置方式。它是:

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

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

        我发现其他配置方法的可选性要少得多:

        • Ini 文件:没有深度结构,灵活性差很多
        • 注册表:远离那个。

        【讨论】:

        • 我的做法大致相同,但我使用 Delphi 的 XML 数据绑定向导将文件映射到对象。
        • 问题是:我认为INI文件即使是用户也很容易理解,但并不是每个人都能理解XML。我知道我应该为此设置设置对话框,但我希望任何人都能够更改更高级的设置,而无需深入研究 XML。
        • XML 文件非常容易阅读。我在这里谈论基本的 XML。只是具有属性和值(文本)的节点。与 INI 相比,它的可读性几乎没有降低。好的,标签中有更多“锅炉电镀”。但是,如果您担心用户修改,那么为他们制作一个 GUI。您不能依靠用户自己修改文件。甚至是 INI。
        • 你可能是对的,但在我的情况下,用户习惯于使用 INI 文件,我想保留它。如果我以如此平坦的方式(没有深度等)使用 XML,那么文件中就会有很多开销。
        • 如果用户必须手动编辑配置文件,你已经输掉了游戏。
        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          这适用于 Java。

          我喜欢使用java.util.Properties 类来读取配置文件或属性文件。我喜欢的是您以与上面显示的方式相同的方式将文件放入行中(键=值)。 此外,它使用#(井号)作为注释行,有点像许多脚本语言。

          所以你可以使用:

          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);
          

          就这么简单。

          【讨论】:

          • 没有太大帮助,因为我要求使用 Delphi,而您建议的类是 Java 特定的......
          • 是的,对此感到抱歉。你实际上没有具体说明,所以我试了一下。问题的措辞非常笼统。没有看到你的 delphi 标签。
          【解决方案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 方法如下所示(相应的 Save 方法):

          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;       
          

          【讨论】:

          • 这对我来说看起来还不错。是什么让您认为可能有更智能的解决方案?
          • @Jeroen:在大多数情况下,当我在这里提问时,我得到了很多聪明的 cmets、改进建议和批评 :) 除此之外,我想分享那段代码,所以其他人可能会受益。
          猜你喜欢
          • 1970-01-01
          • 2010-10-01
          • 1970-01-01
          • 1970-01-01
          • 2018-08-22
          • 1970-01-01
          • 2010-10-06
          • 2010-09-19
          相关资源
          最近更新 更多