今天一直在考虑是否有一个内存泄漏的问题,看一下Delphi的源码。
结果竟然发现控件的Parent能够释放它的子控件,一直以为只有控件的AOwner才能释放,汗!!!!现在总结如下:
情况1:直接从TComponent继承下来的组件,将由它的AOwner释放(如果你不自己释放,下来的情况也一样)
情况2: 如果控件是从TControl继承下来的,你设定了他的Parent,当这个Parent释放时,它会释放掉所有的子控件。
情况3:当最后AOwner释放它的Components时,从最后一个组件开始删除,这里这个组件也会删除它内部的Comonents,如果组件是TWinControl,则它会释放掉它所有的子控件。所以说,Aowner只是负责释放所有的不可视组件,和一部分的可视控件,大部分可视控件还是由Parent来释放的。标记如上,引以为诫!!!大汗,引以为诫!!!!
结果竟然发现控件的Parent能够释放它的子控件,一直以为只有控件的AOwner才能释放,汗!!!!现在总结如下:
情况1:直接从TComponent继承下来的组件,将由它的AOwner释放(如果你不自己释放,下来的情况也一样)
情况2: 如果控件是从TControl继承下来的,你设定了他的Parent,当这个Parent释放时,它会释放掉所有的子控件。
情况3:当最后AOwner释放它的Components时,从最后一个组件开始删除,这里这个组件也会删除它内部的Comonents,如果组件是TWinControl,则它会释放掉它所有的子控件。所以说,Aowner只是负责释放所有的不可视组件,和一部分的可视控件,大部分可视控件还是由Parent来释放的。标记如上,引以为诫!!!大汗,引以为诫!!!!
好象他们的释放机制是差不多的,就是bject list和notify!
而从TControl继承下来的,就是可视控件,
换句话说,就是有Parent的控件,和没有Parent的控件。举一个例子:
var
btn: TButton;
begin
btn:= TButton.create(Self); //这里的Self指的是TForm;
btn.Parent:= Panel1; //将按钮的Parent设为Panel
Panel1.Free; //这时候,Panel会释放掉Btn
end;
具体来解释(跟踪一下源代码,你会理解得更清楚):
当btn:= TButton.create(Self);之后,TForm的Components中就加入了这个Btn
当btn.Parent:= Panel1; 之后,Panel的FWinControls就加入了Btn
这个时候,Components和FWinControls都有Btn。
当Panel1.Free; 之后,Panel1会负责将Btn释放掉。同时FWinControl会删除这一项,Component也会删除这一项。假设,如果btn.Parent:= Panel1; 改为btn.Parent:= Self;
则可能会由Btn的Aowner即Form来释放。
var
paPare:TPanel;
btn: TButton;
begin
paPare:=TPanel.Create(self);//动态创建一个Panel
paPare.Parent:=self;
paPare.SetBound(0,0,200,400);
btn:= TButton.create(Self);//动态创建一个Button
btn.Parent:= paPare;
paPare.Free; //释放Panel
if Not Assigned(btn) then
ShowMessage('动态创建的按钮已经释放')
else
ShowMessage('动态创建的按钮没胡释放,你的结论是错误的');
var
I: Integer;
Instance: TControl;
begin
Destroying;
if FDockSite then
begin
FDockSite := False;
RegisterDockSite(Self, False);
end;
FDockManager := nil;
FDockClients.Free;
if Parent <> nil then RemoveFocus(True);
if FHandle <> 0 then DestroyWindowHandle;
I := ControlCount;
while I <> 0 do
begin
Instance := Controls[I - 1];××××××子控件链表
Remove(Instance);×××××××××
Instance.Destroy;×××××××××
I := ControlCount;
end;
FBrush.Free;
if FObjectInstance <> nil then Classes.FreeObjectInstance(FObjectInstance);
inherited Destroy;
end;我们可以得出结论
1.确切如楼主所说,TWinControl在Destroy时释放了它的子组件
2.由于用了Destroy方法,会不会导致重复的释放组件呢?
源码中我们也可以得出结论: 1.TWinControl有两个List维护了其子组件列表 FWinControlst(可视)和FControls(不可视)
2.所有的控件的父组件都是TWinControl,可能是这个父组件要有回调函数来负责消息的传递和分发,来使的非可视组件也可以处理消息吧。
如果按你的解释,我的结论其实是没有错的,因为Assigned只有在对象为Nil的时候,才会返回False,如果不为Nil它是返回True的
而Btn在被Panel释放了内存之后,Btn这个指针其实还是指向刚才那块内存,并不是指向了Nil,只不过区别在于,Btn刚才那块内存在没有释放之前,其他程序是不能访问的,而释放之后,其他程序就可以访问了。你可以在你的代码后面加一样关于Btn的操作,就会出错。
if Not Assigned(btn) then
ShowMessage('动态创建的按钮已经释放')
else
ShowMessage('动态创建的按钮没胡释放,你的结论是错误的');
这样好像不能够说明btn没有被释放。
在这儿用btn.Parent := Self;会报AV错。
关于你的结论:
2.由于用了Destroy方法,会不会导致重复的释放组件呢?
为什么会有这个结论呢,请你解释一下。另外:
1.TWinControl有两个List维护了其子组件列表 FWinControlst(可视)和FControls(不可视)
2.所有的控件的父组件都是TWinControl,可能是这个父组件要有回调函数来负责消息的传递和分发,来使的非可视组件也可以处理消息吧。其实,FWinControls是放TWinControl的控件,而FControls可以说是放TGraphicControl的控件,而不是不可视的控件。
而非可视组件其实是没有父组件了,只有从TControl继承下来的才会有父组件,而TControl继承下来的都是可视的控件。
当时考虑到如果一个控件C即是一个可视控件A的子控件,又是一个Component B 的子组件,那么在
A.Destroy时 会调用C.Destroy,后来当B.Destroy时,C(这时会nil)也会调用C.Destroy,
会出现释放错误才对。后来:
才发现如果一个组件释放时,会向调用Ower和Parent的方法发出通知,移除Ower和Parent的对自己的引用。也就是说上面的情形是不会出现的。当A.Destroy时 调用C.Destroy,B中的子组件链表中已经没有C了,也就不会再次调用C.Destroy另
关于可视不可视是我理解有误,多谢楼主提醒。
var btn:TButton;
begin
btn:=TButton.Create(panel1);
btn.Parent:=Panel1;
btn.Left:=0;
btn.Top:=0;
/////////////只有如上代码:在Panel1上显示一个按钮
btn.Parent:=panel2;
btn.Left:=0;
btn.Top:=0;
showmessage(IntToStr(Panel2.ControlCount));
/////////////只有如上代码:在Panel2上显示一个按钮
btn.Repaint;
sleep(3000);//明显的看到panel2上出现按钮
panel1.Free;
////////////按钮和Panel一块消失了
showmessage(IntToStr(Panel2.ControlCount));//孩子数量
end;
执行完了上面的代码会有更直观的了解...不知道ControlCount是怎么减去的...
procedure TWinControl.Remove(AControl: TControl);
begin
if AControl is TWinControl then
begin
ListRemove(FTabList, AControl);
ListRemove(FWinControls, AControl);
end else
ListRemove(FControls, AControl);
AControl.FParent := nil;
end;procedure ListRemove(var List: TList; Item: Pointer);
begin
List.Remove(Item);
if List.Count = 0 then
begin
List.Free;
List := nil;
end;
end;
所以我打算看一看Window Programming。
一个TwinControl被销毁的时候,我们为了防止内存的泄漏,我们会做什么工作那?
1、首先,释放以他为Parent的那些控件,不然的话,那些控件将不知道自己显示在什么上面。
2、再者,必须释放以他为owner的那些控件,所有者是一个控件存在的基础,他被销毁了,以他为owner的控件也将不存在。
3、但是有时候一个控件的Parent和owner是不一样的,所以remove函数在销毁控件的同时,也把她在其他控件上的记录也都抹去了。这样就防止了,Parent释放的时候释放掉了一次,owner在释放一次时候可能出现的错误。