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

huangapple go评论76阅读模式
英文:

Delphi Memory Leak or Access Violation while using objects and interfaces

问题

The issue you're facing with memory management and access violations is related to the way you're handling object creation and memory. Delphi's memory management for interfaces can be a bit tricky. Here's a possible solution:

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; // Free the object that was created by FetchBusinessObject
  end;
end;

In this modified code, we explicitly free the object returned by FetchBusinessObject. This ensures that the object is properly disposed of when it's no longer needed, preventing memory leaks.

Make sure you handle memory management consistently throughout your code, ensuring that objects are freed when they're no longer in use. This should help you avoid memory leaks and access violations.

英文:

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?

答案1

得分: 2

以下是您要翻译的内容:

问题出在您已经销毁的对象上存在着一个活跃的接口引用。

在这种情况下,当您将 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
    // 将接口引用保存到变量中
    LDuplicatable := LAbstractBaseDTO as IDuplicatable;
    LAbstractBaseDTODuplicate := LDuplicatable.CreateDuplicate;
    LoadData(LAbstractBaseDTODuplicate);
  finally
    LDuplicatable := nil;
    LAbstractBaseDTO.Free;
  end;
end;
英文:

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;

huangapple
  • 本文由 发表于 2023年4月10日 22:56:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/75978177.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定