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?
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;