Commits

Anonymous committed e02d69f

Added support for registry storage and to stack storages in a hierarchy to support default values.

Comments (0)

Files changed (8)

 {$IMPLICITBUILD ON}
 
 requires
-  rtl;
+  rtl,
+  asutils;
 
 contains
-  ASConfig in '..\Src\ASConfig.pas';
+  ASConfig in '..\Src\ASConfig.pas',
+  ASConfigInterface in '..\Src\ASConfigInterface.pas',
+  ASConfig.IniFileEx in '..\Src\ASConfig.IniFileEx.pas';
 
 end.
 				<MainSource>MainSource</MainSource>
 			</DelphiCompile>
 			<DCCReference Include="rtl.dcp"/>
+			<DCCReference Include="asutils.dcp"/>
 			<DCCReference Include="..\Src\ASConfig.pas"/>
+			<DCCReference Include="..\Src\ASConfigInterface.pas"/>
+			<DCCReference Include="..\Src\ASConfig.IniFileEx.pas"/>
 			<BuildConfiguration Include="Release">
 				<Key>Cfg_2</Key>
 				<CfgParent>Base</CfgParent>

Src/ASConfig.IniFileEx.pas

+unit ASConfig.IniFileEx;
+
+{$IFDEF DEBUG}
+{$DEFINE DEBUG_INIFILE}
+{$ENDIF}
+
+interface
+
+uses
+  Classes, IniFiles, ASConfigInterface;
+
+type
+  TASConfigIniFileEx = class(TCustomIniFile, IASConfigFile)
+  private
+    FIni: TCustomIniFile;
+    FParentFile: IASConfigFile;
+
+    function GetIniFile: TCustomIniFile;
+    function GetParentFile: IASConfigFile;
+    procedure SetParentFile(const Value: IASConfigFile);
+
+  protected
+    procedure MergeStrings(Dest: TStrings; const Src: TStrings);
+    function GetFileName: string;
+
+  protected
+    { IInterface }
+    FRefCount: Integer;
+    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
+    function _AddRef: Integer; stdcall;
+    function _Release: Integer; stdcall;
+
+  public
+    { IInterface }
+    procedure AfterConstruction; override;
+    procedure BeforeDestruction; override;
+    class function NewInstance: TObject; override;
+    property RefCount: Integer read FRefCount;
+
+  public
+    constructor Create(const FileName: string; const IniFile: TCustomIniFile; const ParentFile: IASConfigFile = nil);
+    destructor Destroy; override;
+    function AddDefaultSection(const Section: string; AtDepth: Integer = 0; AsRoot: Boolean = False): IASConfigFile;
+    function GetSaveFileName: string;
+
+    function ReadString(const Section, Ident, Default: string): string; override;
+    procedure WriteString(const Section, Ident, Value: String); override;
+
+    procedure ReadSection(const Section: string; Strings: TStrings); override;
+    procedure ReadSections(Strings: TStrings); overload; override;
+    procedure ReadSections(const Section: string; Strings: TStrings); overload; override;
+    procedure ReadSectionValues(const Section: string; Strings: TStrings); override;
+    procedure EraseSection(const Section: string); override;
+    procedure DeleteKey(const Section, Ident: String); override;
+    procedure UpdateFile; override;
+
+    function ToString: string; override;
+
+    property IniFile: TCustomIniFile read GetIniFile;
+    property ParentFile: IASConfigFile read GetParentFile write SetParentFile;
+  end;
+
+implementation
+
+uses
+  SysUtils, Windows, Registry, ASStringsHelper, ASDebug;
+
+{ TASConfigIniFileEx }
+
+function TASConfigIniFileEx.AddDefaultSection(const Section: string; AtDepth: Integer; AsRoot: Boolean): IASConfigFile;
+var
+  S: string;
+  I: TCustomIniFile;
+  Root: IASConfigFile;
+begin
+  Root := Self;
+
+  if (AtDepth <> 0) and Assigned(ParentFile) then begin
+    Result := ParentFile.AddDefaultSection(Section, AtDepth - 1, AsRoot);
+  end else begin
+    S := Section;
+    if (S[1] <> '\') then begin
+      S := IniFile.FileName + '\' + S;
+      S := ExpandFileName('\\.' + S);
+      S := Copy(S, 4, Length(S));
+    end;
+
+    I := nil;
+    if IniFile is TRegistryIniFile then
+      I := TRegistryIniFile.Create(S);
+
+    Assert(Assigned(I), 'Ini class not supported: ' + IniFile.ClassName);
+
+    if AsRoot or (AtDepth <> 0) then
+      Root := nil;
+
+    if AtDepth < 0 then
+      AtDepth := 1;
+
+    Result := TASConfigIniFileEx.Create(Section, I, Root);
+  end;
+
+  if (AtDepth = 1) then begin
+{$IFDEF DEBUG_INIFILE}
+    TASDebug.OutputDebugStringFmt('Reparent: %s'#13#10, [ToString]);
+    if ParentFile <> nil then
+      TASDebug.OutputDebugStringFmt('--Previous parent: %s'#13#10, [(ParentFile as TObject).ToString]);
+    TASDebug.OutputDebugStringFmt('++New parent: %s'#13#10, [(Result as TObject).ToString]);
+{$ENDIF}
+
+    FParentFile := Result;
+  end;
+end;
+
+procedure TASConfigIniFileEx.AfterConstruction;
+begin
+// Release the constructor's implicit refcount
+  InterlockedDecrement(FRefCount);
+end;
+
+procedure TASConfigIniFileEx.BeforeDestruction;
+begin
+  if RefCount <> 0 then
+    System.Error(reInvalidPtr);
+end;
+
+constructor TASConfigIniFileEx.Create(const FileName: string; const IniFile: TCustomIniFile; const ParentFile: IASConfigFile );
+{$IFDEF DEBUG_INIFILE}
+var
+  S: string;
+{$ENDIF}
+begin
+  inherited Create(FileName);
+  FParentFile := ParentFile;
+  FIni := IniFile;
+
+{$IFDEF DEBUG_INIFILE}
+  if ParentFile = nil then
+    S := '<no parent>'
+  else
+    S := (ParentFile as TObject).ToString;
+
+  TASDebug.OutputDebugStringFmt('Create: %s'#13#10'Parent: %s'#13#10, [ToString, S]);
+{$ENDIF}
+end;
+
+procedure TASConfigIniFileEx.DeleteKey(const Section, Ident: String);
+begin
+  IniFile.DeleteKey(Section, Ident);
+end;
+
+destructor TASConfigIniFileEx.Destroy;
+begin
+{$IFDEF DEBUG_INIFILE}
+  TASDebug.OutputDebugStringFmt('Destroy: %s', [ToString]);
+{$ENDIF}
+
+  FreeAndNil(FIni);
+  FParentFile := nil;
+
+  inherited;
+end;
+
+procedure TASConfigIniFileEx.EraseSection(const Section: string);
+begin
+  IniFile.EraseSection(Section);
+end;
+
+function TASConfigIniFileEx.GetFileName: string;
+begin
+  Result := FileName;
+end;
+
+function TASConfigIniFileEx.GetIniFile: TCustomIniFile;
+begin
+  Assert(Assigned(FIni), 'Ini file not assigned');
+  Result := FIni;
+end;
+
+function TASConfigIniFileEx.GetParentFile: IASConfigFile;
+begin
+  Result := FParentFile;
+end;
+
+function TASConfigIniFileEx.GetSaveFileName: string;
+begin
+  Result := IniFile.FileName;
+end;
+
+procedure TASConfigIniFileEx.MergeStrings(Dest: TStrings; const Src: TStrings);
+var
+  I: Integer;
+begin
+  for I := 0 to Src.Count - 1 do
+    if Length(Src.Names[I]) > 0 then
+      Dest.Values[Src.Names[I]] := Src.ValueFromIndex[I]
+    else
+      Dest.Add(Src.Strings[I]);
+end;
+
+class function TASConfigIniFileEx.NewInstance: TObject;
+begin
+  Result := inherited NewInstance;
+  TASConfigIniFileEx(Result).FRefCount := 1;
+end;
+
+function TASConfigIniFileEx.QueryInterface(const IID: TGUID; out Obj): HResult;
+begin
+  if GetInterface(IID, Obj) then
+    Result := 0
+  else
+    Result := E_NOINTERFACE;
+end;
+
+procedure TASConfigIniFileEx.ReadSection(const Section: string; Strings: TStrings);
+var
+  Res: TStringList;
+begin
+  Strings.BeginUpdate;
+  Strings.Clear;
+  Res := TStringList.Create;
+  try
+    if Assigned(ParentFile) then begin
+      ParentFile.ReadSection(Section, Res);
+      Strings.AddStrings(Res);
+    end;
+
+    IniFile.ReadSection(Section, Res);
+    MergeStrings(Strings, Res);
+
+  finally
+    Res.Free;
+    Strings.EndUpdate;
+  end;
+end;
+
+procedure TASConfigIniFileEx.ReadSections(const Section: string; Strings: TStrings);
+var
+  Res: TStringList;
+begin
+  Strings.BeginUpdate;
+  Strings.Clear;
+  Res := TStringList.Create;
+  try
+    if Assigned(ParentFile) then begin
+      ParentFile.ReadSections(Section, Res);
+      Strings.AddStrings(Res);
+    end;
+
+    IniFile.ReadSections(Section, Res);
+    MergeStrings(Strings, Res);
+
+  finally
+    Res.Free;
+    Strings.EndUpdate;
+  end;
+end;
+
+procedure TASConfigIniFileEx.ReadSections(Strings: TStrings);
+var
+  Res: TStringList;
+begin
+  Strings.BeginUpdate;
+  Strings.Clear;
+  Res := TStringList.Create;
+  try
+    if Assigned(ParentFile) then begin
+      ParentFile.ReadSections(Res);
+      Strings.MergeStrings(Res);
+    end;
+
+    IniFile.ReadSections(Res);
+    Strings.MergeStrings(Res);
+
+  finally
+    Res.Free;
+    Strings.EndUpdate;
+  end;
+end;
+
+procedure TASConfigIniFileEx.ReadSectionValues(const Section: string; Strings: TStrings);
+var
+  Res: TStringList;
+begin
+  Strings.BeginUpdate;
+  Strings.Clear;
+  Res := TStringList.Create;
+  try
+    if Assigned(ParentFile) then begin
+      ParentFile.ReadSectionValues(Section, Res);
+      Strings.AddStrings(Res);
+    end;
+
+    IniFile.ReadSectionValues(Section, Res);
+    MergeStrings(Strings, Res);
+
+  finally
+    Res.Free;
+    Strings.EndUpdate;
+  end;
+end;
+
+function TASConfigIniFileEx.ReadString(const Section, Ident, Default: string): string;
+begin
+  Result := IniFile.ReadString(Section, Ident, #1);
+  if Result = #1 then begin
+    if Assigned(ParentFile) then
+      Result := ParentFile.ReadString(Section, Ident, Default)
+    else
+      Result := Default;
+{$IFDEF DEBUG_INIFILE}
+  end else begin
+    TASDebug.OutputDebugStringFmt('Read String %s/%s = "%s" [%s]'#13#10, [Section, Ident, Result, ToString]);
+{$ENDIF}
+  end;
+
+end;
+
+procedure TASConfigIniFileEx.SetParentFile(const Value: IASConfigFile);
+begin
+  FParentFile := Value;
+end;
+
+function TASConfigIniFileEx.ToString: string;
+begin
+  Result := Format('%s(%s) [%s]', [ClassName, FileName, IniFile.FileName]);
+end;
+
+procedure TASConfigIniFileEx.UpdateFile;
+begin
+  if Assigned(ParentFile) then
+    ParentFile.UpdateFile;
+
+  IniFile.UpdateFile;
+end;
+
+procedure TASConfigIniFileEx.WriteString(const Section, Ident, Value: String);
+begin
+{$IFDEF DEBUG_INIFILE}
+  TASDebug.OutputDebugStringFmt('Write String %s/%s = "%s" [%s]'#13#10, [Section, Ident, Value, ToString]);
+{$ENDIF}
+
+  IniFile.WriteString(Section, Ident, Value);
+end;
+
+function TASConfigIniFileEx._AddRef: Integer;
+begin
+  Result := InterlockedIncrement(FRefCount);
+end;
+
+function TASConfigIniFileEx._Release: Integer;
+begin
+  Result := InterlockedDecrement(FRefCount);
+  if Result = 0 then
+    Destroy;
+end;
+
+end.
 interface
 
 uses
-  Classes, SysUtils, Rtti, TypInfo, IniFiles;
+  Windows, Classes, SysUtils, Rtti, TypInfo, IniFiles, ASConfig.IniFileEx, ASConfigInterface;
 
 type
   TSavePropMethod = procedure(const Name: string; const Value: TValue) of object;
 
   TASCustomConfig = class(TComponent)
   private
+    FIniFile: IASConfigFile;
+    FParentConfig: TASCustomConfig;
     FSavePropMethodMap: TSavePropMethodMap;
     FLoadPropMethodMap: TLoadPropMethodMap;
+    procedure SetParentConfig(const Value: TASCustomConfig);
+
   protected
     procedure BeforeSave(const Obj: TObject; const Path: string); virtual;
     procedure AfterSave(const Obj: TObject; const Path: string); virtual;
   public
     procedure Save(const Obj: TObject; const Path: string = 'Default');
     procedure Load(const Obj: TObject; const Path: string = 'Default');
+    procedure Clear(ClearParents: Boolean = False); virtual;
+    property IniFile: IASConfigFile read FIniFile;
+    property ParentConfig: TASCustomConfig read FParentConfig write SetParentConfig;
   end;
 
   TASCustomConfigIniFile = class(TASCustomConfig)
   private
     FFileName: string;
-    FIniFile: TIniFile;
     FSection: string;
+
   protected
     procedure SaveInteger(const Name: string; const Value: TValue);
     procedure SaveString(const Name: string; const Value: TValue);
     function LoadString(const Name: string; const Default: TValue): TValue;
     function LoadFloat(const Name: string; const Default: TValue): TValue;
 
-    procedure OpenIniFile(const Section: string);
-    procedure CloseIniFile;
+    function CreateIniFile: TCustomIniFile; virtual;
+    procedure ClearIniFile; virtual;
+    procedure OpenIniFile(const Section: string); virtual;
+    procedure CloseIniFile; virtual;
+
 
     procedure BeforeSave(const Obj: TObject; const Path: string); override;
     procedure AfterSave(const Obj: TObject; const Path: string); override;
     procedure BeforeLoad(const Obj: TObject; const Path: string); override;
     procedure AfterLoad(const Obj: TObject; const Path: string); override;
 
-    property IniFile: TIniFile read FIniFile;
     property Section: string read FSection write FSection;
+
   public
     constructor Create(AOwner: TComponent); override;
+    procedure Clear(ClearParents: Boolean = False); override;
     property FileName: string read FFileName write FFileName;
   end;
 
+  TASCustomConfigRegistryIniFile = class(TASCustomConfigIniFile)
+  private
+    FRootKey: HKEY;
+    procedure SetRootKey(const Value: HKEY);
+    function GetPath: string;
+    procedure SetPath(const Value: string);
+
+  protected
+    function CreateIniFile: TCustomIniFile; override;
+    procedure ClearIniFile; override;
+
+    property FileName;
+
+  public
+    constructor Create(AOwner: TComponent); override;
+    property RootKey: HKEY read FRootKey write SetRootKey default HKEY_CURRENT_USER;
+    property Path: string read GetPath write SetPath;
+  end;
+
   TASConfigIniFile = class(TASCustomConfigIniFile)
   published
     property FileName;
+    property ParentConfig;
   end;
 
-  TASCustomConfigRegistry = class(TASCustomConfig)
-
+  TASConfigRegistryIniFile = class(TASCustomConfigRegistryIniFile)
+  published
+    property ParentConfig;
+    property Path;
+    property RootKey;
   end;
 
-  TASConfigRegistry = class(TASCustomConfigRegistry)
+  TASConfigStore = class(TComponent)
+  private
+    FConfig: TASCustomConfig;
+  public
+    procedure Save(const Obj: TObject; const Path: string = 'Default');
+    procedure Load(const Obj: TObject; const Path: string = 'Default');
 
+  published
+    property Config: TASCustomConfig read FConfig write FConfig;
   end;
 
 procedure Register;
 
 implementation
 
+uses
+  Registry;
+
 procedure Register;
 begin
-  RegisterComponents('ASTEKK', [TASConfigIniFile {, TASConfigRegistry}]);
+  RegisterComponents('ASTEKK', [TASConfigStore, TASConfigIniFile, TASConfigRegistryIniFile]);
 end;
 
 { TASCustomConfig }
 begin
 end;
 
+procedure TASCustomConfig.Clear(ClearParents: Boolean);
+begin
+  if ClearParents and Assigned(FParentConfig) then
+    ParentConfig.Clear(True);
+end;
+
 procedure TASCustomConfig.Load(const Obj: TObject; const Path: string);
 var
   Ctx: TRttiContext;
   AfterSave(Obj, Path);
 end;
 
+procedure TASCustomConfig.SetParentConfig(const Value: TASCustomConfig);
+var
+  I: TASCustomConfig;
+begin
+  I := Value;
+  while Assigned(I) do begin
+    if I = Self then
+      Exit;
+
+    I := I.ParentConfig;
+  end;
+
+  if Value <> Self then
+    FParentConfig := Value;
+
+  if Assigned(FIniFile) then begin
+    if Assigned(Value) then
+      IniFile.ParentFile := Value.IniFile
+    else
+      IniFile.ParentFile := nil;
+  end;
+end;
+
 { TASCustomConfigIniFile }
 
 procedure TASCustomConfigIniFile.AfterLoad(const Obj: TObject; const Path: string);
 begin
   CloseIniFile;
+
+  if Assigned(FParentConfig) then
+    ParentConfig.AfterLoad(Obj, Path);
 end;
 
 procedure TASCustomConfigIniFile.AfterSave(const Obj: TObject; const Path: string);
 begin
   CloseIniFile;
+
+  if Assigned(FParentConfig) then
+    ParentConfig.AfterSave(Obj, Path);
 end;
 
 procedure TASCustomConfigIniFile.BeforeLoad(const Obj: TObject; const Path: string);
 begin
+  if Assigned(FParentConfig) then
+    ParentConfig.BeforeLoad(Obj, Path);
+
   OpenIniFile(Path);
 end;
 
 procedure TASCustomConfigIniFile.BeforeSave(const Obj: TObject; const Path: string);
 begin
+  if Assigned(FParentConfig) then
+    ParentConfig.BeforeSave(Obj, Path);
+
   OpenIniFile(Path);
 end;
 
+procedure TASCustomConfigIniFile.Clear(ClearParents: Boolean);
+begin
+  ClearIniFile;
+  inherited;
+end;
+
+procedure TASCustomConfigIniFile.ClearIniFile;
+begin
+  if FileExists(FileName) then
+    DeleteFile(FileName);
+end;
+
 procedure TASCustomConfigIniFile.CloseIniFile;
 begin
-  FreeAndNil(FIniFile);
+  FIniFile := nil;
 end;
 
 constructor TASCustomConfigIniFile.Create(AOwner: TComponent);
   LoadPropMethodMap := LoadMap;
 end;
 
+function TASCustomConfigIniFile.CreateIniFile: TCustomIniFile;
+begin
+  Result := TIniFile.Create(FileName);
+end;
+
 function TASCustomConfigIniFile.LoadFloat(const Name: string; const Default: TValue): TValue;
 begin
   Result := TValue.From(IniFile.ReadFloat(Section, Name, Default.AsExtended));
 end;
 
 procedure TASCustomConfigIniFile.OpenIniFile(const Section: string);
+var
+  Parent: IASConfigFile;
 begin
   if Assigned(FIniFile) then
-    FreeAndNil(FIniFile);
+    CloseIniFile;
 
-  FIniFile := TIniFile.Create(FileName);
+  if Assigned(FParentConfig) then
+    Parent := ParentConfig.IniFile;
+
   Self.Section := Section;
+  FIniFile := TASConfigIniFileEx.Create(Name, CreateIniFile, Parent);
 end;
 
 procedure TASCustomConfigIniFile.SaveFloat(const Name: string; const Value: TValue);
   IniFile.WriteString(Section, Name, Value.AsString);
 end;
 
+{ TASConfigStore }
+
+procedure TASConfigStore.Load(const Obj: TObject; const Path: string);
+begin
+  Config.Load(Obj, Path);
+end;
+
+procedure TASConfigStore.Save(const Obj: TObject; const Path: string);
+begin
+  Config.Save(Obj, Path);
+end;
+
+{ TASCustomConfigRegistry }
+
+procedure TASCustomConfigRegistryIniFile.ClearIniFile;
+var
+  R: TRegistry;
+begin
+  R := TRegistry.Create;
+  try
+    R.RootKey := RootKey;
+    R.DeleteKey(Path);
+  finally
+    R.Free;
+  end;
+end;
+
+constructor TASCustomConfigRegistryIniFile.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FRootKey := HKEY_CURRENT_USER;
+end;
+
+function TASCustomConfigRegistryIniFile.CreateIniFile: TCustomIniFile;
+begin
+  Result := TRegistryIniFile.Create(FileName);
+  (Result as TRegistryIniFile).RegIniFile.RootKey := RootKey;
+end;
+
+function TASCustomConfigRegistryIniFile.GetPath: string;
+begin
+  Result := FileName;
+end;
+
+procedure TASCustomConfigRegistryIniFile.SetPath(const Value: string);
+begin
+  FileName := Value;
+end;
+
+procedure TASCustomConfigRegistryIniFile.SetRootKey(const Value: HKEY);
+begin
+  FRootKey := Value;
+
+  if Assigned(FIniFile) then
+    (IniFile.IniFile as TRegistryIniFile).RegIniFile.RootKey := Value;
+end;
+
 end.

Src/ASConfigInterface.pas

+unit ASConfigInterface;
+
+interface
+
+uses
+  Classes, IniFiles;
+
+type
+  IASConfigFile = interface
+    ['{C0DAA54D-BFA8-46E6-B9D8-387471BC9C73}']
+
+    function AddDefaultSection(const Section: string; AtDepth: Integer = 0; AsRoot: Boolean = False): IASConfigFile;
+    function GetSaveFileName: string;
+
+    function GetIniFile: TCustomIniFile;
+    property IniFile: TCustomIniFile read GetIniFile;
+
+    function GetParentFile: IASConfigFile;
+    procedure SetParentFile(const Value: IASConfigFile);
+    property ParentFile: IASConfigFile read GetParentFile write SetParentFile;
+
+    { Public interface of TCustomIniFile }
+    function SectionExists(const Section: string): Boolean;
+    function ReadString(const Section, Ident, Default: string): string;
+    procedure WriteString(const Section, Ident, Value: String);
+    function ReadInteger(const Section, Ident: string; Default: Longint): Longint;
+    procedure WriteInteger(const Section, Ident: string; Value: Longint);
+    function ReadBool(const Section, Ident: string; Default: Boolean): Boolean;
+    procedure WriteBool(const Section, Ident: string; Value: Boolean);
+    function ReadBinaryStream(const Section, Name: string; Value: TStream): Integer;
+    function ReadDate(const Section, Name: string; Default: TDateTime): TDateTime;
+    function ReadDateTime(const Section, Name: string; Default: TDateTime): TDateTime;
+    function ReadFloat(const Section, Name: string; Default: Double): Double;
+    function ReadTime(const Section, Name: string; Default: TDateTime): TDateTime;
+    procedure WriteBinaryStream(const Section, Name: string; Value: TStream);
+    procedure WriteDate(const Section, Name: string; Value: TDateTime);
+    procedure WriteDateTime(const Section, Name: string; Value: TDateTime);
+    procedure WriteFloat(const Section, Name: string; Value: Double);
+    procedure WriteTime(const Section, Name: string; Value: TDateTime);
+    procedure ReadSection(const Section: string; Strings: TStrings);
+    procedure ReadSections(Strings: TStrings); overload;
+    procedure ReadSections(const Section: string; Strings: TStrings); overload;
+    procedure ReadSubSections(const Section: string; Strings: TStrings; Recurse: Boolean = False);
+    procedure ReadSectionValues(const Section: string; Strings: TStrings);
+    procedure EraseSection(const Section: string);
+    procedure DeleteKey(const Section, Ident: String);
+    procedure UpdateFile;
+    function ValueExists(const Section, Ident: string): Boolean;
+    function GetFileName: string;
+    property FileName: string read GetFileName;
+  end;
+
+implementation
+
+end.

Test/TestASConfig.pas

 interface
 
 uses
-  Windows, TestFramework, Classes, SysUtils, ASConfig;
+  Windows, TestFramework, Classes, SysUtils, ASConfig, ASConfigInterface;
 
 type
   // Test methods for class TASCustomConfig
     FASCustomConfig: TASCustomConfig;
 
   protected
-    property Config: TASCustomConfig read FASCustomConfig;
+    property Config: TASCustomConfig read FASCustomConfig write FASCustomConfig;
+
+    function FileName: string;
+    function CreateIniFile: IASConfigFile; virtual; abstract;
 
   public
-    procedure SetUp; override;
     procedure TearDown; override;
 
   published
     procedure TestLoadFromIniFile;
   end;
 
+  TestTASConfigIniFile = class(TestTASCustomConfig)
+  protected
+    function CreateIniFile: IASConfigFile; override;
+
+  public
+    procedure SetUp; override;
+
+  published
+    procedure TestClearIniFile;
+  end;
+
+  TestTASConfigRegistryIniFile = class(TestTASCustomConfig)
+  protected
+    function CreateIniFile: IASConfigFile; override;
+
+  public
+    procedure SetUp; override;
+
+  published
+    procedure TestClearIniFile;
+  end;
+
   TTestObject = class
   private
     Fstring: string;
 implementation
 
 uses
-  Forms, IniFiles;
+  Forms, IniFiles, Registry, ASConfig.IniFileEx;
 
-procedure TestTASCustomConfig.SetUp;
+function TestTASCustomConfig.FileName: string;
 begin
-  FASCustomConfig := TASConfigIniFile.Create(Application);
+  Result := (Config as TASCustomConfigIniFile).FileName;
 end;
 
 procedure TestTASCustomConfig.TearDown;
 begin
-  FASCustomConfig.Free;
-  FASCustomConfig := nil;
+  FreeAndNil(FASCustomConfig);
 end;
 
 procedure TestTASCustomConfig.TestLoadFromIniFile;
 var
   Obj: TTestObject;
-  Ini: TIniFile;
-  FileName: string;
+  Ini: IASConfigFile;
 begin
-  FileName := './as-test.ini';
-  if FileExists(FileName) then
-    CheckTrue(DeleteFile(FileName), 'Delete existing ini file (' + FileName + ')');
+  Config.Clear;
 
-  Ini := TIniFile.Create(FileName);
-  try
-    Ini.WriteString('Default', 'Astring', 'My string');
-    Ini.WriteInteger('Default', 'Aint', 44);
-  finally
-    Ini.Free;
-  end;
+  Ini := CreateIniFile;
+  Ini.WriteString('Default', 'Astring', 'My string');
+  Ini.WriteInteger('Default', 'Aint', 44);
+  Ini := nil;
 
   Obj := TTestObject.Create;
   try
-    (Config as TASConfigIniFile).FileName := FileName;
     Config.Load(Obj);
 
     CheckEqualsString('My string', Obj.Astring, '[Default]');
 procedure TestTASCustomConfig.TestSaveToIniFile;
 var
   Obj: TTestObject;
-  Ini: TIniFile;
-  FileName: string;
+  Ini: IASConfigFile;
 begin
-  FileName := './as-test.ini';
-  if FileExists(FileName) then
-    CheckTrue(DeleteFile(FileName), 'Delete existing ini file (' + FileName + ')');
+  Config.Clear;
 
   Obj := TTestObject.Create;
   try
     Obj.Astring := 'A string';
     Obj.Aint := 55;
 
-    (Config as TASConfigIniFile).FileName := FileName;
     Config.Save(Obj);
 
-    Ini := TIniFile.Create(FileName);
-    try
-      CheckEqualsString('A string', Ini.ReadString('Default', 'Astring', ''), '[Default]');
-      CheckEquals(55, Ini.ReadInteger('Default', 'Aint', 0), '[Default]');
-    finally
-      Ini.Free;
-    end;
   finally
     Obj.Free;
   end;
+
+  Ini := CreateIniFile;
+  CheckEqualsString('A string', Ini.ReadString('Default', 'Astring', ''), '[Default]');
+  CheckEquals(55, Ini.ReadInteger('Default', 'Aint', 0), '[Default]');
+end;
+
+{ TestTASConfigIniFile }
+
+function TestTASConfigIniFile.CreateIniFile: IASConfigFile;
+begin
+  Result := TASConfigIniFileEx.Create('',
+    TIniFile.Create((Config as TASConfigIniFile).FileName)
+  );
+end;
+
+procedure TestTASConfigIniFile.SetUp;
+begin
+  Config := TASConfigIniFile.Create(Application);
+  (Config as TASConfigIniFile).FileName := './as-test.ini';
+end;
+
+procedure TestTASConfigIniFile.TestClearIniFile;
+var
+  Ini: IASConfigFile;
+begin
+  if not FileExists(FileName) then begin
+    Ini := CreateIniFile;
+    Ini.WriteString('Default', 'Astring', 'My string');
+    Ini.WriteInteger('Default', 'Aint', 44);
+    Ini := nil;
+  end;
+
+  CheckTrue(FileExists(FileName), 'File exists');
+  Config.Clear;
+  CheckFalse(FileExists(FileName), 'File exists');
+end;
+
+{ TestTASConfigRegistryIniFile }
+
+function TestTASConfigRegistryIniFile.CreateIniFile: IASConfigFile;
+begin
+  Result := TASConfigIniFileEx.Create('',
+    TRegistryIniFile.Create((Config as TASConfigRegistryIniFile).Path)
+  );
+end;
+
+procedure TestTASConfigRegistryIniFile.SetUp;
+begin
+  Config := TASConfigRegistryIniFile.Create(Application);
+  (Config as TASConfigRegistryIniFile).Path := '\Software\ASTEKK\Test';
+end;
+
+
+procedure TestTASConfigRegistryIniFile.TestClearIniFile;
+var
+  Ini: IASConfigFile;
+  Reg: TRegistry;
+begin
+  Reg := TRegistry.Create;
+  try
+    if not Reg.KeyExists(FileName) then begin
+      Ini := CreateIniFile;
+      Ini.WriteString('Default', 'Astring', 'My string');
+      Ini.WriteInteger('Default', 'Aint', 44);
+      Ini := nil;
+    end;
+
+    CheckTrue(Reg.KeyExists(FileName), 'File exists');
+    Config.Clear;
+    CheckFalse(Reg.KeyExists(FileName), 'File exists');
+  finally
+    Reg.Free;
+  end;
 end;
 
 initialization
   // Register any test cases with the test runner
-  RegisterTest(TestTASCustomConfig.Suite);
+  RegisterTest(TestTASConfigIniFile.Suite);
+  RegisterTest(TestTASConfigRegistryIniFile.Suite);
 end.
 

Test/ascfgTests.dpr

   GUITestRunner,
   TextTestRunner,
   TestASConfig in 'TestASConfig.pas',
-  ASConfig in '..\Src\ASConfig.pas';
+  ASConfig in '..\Src\ASConfig.pas',
+  ASConfig.IniFileEx in '..\Src\ASConfig.IniFileEx.pas',
+  ASConfigInterface in '..\Src\ASConfigInterface.pas';
 
 {$R *.RES}
 

Test/ascfgTests.dproj

 			<DCC_ExeOutput>.\$(Config)\$(Platform)</DCC_ExeOutput>
 		</PropertyGroup>
 		<PropertyGroup Condition="'$(Cfg_1)'!=''">
+			<DCC_DebugDCUs>true</DCC_DebugDCUs>
 			<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
 			<DCC_Optimize>false</DCC_Optimize>
 			<DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
 			</DelphiCompile>
 			<DCCReference Include="TestASConfig.pas"/>
 			<DCCReference Include="..\Src\ASConfig.pas"/>
+			<DCCReference Include="..\Src\ASConfig.IniFileEx.pas"/>
+			<DCCReference Include="..\Src\ASConfigInterface.pas"/>
 			<BuildConfiguration Include="Release">
 				<Key>Cfg_2</Key>
 				<CfgParent>Base</CfgParent>
 					<SourceProjectName>C:\Work\ASTEKK\Delphi\ASConfig\Proj\ascfg.dproj</SourceProjectName>
 					<TestFramework>DUnit / Delphi Win32</TestFramework>
 					<TestRunner>GUI</TestRunner>
+					<TestProjectName/>
 				</UnitTesting>
 			</BorlandProject>
 			<ProjectFileVersion>12</ProjectFileVersion>