Search code examples
delphimemory-managementinterface

Delphi Memory Leak or Access Violation while using objects and interfaces


Currently I'm stuck a little bit regarding objects and interfaces and their memory management. I have a class which inherits from TInterfacedPersistent, called TAbstractBaseDTO. I also have an interface IDuplicatable with a function function CreateDuplicate: TAbstractBaseDTO.

I am using interfaces to achieve abstraction, not to manage memory and that is why I am using TInterfacedPersistent as an ancestor class.

Unit.pas

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Actions, Vcl.ActnList,
  Vcl.StdCtrls;

type
  TAbstractBaseDTO = class abstract (TInterfacedPersistent)
    public
      constructor CreateEmpty; virtual; abstract;
  end;

  IDuplicatable = interface
    ['{153275DC-71C0-4CB6-B933-667419950C68}']
    function CreateDuplicate: TAbstractBaseDTO;
  end;

  TCountryMasterDataDTO = class (TAbstractBaseDTO, IDuplicatable)
    public
      constructor CreateEmpty; override;
      function CreateDuplicate: TAbstractBaseDTO;
  end;

  TBaseDataForm = class(TForm)
    ActionList1: TActionList;
    actDuplicate: TAction;
    Button1: TButton;
    procedure actDuplicateExecute(Sender: TObject);
    strict private
      var
        FDataObject: TAbstractBaseDTO;
      function FetchBusinessObject: TAbstractBaseDTO;
    public
      procedure LoadData(ADataObject: TAbstractBaseDTO);
      destructor Destroy; override;
  end;

var
  BaseDataForm: TBaseDataForm;

implementation

{$R *.dfm}

procedure TBaseDataForm.LoadData(ADataObject: TAbstractBaseDTO);
begin
  FDataObject.Free;
  FDataObject := ADataObject;
end;

destructor TBaseDataForm.Destroy;
begin
  FDataObject.Free;
  inherited;
end;

constructor TCountryMasterDataDTO.CreateEmpty;
begin
  Create;
end;

function TCountryMasterDataDTO.CreateDuplicate: TAbstractBaseDTO;
begin
  Result := TCountryMasterDataDTO.Create;
end;

procedure TBaseDataForm.actDuplicateExecute(Sender: TObject);
var
  LAbstractBaseDTO: IDuplicatable;
  LAbstractBaseDTODuplicate: TAbstractBaseDTO;
begin
  LAbstractBaseDTO := Self.FetchBusinessObject as IDuplicatable;
  try
    LAbstractBaseDTODuplicate := LAbstractBaseDTO.CreateDuplicate;
    Self.LoadData(LAbstractBaseDTODuplicate);
  finally
    LAbstractBaseDTO := nil;
  end;
end;

function TBaseDataForm.FetchBusinessObject: TAbstractBaseDTO;
begin
  Result := TCountryMasterDataDTO.Create;
end;

end.

When I run actDuplicate action FastMM4 reports a memory leak on shutdown.

However, changing the execute function to:

procedure TBaseDataForm.actDuplicateExecute(Sender: TObject);
var
  LAbstractBaseDTO: TAbstractBaseDTO;
  LAbstractBaseDTODuplicate: TAbstractBaseDTO;
begin
  LAbstractBaseDTO := Self.FetchBusinessObject;
  try
    LAbstractBaseDTODuplicate := (LAbstractBaseDTO as IDuplicatable).CreateDuplicate;
    Self.LoadData(LAbstractBaseDTODuplicate);
  finally
    LAbstractBaseDTO.Free;
  end;
end;

will immediately raise an access violation, when running the action.

How can I solve this problem?


Solution

  • The problem you are experiencing is because there is alive interface reference to the object you have already destroyed.

    In this case it is hidden implicit interface reference created by compiler when you typecasted LAbstractBaseDTO as IDuplicatable.

    LAbstractBaseDTODuplicate := (LAbstractBaseDTO as IDuplicatable).CreateDuplicate;
    

    This hidden interface reference will be cleared by compiler in the method epilogue and this will call _Release method on the already destroyed object instance.

    If you run your code with FASTMM in full debug mode, FASTMM will detect such scenario and show error message, followed by a stack trace:

    FASTMM has detected an attempt to use an interface of a freed object. An access violation will now be raised in order to abort the current operation.

    To solve that problem you need to conver that implicit interface reference to explicit one under your control and then you can clear that reference yoursel, before you free the object it references.

    procedure TBaseDataForm.actDuplicateExecute(Sender: TObject);
    var
      LAbstractBaseDTO: TAbstractBaseDTO;
      LAbstractBaseDTODuplicate: TAbstractBaseDTO;
      LDuplicatable: IDuplicatable;
    begin
      LAbstractBaseDTO := FetchBusinessObject;
      try
        // save interface reference to a variable
        LDuplicatable := LAbstractBaseDTO as IDuplicatable;
        LAbstractBaseDTODuplicate := LDuplicatable.CreateDuplicate;
        LoadData(LAbstractBaseDTODuplicate);
      finally
        LDuplicatable := nil;
        LAbstractBaseDTO.Free;
      end;
    end;