为什么在 class 析构函数中释放成员组件会在应用程序关闭时导致 EInvalidPointer 错误?
Why does freeing a member component in a class destructor cause an EInvalidPointer error when the application is closed?
这是我创建的 class 用于将 TLabel 添加到 TTrackBar。标签在拖动时显示轨迹栏的值,然后淡出。在 运行 时间创建了一个实例,并将父项设置为表单。它工作正常,但如果轨迹栏仍然存在,则在关闭应用程序时会出错。但是,如果在 运行 时间释放轨迹栏然后关闭应用程序,则没有问题。当应用程序关闭时调试该行时 (FLabel.Free;) 我看到 FLabel 和其中的数据仍然存在,但它仍然给出该错误。我担心如果我只是删除该行,那么在 运行 时间释放对象时会发生内存泄漏。我试过将其更改为 if Assigned(FLabel) then 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.
大多数情况下,无效指针异常意味着您尝试释放一个对象两次。
这种情况下的问题是控件在释放时会释放其 子项 。因此,当释放窗体时,它也会释放 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 then Destroy
,所以
if Assigned(FLabel) then
FLabel.Free;
表示
if Assigned(FLabel) then
if Assigned(FLabel) then
FLabel.Destroy;
这很傻。
这是我创建的 class 用于将 TLabel 添加到 TTrackBar。标签在拖动时显示轨迹栏的值,然后淡出。在 运行 时间创建了一个实例,并将父项设置为表单。它工作正常,但如果轨迹栏仍然存在,则在关闭应用程序时会出错。但是,如果在 运行 时间释放轨迹栏然后关闭应用程序,则没有问题。当应用程序关闭时调试该行时 (FLabel.Free;) 我看到 FLabel 和其中的数据仍然存在,但它仍然给出该错误。我担心如果我只是删除该行,那么在 运行 时间释放对象时会发生内存泄漏。我试过将其更改为 if Assigned(FLabel) then 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.
大多数情况下,无效指针异常意味着您尝试释放一个对象两次。
这种情况下的问题是控件在释放时会释放其 子项 。因此,当释放窗体时,它也会释放 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 then Destroy
,所以
if Assigned(FLabel) then
FLabel.Free;
表示
if Assigned(FLabel) then
if Assigned(FLabel) then
FLabel.Destroy;
这很傻。