Delphi - 使用接口内的接口时,我出现内存泄漏,但不知道为什么

fis*_*pjm 2 delphi ini memory-leaks interface

我的目标是获得一个设置界面,其中包含我的应用程序设置信息。这可能是仅在应用程序运行时相关的信息以及应该是永久性的信息。为此,我认为在该应用程序设置界面中创建一个应用程序界面和一个持久设置界面是一个好主意,这样我就可以在 Ini 文件、数据库、加密文件等等之间切换。我认为代码有效。但是关闭 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.
Run Code Online (Sandbox Code Playgroud)

这是我创建对象的方法:

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

任何有关更好编码的建议也非常感谢

Dal*_*kar 5

代码中的问题在于,您使用_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;
Run Code Online (Sandbox Code Playgroud)

您正在方法中构造FIniFile实例_AddRef,这是错误的地方,也是_Release错误的地方 free FIniFile

_AddRef并且_Release方法可以在实例生命周期内多次调用,每次调用_AddRef都会导致新TIniFile实例的构造。

为了正确的内存管理,这两个方法将被调用相同的次数,但有可能_AddRef连续被调用多次,然后_Release根据代码在将来的某个时间将出现多次调用。

换句话说,以下序列也是可能的:

_AddRef
_AddRef
_Release
_Release
Run Code Online (Sandbox Code Playgroud)

或者

_AddRef
_AddRef
_Release
_AddRef
_Release
_Release
Run Code Online (Sandbox Code Playgroud)

当您调用APPSettings := TAppSettings.create(TIniSettings.create(...第一个 _时AddRef,将在将实例作为参数传递时调用aPersistentSettings,因为它没有声明为 const。然后 next将在分配_AddRef时被调用,第二次调用将导致实例泄漏。aPersistentSettingsFPersistentSettingsTIniFile

在构造函数调用结束时,将有匹配的参数调用_Release调用。这也将释放 ini 文件,并且在您再次触发实例上的引用计数之前将不可用。aPersistentSettings_AddRefFIniFilenilFPersistentSettings


您可能希望FIniFileTIniSettings构造函数中创建并在其析构函数中销毁它,或者如果您不想一直创建该 ini 文件并且可以在任何时候调用它们,则可以引入其他方法 ( Open/Close) 来达到此目的。TIniSettings读或写操作。

_AddRef当您从和中删除与 ini 文件相关的代码时_Release,您不再需要在类中实现它们,因为默认实现将完成这项工作。


还有其他方法可以通过TIniFile仅在未分配的情况下创建代码来修复_AddRef代码,但是引用计数方法仍然是执行此类操作的错误位置,因为您必须确保不会意外地在错误的位置触发附加引用计数这可能会让你留下 nilFIniFile变量,而你可能仍然需要它。

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