Delphi 为什么在应用程序关闭时释放类析构函数中的成员组件会导致EInvalidPointer错误?

Delphi 为什么在应用程序关闭时释放类析构函数中的成员组件会导致EInvalidPointer错误?,delphi,firemonkey,delphi-10.4-sydney,Delphi,Firemonkey,Delphi 10.4 Sydney,下面是我创建的一个类,用于将TLabel添加到TTrackBar。标签显示拖动轨迹栏后淡出的值。在运行时创建实例,并将父对象设置为窗体。它工作正常,但如果轨迹栏仍然存在,则在关闭应用程序时会出现错误。但是,如果在运行时释放轨迹栏,然后关闭应用程序,则没有问题。当应用程序关闭(FLabel.Free;)时调试该行时,我看到FLabel和其中的数据仍然存在,但它仍然给出了错误。我担心,如果我只是删除这一行,那么在运行时释放对象时就会出现内存泄漏。我试着把它改为if Assigned(FLabel),

下面是我创建的一个类,用于将TLabel添加到TTrackBar。标签显示拖动轨迹栏后淡出的值。在运行时创建实例,并将父对象设置为窗体。它工作正常,但如果轨迹栏仍然存在,则在关闭应用程序时会出现错误。但是,如果在运行时释放轨迹栏,然后关闭应用程序,则没有问题。当应用程序关闭(FLabel.Free;)时调试该行时,我看到FLabel和其中的数据仍然存在,但它仍然给出了错误。我担心,如果我只是删除这一行,那么在运行时释放对象时就会出现内存泄漏。我试着把它改为if Assigned(FLabel),然后改为FLabel.Free;但是没有变化。我知道这一定与标签的父项已设置的事实有关

unit TrackBarLabelUnit;

interface

uses
  System.Types, System.Classes, System.SysUtils, FMX.Types, FMX.StdCtrls,
  FMX.Controls;

type
  TValueToString = function(AValue : Single) : String of object;

  TTrackBarLabel = class(TTrackBar)
  private
    FLabel : TLabel;
    FSuffix : String;
    FTimer : TTimer;
    FOffset : Integer;
    FValueToString : TValueToString;

    procedure TimerTimer(Sender: TObject);
  protected
    procedure ParentChanged; override;
    procedure DoTracking; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    property Suffix : String read FSuffix write FSuffix;
    property LabelOffset : Integer read FOffset write FOffset;
    property ValueToString : TValueToString write FValueToString;
  end;

implementation

constructor TTrackBarLabel.Create(AOwner: TComponent);
begin
  inherited;
  FLabel := TLabel.Create(nil);
  FLabel.Visible := False;
  FTimer := TTimer.Create(nil);
  FTimer.Interval := 100;
  FTimer.Enabled := False;
  FTimer.OnTimer := TimerTimer;
  FSuffix := '';
  FOffset := 22;
end;

destructor TTrackBarLabel.Destroy;
begin
  FLabel.Free; // EInvalidPointer error here when application is closed
  FTimer.Free;
  inherited Destroy;
end;

procedure TTrackBarLabel.ParentChanged;
begin
  inherited;
  FLabel.Parent := Parent;
end;

procedure TTrackBarLabel.DoTracking;
begin
  inherited;

  if not Assigned(Thumb) then Exit;

  FLabel.Visible := True;
  FLabel.Tag := 10;
  FLabel.Opacity := 1;

  if Assigned(FValueToString) then
    FLabel.Text := FValueToString(Value) + FSuffix
  else
    FLabel.Text := FloatToStrF(Value, ffFixed, 12, 1) + FSuffix;

  if Orientation = TOrientation.Horizontal then begin
    FLabel.Position.X := Position.X + Thumb.Position.X +
                         (Thumb.Width - FLabel.Width) * 0.5;
    FLabel.Position.Y := Position.Y + FOffset;
    FLabel.TextSettings.HorzAlign := TTextAlign.Center;
  end else begin
    FLabel.Position.X := Position.X + FOffset;
    FLabel.Position.Y := Position.Y + Thumb.Position.Y - 2;
    FLabel.TextSettings.HorzAlign := TTextAlign.Leading;
  end;

  FTimer.Enabled := False;
  FTimer.Enabled := True;
end;

procedure TTrackBarLabel.TimerTimer(Sender: TObject);
begin
  FLabel.Tag := FLabel.Tag - 1;
  FLabel.Opacity := FLabel.Tag * 0.2;
  if FLabel.Tag < 0 then begin
    FLabel.Visible := False;
    FTimer.Enabled := False;
  end;
end;

end.
unit TrackBarLabelUnit;
接口
使用
System.Types、System.Class、System.SysUtils、FMX.Types、FMX.StdCtrls、,
FMX.控制;
类型
TValueToString=函数(AValue:Single):对象的字符串;
TTrackBarLabel=class(TTrackBar)
私有的
FLabel:TLabel;
FSuffix:字符串;
FTimer:TTimer;
偏移量:整数;
FValueToString:TValueToString;
程序计时器(发送方:TObject);
受保护的
程序变更;推翻
程序跟踪;推翻
公众的
构造函数创建(AOwner:TComponent);推翻
毁灭者毁灭;推翻
属性后缀:字符串读取FSuffix写入FSuffix;
属性标签偏移:整数读取偏移量写入偏移量;
属性值字符串:TValueToString写入FValueToString;
结束;
实施
构造函数TTrackBarLabel.Create(所有者:TComponent);
开始
继承;
FLabel:=TLabel.Create(nil);
FLabel.Visible:=假;
FTimer:=TTimer.Create(无);
FTimer.间隔:=100;
FTimer.Enabled:=False;
FTimer.OnTimer:=计时器;
FSuffix:='';
偏移量:=22;
结束;
析构函数TTrackBarLabel.Destroy;
开始
FLabel.Free;//应用程序关闭时此处出现EInvalidPointer错误
FTimer.Free;
继承性破坏;
结束;
程序TTrackBarLabel.ParentChanged;
开始
继承;
FLabel.Parent:=父对象;
结束;
程序TTrackBarLabel.DoTracking;
开始
继承;
如果未分配(拇指),则退出;
FLabel.Visible:=真;
FLabel.Tag:=10;
不透明度:=1;
如果已分配(FValueToString),则
FLabel.Text:=FValueToString(值)+FSuffix
其他的
FLabel.Text:=FloatToStrF(值,ffFixed,12,1)+FSuffix;
如果方向=方位。水平,则开始
FLabel.Position.X:=Position.X+Thumb.Position.X+
(拇指宽度-襟翼宽度)*0.5;
FLabel.Position.Y:=Position.Y+偏移量;
FLabel.TextSettings.HorzAlign:=TTextAlign.Center;
结束,否则开始
FLabel.Position.X:=Position.X+偏移量;
FLabel.Position.Y:=Position.Y+Thumb.Position.Y-2;
FLabel.TextSettings.HorzAlign:=TTextAlign.Leading;
结束;
FTimer.Enabled:=False;
FTimer.Enabled:=True;
结束;
程序TTrackBarLabel.TimerTimer(发送方:ToObject);
开始
FLabel.Tag:=FLabel.Tag-1;
FLabel.Opacity:=FLabel.Tag*0.2;
如果FLabel.Tag<0,则开始
FLabel.Visible:=假;
FTimer.Enabled:=False;
结束;
结束;
结束。

通常,无效指针异常意味着您尝试释放对象两次

本例中的问题是控件在释放时释放其子控件。因此,当表单被释放时,它也会释放
TLabel
。因此,当执行
TTrackBarLabel.Destroy
时,您的
FLabel
是一个悬空指针,您不能执行
FLabel.Free

所有Delphi开发人员都知道,组件在释放时会释放其拥有的组件。一个不太为人所知的事实是,控件也释放了它的子控件

在您的情况下,只需删除
FLabel.Free
。但是,如果从未设置
FLabel
Parent
属性,则会导致内存泄漏

要确保标签在轨迹栏打开时自动释放,请使轨迹栏成为标签的所有者:

  FLabel := TLabel.Create(Self);

顺便提一下,你的建议

if Assigned(FLabel) then
  FLabel.Free;
不会有帮助,因为发生错误时,
FLabel
是一个悬空指针(它不是
nil

此外,在Delphi中,您从不写

if Assigned(FLabel) then
  FLabel.Free;
因为
TObject.Free
基本上是
如果分配了,那么就会销毁
,所以

if Assigned(FLabel) then
  FLabel.Free;
意味着


这太傻了。

谢谢。因此,总而言之,我不需要自己释放它,并确保我使trackbar成为标签的所有者。我本来是这样做的,但我想自己释放它,以确保它被释放。我在某个地方读到,用nil创建标签意味着我有责任自己释放它,但事实似乎并非如此,因为表单释放了它的孩子。为什么TTimer没有问题?我知道父对象的设置与标签不一样,但如果我使用trackbar作为所有者创建它,在释放它时仍然不会遇到任何问题。@XylemFlow:(1)正确。删除
FLabel.Free
并写入
TLabel.Create(Self)
,使轨迹栏成为标签的所有者。(2) 实际上,“每个人”都知道一个组件在被释放时会释放它所拥有的(
Owner
)组件,但控件在被释放时释放它的子组件(
Parent
)并不为人所知。(3)
t定时器
是一个组件(
t组件
),但不是控件(
t控件
)。它不是屏幕上显示的东西;它没有父对象。如果您将轨迹栏作为其所有者来创建它,则当轨迹栏处于活动状态时,它确实会被释放,但当您执行
FTimer.Free
。。。。。。将通知所有者(轨迹栏),计时器将从所有者拥有的组件列表中删除。谢谢。听起来我不知道
if Assigned(FLabel) then
  if Assigned(FLabel) then
    FLabel.Destroy;