简体   繁体   English

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

[英]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. 目标:一个可读的配置类(如INI文件),但无需编写(并在添加新配置项后进行调整)加载和保存方法。

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 调用TMyConfiguration.Save(继承自TConfiguration)应该创建一个类似的文件

[Options]
ShowFlags=1
NumFlags=42

Question: What is the best way to do this? 问题:最好的方法是什么?

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

The base class could provide load and save methods for the delphi default types. 基类可以为delphi默认类型提供加载和保存方法。 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;       

I use XML for all my application as means of configuration. 我将XML用于我的所有应用程序作为配置方法。 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. 我有一个XML库,可以非常轻松地读取或修改配置,甚至无需查看缺失的值。 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. 现在,您还可以将XML映射到应用程序内的类,以便在速度成为问题时更快地访问,或者不断读取某些值。

I find other configuration methods far less optional: 我发现其他配置方法更不可选:

  • Ini file: no in depth structure, far less flexible Ini文件:没有深度结构,灵活性差得多
  • registry: just keep away from that. 注册表:只是远离那个。

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. 大多数情况下,实际实现不是主要形式,它只是一个占位符,我使用implements关键字将接口重定向到主窗体拥有的另一个对象。 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. 每个单元都不关心配置是存储在表,ini文件,xml文件中,还是存储在注册表中。 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. 所需要的只是将FORMS和我的全局接口单元添加到我正在使用的单元中。 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. 并且因为它不使用mainform,如果我决定稍后将其重新用于另一个项目,我不需要做任何进一步的更改....它只是工作,即使配置存储方案完全不同。

My general preference is to create a table (if I'm dealing with a database application) or an XML file. 我的一般偏好是创​​建一个表(如果我正在处理数据库应用程序)或XML文件。 If it is a multi-user database application, then I will create two tables. 如果它是一个多用户数据库应用程序,那么我将创建两个表。 One for global configuration, and another for user configuration. 一个用于全局配置,另一个用于用户配置。

Basically you are asking for a solution to serialize a given object (in your case a configurations to ini files). 基本上你要求一个序列化给定对象的解决方案(在你的情况下是对ini文件的配置)。 There are ready made components for that and you can start looking here and here . 有现成的组件,你可以开始在这里这里看

Sometime ago I wrote small unit for same task - to save/load the configuration of application in xml-file. 前段时间我为同一个任务写了一个小单元 - 在xml文件中保存/加载应用程序的配置。

Check the Obj2XML.pas unit in our freeware SMComponent library: http://www.scalabium.com/download/smcmpnt.zip 检查我们的免费软件SMComponent库中的Obj2XML.pas单元: http//www.scalabium.com/download/smcmpnt.zip

This would be for Java. 这将是Java。

I like to use the java.util.Properties class for reading in config files or properties files. 我喜欢使用java.util.Properties类来读取配置文件或属性文件。 What I like is that you put your file with lines in the same way you showed above (key=value). 我喜欢的是你用你上面显示的相同方式(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. 很简单。

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. 尼克斯回答(使用Java属性)有一点:这种在应用程序各部分之间读取和传递配置的简单方法不会引入特殊配置类的依赖关系。 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. 在Delphi中,基于TStrings的简单配置是实现配置的简便方法。 Example: 例:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM