Delphi FMX自定义TTrackBarLabel应用关闭时析构释放FLabel报EInvalidPointer错误原因
触发EInvalidPointer的核心原因
这是典型的双所有权导致的重复释放内存问题,完全和FMX控件的销毁机制对应:
- 你创建FLabel时用
TLabel.Create(nil),本意是由自定义类完全接管FLabel的生命周期 - 但在
ParentChanged方法里执行FLabel.Parent := Parent后,FLabel因为绑定到和TTrackBarLabel同一个父容器(窗体),会自动加入窗体的控件管理列表:父容器销毁时,会按照从后往前的顺序遍历列表,自动Free所有归属自己的子控件 - 此时FLabel和TTrackBarLabel是平级的窗体直接子控件,两者的销毁顺序完全不受自定义类控制:如果FLabel在控件列表里的顺序比TTrackBarLabel靠后,窗体销毁流程会先Free掉FLabel,等轮到销毁TTrackBarLabel、执行析构里的
FLabel.Free时,这块内存已经被释放过了,自然抛出EInvalidPointer。
调试时你看到FLabel的内部数据还没失效,只是因为程序刚释放完内存、还没往这块地址写入新数据,实际上这个指针已经是无效野指针了。
之前你手动在运行期释放TTrackBarLabel时不会出错,是因为此时窗体还没启动销毁流程:你释放FLabel时,它会自动从窗体的控件列表里移除,等后续窗体销毁时列表里已经没有FLabel的引用,自然不会出现重复释放。
解决方法
核心是保证FLabel的所有权唯一,从根源上避免重复释放,两种可选实现方案:
- 方案1:在析构函数释放FLabel前,先切断它和原父容器的关联,取消父容器对它的销毁责任。修改后的析构逻辑:
destructor TTrackBarLabel.Destroy; begin if Assigned(FLabel) then FLabel.Parent := nil; FLabel.Free; FTimer.Free; inherited Destroy; end; - 方案2:把FLabel的父控件改为TTrackBarLabel自身,让FLabel成为自定义类的内部子控件,所有权完全归属当前类。这种方案下不需要手动Free FLabel,因为当前控件销毁时会自动遍历自己的子控件列表完成释放,只要删掉析构里的
FLabel.Free代码即可,完全不会产生内存泄漏。
需要配合修改两处逻辑:- 修改ParentChanged方法里的Parent赋值:
procedure TTrackBarLabel.ParentChanged; begin inherited; FLabel.Parent := Self; end; - 调整DoTracking里的坐标计算,把原来相对于窗体的坐标改成相对于当前控件的内部坐标:
// 水平方向分支的坐标计算修改为 FLabel.Position.X := Thumb.Position.X + (Thumb.Width - FLabel.Width) * 0.5; FLabel.Position.Y := FOffset; // 垂直方向分支的坐标计算修改为 FLabel.Position.X := FOffset; FLabel.Position.Y := Thumb.Position.Y - 2;
- 修改ParentChanged方法里的Parent赋值:
补充说明
之前你尝试加if Assigned(FLabel) then FLabel.Free没有效果,是因为触发问题的场景里FLabel指针本身不是nil,只是它指向的内存已经被外部提前释放了——Assigned只能判断指针本身是不是空值,没法检测指向的内存是否合法。
内容的提问来源于stack exchange,提问作者XylemFlow
相关产品推荐
相关产品推荐

