Представление INI с помощью RTTI
Этот пост основывается на коде модуля IniPersit.pas, недавно реализованного мною.
Обычно я делаю конфигурационные классы для создания общей и легкодоступной информации, хранящейся в INI, реестре или в XML. В данных примерах я покажу, как использовать RTTI и атрибуты в Delphi 2010, для того, что бы получить новый способ создания конфигурационного класса, имеющего доступ к информации, хранящейся в INI файле.
Давайте, прежде всего, посмотрим, как использовать Новый Модуль, а затем мы сможем приоткрыть завесу, что бы посмотреть, как это работает.
unit ConfigSettings; interface uses IniPersist; type TConfigSettings = class (TObject) private FConnectString: String; FLogLevel: Integer; FLogDirectory: String; FSettingsFile: String; public constructor create; // Use the IniValue attribute on any property or field // you want to show up in the INI File. [IniValue('Database','ConnectString','')] property ConnectString : String read FConnectString write FConnectString; [IniValue('Logging','Level','0')] property LogLevel : Integer read FLogLevel write FLogLevel; [IniValue('Logging','Directory','')] property LogDirectory : String read FLogDirectory write FLogDirectory; property SettingsFile : String read FSettingsFile write FSettingsFile; procedure Save; procedure Load; end; implementation uses SysUtils; { TApplicationSettings } constructor TConfigSettings.create; begin FSettingsFile := ExtractFilePath(ParamStr(0)) + 'settings.ini'; end; procedure TConfigSettings.Load; begin // This loads the INI File Values into the properties. TIniPersist.Load(FSettingsFile,Self); end; procedure TConfigSettings.Save; begin // This saves the properties to the INI TIniPersist.Save(FSettingsFile,Self); end; end.
program Project13; {$APPTYPE CONSOLE} uses SysUtils, IniPersist, ConfigSettings; var Settings : TConfigSettings; begin Settings := TConfigSettings.Create; try Settings.ConnectString := '\\127.0.0.1\DB:2032'; Settings.LogLevel := 3; Settings.LogDirectory := 'C:\Log'; Settings.Save; finally Settings.Free; end; Settings := TConfigSettings.Create; try Settings.Load; WriteLn(Settings.ConnectString); Writeln(Settings.LogLevel); Writeln(Settings.LogDirectory); finally Settings.Free; end; Readln; end.
Результат:
\\127.0.0.1\DB:2032 3 C:\Log
Результирующий INI файл:
[Database] ConnectString=\\127.0.0.1\DB:2032 [Logging] Level=3 Directory=C:\Log
Как вы можете видеть в приведенном выше коде, это, действительно, не слишком сложно , если вы хотите сохранить в INI файле поле или свойство, вы сразу же должны добавить IniValue атрибут.
[Database] ConnectString=\\127.0.0.1\DB:2032 [Logging] Level=3 Directory=C:\Log
Конструктор IniValue позволяет вам указать Секцию(Section), Имя (Name) поля или свойства, которое будет сохраняться. Он также позволит вам задать текущее значение если имя и секция не существуют в INI файле.
IniValueAttribute = class(TCustomAttribute) private FName: string; FDefaultValue: string; FSection: string; published constructor Create(const aSection : String;const aName : string;const aDefaultValue : String = ''); property Section : string read FSection write FSection; property Name : string read FName write FName; property DefaultValue : string read FDefaultValue write FDefaultValue; end; ... constructor IniValueAttribute.Create(const aSection, aName, aDefaultValue: String); begin FSection := aSection; FName := aName; FDefaultValue := aDefaultValue; end;
Вся “магия” действительно находится в TIniPersist.
TIniPersist = class (TObject) private class procedure SetValue(aData : String;var aValue : TValue); class function GetValue(var aValue : TValue) : String; class function GetIniAttribute(Obj : TRttiObject) : IniValueAttribute; public class procedure Load(FileName : String;obj : TObject); class procedure Save(FileName : String;obj : TObject); end;
Методы загрузки и сохранения (Load and Save) почти одинаковы, так, что давайте рассмотрим загрузку.
class procedure TIniPersist.Load(FileName: String; obj: TObject); var ctx : TRttiContext; objType : TRttiType; Field : TRttiField; Prop : TRttiProperty; Value : TValue; IniValue : IniValueAttribute; Ini : TIniFile; Data : String; begin ctx := TRttiContext.Create; try Ini := TIniFile.Create(FileName); try objType := ctx.GetType(Obj.ClassInfo); for Prop in objType.GetProperties do begin IniValue := GetIniAttribute(Prop); if Assigned(IniValue) then begin Data := Ini.ReadString(IniValue.Section,IniValue.Name,IniValue.DefaultValue); Value := Prop.GetValue(Obj); SetValue(Data,Value); Prop.SetValue(Obj,Value); end; end; for Field in objType.GetFields do begin IniValue := GetIniAttribute(Field); if Assigned(IniValue) then begin Data := Ini.ReadString(IniValue.Section,IniValue.Name,IniValue.DefaultValue); Value := Field.GetValue(Obj); SetValue(Data,Value); Field.SetValue(Obj,Value); end; end; finally Ini.Free; end; finally ctx.Free; end; end;
Как вы можете видеть, мы, по сути, в цикле перебираем все свойства и поля, проверяя атрибуты, и если они существуют, мы берем текущие значения, которые установлены в TypeInfo в TValue объекте. Затем мы определяем строку, возвращаемую из INI файла в TValue, и вызываем SetValue().
Давайте посмотрим на два метода, которые вызываются.
class procedure SetValue(aData : String;var aValue : TValue); class function GetIniAttribute(Obj : TRttiObject) : IniValueAttribute;
Сперва взглянем на SetValue(). Вы можете увидеть, что он зависит от представления TypeInfo, переданного в TValue. Мы проверяем TValue и выполняем необходимое преобразование, для приведения строки (String) к корректному типу, перед сохранением.
class procedure TIniPersist.SetValue(aData: String;var aValue: TValue); var I : Integer; begin case aValue.Kind of tkWChar, tkLString, tkWString, tkString, tkChar, tkUString : aValue := aData; tkInteger, tkInt64 : aValue := StrToInt(aData); tkFloat : aValue := StrToFloat(aData); tkEnumeration: aValue := TValue.FromOrdinal(aValue.TypeInfo,GetEnumValue(aValue.TypeInfo,aData)); tkSet: begin i := StringToSet(aValue.TypeInfo,aData); TValue.Make(@i, aValue.TypeInfo, aValue); end; else raise EIniPersist.Create('Type not Supported'); end; end;
Теперь давайте рассмотрим GetIniAttribute(). Цель этого метода проконтролировать и выявить случаи, когда TRttimember (поле или свойство) имеет атрибут IniValue, и если это так – возвратить его, в противном случае возвратить NIL.
class function TIniPersist.GetIniAttribute(Obj: TRttiObject): IniValueAttribute; var Attr: TCustomAttribute; begin for Attr in Obj.GetAttributes do begin if Attr is IniValueAttribute then begin exit(IniValueAttribute(Attr)); // Exit with a parameter new in Delphi 2010 end; end; result := nil; end;
В общем, это и все, здесь действительно не много кода, и этот код делает использование (RTTI) более простым. В данном случае обработка TIniValue не так уж сложна, но этот подход может быть применен и к другим приложениям.









One Comments to “Представление INI с помощью RTTI”
Вот здесь очень пригодится применение паттернов проектирования – http://fast-base.ru/blogger/?p=6