Delphi -在一个接口中有一个接口,我得到了一个内存泄漏,但不知道为什么

relj7zay  于 2023-06-22  发布在  其他
关注(0)|答案(1)|浏览(85)

我的目标是,得到一个设置界面,持有我的应用程序设置的信息.这可能是信息,只有当应用程序运行时才相关,信息应该是永久性的。为此,我认为这将是一个好主意,创建一个应用程序界面和一个持久设置界面内的应用程序设置界面,所以我可以之间切换让我们说INI文件,DB,加密文件,无论什么....之后代码工作,我认为。但在关闭FastMM时,我在TiniFile中留下了内存泄漏,我不知道为什么。下面是带有类的接口单元。

unit UAPPSettings;

interface

uses
  IniFiles;

type
  IPersistentSettings = Interface
    ['{11542859-DEA3-4DBB-9A88-2068E407552C}']
    function ReadString(Section, Name, Default: String): string;
    procedure WriteString(Section, Name, Default: String);
    function ReadPassword: string;
    procedure WritePassword(Password: String);
  end;

type
  IAppSettings = Interface
    ['{559B8219-7D81-44CA-87A0-6D261B4A87E7}']
    function GetPersistentSettings: IPersistentSettings;
    property PersistentSettings: IPersistentSettings read GetPersistentSettings;
  End;

type
  TAppSettings = class(TInterfacedObject, IAppSettings)
  strict private
    FPersistentSettings: IPersistentSettings;
    function GetPersistentSettings: IPersistentSettings;
    procedure SetPersistentSettings(const Value: IPersistentSettings);
    property PersistentSettings: IPersistentSettings read GetPersistentSettings write SetPersistentSettings;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    constructor create(aPersistentSettings: IPersistentSettings);
  end;

type
  TIniSettings = class(TInterfacedObject, IPersistentSettings)
  strict private
    FIniFile: TIniFile;
    FfilePath: String;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    function ReadString(Section, Name, Default: String): string;
    procedure WriteString(Section, Name, Default: String);
    function ReadPassword: string;
    procedure WritePassword(Password: string);
  public
    constructor create(FilePath: String);

  end;

  var
  APPSettings: IAppSettings;

implementation

uses
  FMX.Types;


  { TIniSettings }

constructor TIniSettings.create(FilePath: String);
begin
  FfilePath := FilePath;
end;

function TIniSettings.ReadPassword: string;
begin
  Result := ReadString('App', 'Passwort', '');
end;

function TIniSettings.ReadString(Section, Name, Default: String): string;
begin
  If FIniFile.ValueExists(Section, Name) = false then
    WriteString(Section, Name, Default);
  Result := FIniFile.ReadString(Section, Name, Default);
end;

procedure TIniSettings.WritePassword(Password: string);
begin
  WriteString('App', 'Passwort', Password);
end;

procedure TIniSettings.WriteString(Section, Name, Default: String);
begin
  FIniFile.WriteString(Section, Name, Default);
end;

function TIniSettings._AddRef: Integer;
begin
  log.d('IniSettings _AddRef');
  FIniFile := TIniFile.create(FfilePath);
  Result := inherited _AddRef;
end;

function TIniSettings._Release: Integer;
begin
  log.d('IniSettings _Release');
  Result := inherited _Release;
  FIniFile.Free;
  FIniFile := nil;
end;

{ TAppSettings }

constructor TAppSettings.create(aPersistentSettings: IPersistentSettings);
begin
  FPersistentSettings := aPersistentSettings;
end;

function TAppSettings.GetPersistentSettings: IPersistentSettings;
begin
  Result := FPersistentSettings;
end;

procedure TAppSettings.SetPersistentSettings(const Value: IPersistentSettings);
begin
  FPersistentSettings := Value;
end;

function TAppSettings._AddRef: Integer;
begin
  log.d('AppSettings _AddRef');
  Result := inherited _AddRef;
end;

function TAppSettings._Release: Integer;
begin
  log.d('AppSettings _Release');
  Result := inherited _Release;
  FPersistentSettings := nil;
end;

end.

下面是我创建Object的方法:

APPSettings := TAppSettings.create(TIniSettings.create(System.IOUtils.TPath.GetDocumentsPath + System.SysUtils.PathDelim + 'config.ini'));

任何更好的编码建议,以及真的很感激

tpgth1q7

tpgth1q71#

代码中的问题是,您使用_AddRef_Release方法的目的不是对调用这两个方法的示例进行内存管理。
以下代码是问题的原因:

function TIniSettings._AddRef: Integer;
begin
  log.d('IniSettings _AddRef');
  FIniFile := TIniFile.create(FfilePath);
  Result := inherited _AddRef;
end;

function TIniSettings._Release: Integer;
begin
  log.d('IniSettings _Release');
  Result := inherited _Release;
  FIniFile.Free;
  FIniFile := nil;
end;

您正在_AddRef方法中构造FIniFile示例,这是错误的位置,并且_Release是释放FIniFile的错误位置。
_AddRef_Release方法可以在示例生命周期内多次调用,每次调用_AddRef都会导致新的TIniFile示例的构建。
为了正确的内存管理,这两个方法将被调用相同的次数,但是_AddRef可能会连续被调用多次,然后根据代码的不同,在未来的某个时间会有多个_Release调用。
换句话说,以下序列也是可能的:

_AddRef
_AddRef
_Release
_Release

_AddRef
_AddRef
_Release
_AddRef
_Release
_Release

当你调用APPSettings := TAppSettings.create(TIniSettings.create(... first _ AddRef时,当示例作为aPersistentSettings参数传递时会被调用,因为它没有被声明为const。然后,在将aPersistentSettings分配给FPersistentSettings时,将调用下一个_AddRef,第二次调用将创建TIniFile示例的泄漏。
在构造函数调用结束时,将有aPersistentSettings参数_AddRef调用的匹配_Release调用。这也将释放ini文件,并且FIniFile将是nil并且不可用,直到您再次触发FPersistentSettings示例上的引用计数。
你可以在TIniSettings构造函数中创建FIniFile,然后在它的析构函数中销毁它,或者在TIniSettings中引入额外的方法(Open/Close),如果你不想一直创建那个ini文件,你可以在任何阅读或写操作中调用它们。
当你从_AddRef_Release中删除与ini文件相关的代码时,你不再需要在你的类中实现它们,因为默认实现将完成这项工作。
有一种额外的方法可以修复代码,即只在_AddRef中没有赋值的情况下创建TIniFile,但是引用计数方法仍然是错误的地方,因为你必须确保你不会在错误的地方意外触发额外的引用计数,这可能会在你仍然需要它的时候给你留下nil FIniFile变量。

function TIniSettings._AddRef: Integer;
begin
  log.d('IniSettings _AddRef');
  if not Assigned(FIniFile) then
    FIniFile := TIniFile.create(FfilePath);
  Result := inherited _AddRef;
end;

相关问题