Delphi 使用对象和接口时内存泄漏或访问冲突

ssgvzors  于 2023-04-20  发布在  其他
关注(0)|答案(1)|浏览(172)

目前,我在对象和接口以及它们的内存管理方面遇到了一些问题。我有一个继承自TInterfacedPersistent的类,名为TAbstractBaseDTO。我还有一个接口IDuplicatable,函数function CreateDuplicate: TAbstractBaseDTO
我使用接口来实现抽象,而不是管理内存,这就是为什么我使用TInterfacedPersistent作为祖先类。
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.

当我运行actDuplicate action时,FastMM4在关机时报告内存泄漏。
但是,将execute函数更改为:

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;

将在运行操作时立即引发访问冲突。
我该如何解决这个问题?

kx7yvsdv

kx7yvsdv1#

您遇到的问题是因为存在对已销毁的对象的活动接口引用。
在这种情况下,它是编译器在将LAbstractBaseDTO类型转换为IDuplicatable时创建的隐藏隐式接口引用。

LAbstractBaseDTODuplicate := (LAbstractBaseDTO as IDuplicatable).CreateDuplicate;

这个隐藏的接口引用将被编译器在方法尾声中清除,这将在已经销毁的对象示例上调用_Release方法。
如果您在完全调试模式下使用FASTMM运行代码,FASTMM将检测到这种情况并显示错误消息,然后是堆栈跟踪:
FASTMM检测到试图使用已释放对象的接口。现在将引发访问冲突以中止当前操作。
为了解决这个问题,你需要将隐式接口引用转换为你可以控制的显式接口引用,然后在释放它引用的对象之前,你可以自己清除这个引用。

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;

相关问题