Recent

Author Topic: Why is this crashing?  (Read 1751 times)

kupferstecher

  • Hero Member
  • *****
  • Posts: 583
Why is this crashing?
« on: April 15, 2021, 07:07:09 pm »
Hello,

attached a minimum project, with a derived class (TPanel). When I override the procedure Resize and try to access a child control in that procedure then the application crashes on startup. Seems the components Resize procedure is called before even the components Constructor was finished.

What I am doing wrong? Is this intended behaviour?

The derived class is created in the Forms OnCreate handler:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender:TObject);
  2. begin
  3.   LabelPanel:= TLabelPanel.Create(self);
  4.   LabelPanel.Parent:= self;
  5.   LabelPanel.Align:= alClient;
  6. end;

And the derived class:
Code: Pascal  [Select][+][-]
  1. Type TLabelPanel = class(TPanel)
  2.     BackgndShape: TShape;
  3.   public
  4.     Constructor Create(TheOwner:TComponent);override;
  5.     Destructor Destroy;override;
  6.     Procedure Resize;override;
  7.  
  8. end;
  9.  
  10. IMPLEMENTATION
  11.  
  12. Constructor TLabelPanel.Create(TheOwner:TComponent);
  13. begin
  14.   inherited Create(TheOwner);
  15.  
  16.   BackgndShape:= TShape.Create(Self);
  17.   BackgndShape.Parent:= Self;
  18.   BackgndShape.Brush.Color:= clGreen;
  19.   BackgndShape.Pen.Width:= 10;
  20.   BackgndShape.Left:= 0;
  21.   BackgndShape.Top:= 0;
  22.  
  23. end;
  24.  
  25. Procedure TLabelPanel.Resize;
  26. begin
  27.   inherited Resize;
  28.  
  29.   //if assigned(BackgndShape) then begin   //No chrash if checked for assignment first
  30.     BackgndShape.Left:= 10;
  31.     BackgndShape.Top:= 10;
  32.     BackgndShape.Width:= Width-20;
  33.     BackgndShape.Height:= Height-20;
  34.   //end;
  35.  
  36. end;
  37.  

EDIT: Lazarus 2.0.10 and 2.0.12 on Win64
« Last Edit: April 15, 2021, 07:09:29 pm by kupferstecher »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9754
  • Debugger - SynEdit - and more
    • wiki
Re: Why is this crashing?
« Reply #1 on: April 15, 2021, 07:16:05 pm »
Well, without testing, I would say: Move the TLabelPanel  "inherited create" call to the end of the constructor.

As for Resize being called, no idea if that is "by design".
But, even if there is no handle, during "create" the bounds are initialized (there is some func to deliver default values). So if the bounds are set, then Resize would be called.

When you create a form from an LFM a lot of other stuff happens. You need to check the TReader and related classes. Afaik (but that is from very sketchy memory) the class creation is split into Memory alloc, and constructor. So the order may be different. Also cslLoading will be set (afaik even while the constructor runs / or maybe in the parent/owner?), which again can affect behaviour. If interested dive into the source yourself....
« Last Edit: April 15, 2021, 07:17:40 pm by Martin_fr »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9754
  • Debugger - SynEdit - and more
    • wiki
Re: Why is this crashing?
« Reply #2 on: April 15, 2021, 07:23:01 pm »
Right so, this is what I meant (LFM reading)

fpc_3.2.2\source\rtl\objpas\classes\reader.inc
line 945 ....
TReader.ReadComponent
Code: Pascal  [Select][+][-]
  1.             NewComponent := TComponent(ComponentClass.NewInstance); // Get Memory
  2.             if ffInline in Flags then
  3.               NewComponent.FComponentState :=
  4.                 NewComponent.FComponentState + [csLoading, csInline];
  5.             NewComponent.Create(Owner);    // Constructor (yes, called on the instance)
  6.  

Of course TReader may be subclassed, and more behavioural changes may exist.

kupferstecher

  • Hero Member
  • *****
  • Posts: 583
Re: Why is this crashing?
« Reply #3 on: April 15, 2021, 08:22:29 pm »
Thanks for the fast respond!

Well, without testing, I would say: Move the TLabelPanel  "inherited create" call to the end of the constructor.
I just checked, that doesn't work either, when setting the Parent of the child (i.e. self), then the application crashes, because the component isn't properly initialized.

The place where Resize is called I also found. In inherited Create the default bounds are set, there also Resize is called.

Code: Pascal  [Select][+][-]
  1. if (not (csLoading in ComponentState)) then
  2.   begin
  3.     Resize;
  4.     DebugInvalidPos(5);
  5.     CheckOnChangeBounds;
  6.     DebugInvalidPos(6);
  7.     // for delphi compatibility send size/move messages
  8.     if PosSizeChanged then
  9.       SendMoveSizeMessages(SizeChanged,PosChanged);
  10.   end;

Seems like csLoading is not set. Imho Resize actually should be called, probably its just not possible to do that after "inherited" (in an automatic manner). So I guess it is like it should be. Not ideal though.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9754
  • Debugger - SynEdit - and more
    • wiki
Re: Why is this crashing?
« Reply #4 on: April 15, 2021, 09:42:23 pm »
I just checked, that doesn't work either, when setting the Parent of the child (i.e. self), then the application crashes, because the component isn't properly initialized.

If the owner (i.e Tchild.Create(self)) works, then you can move the  ".Parent" assignment after the initialized.

Of course that breaks the code into small bits... And I actually do not know if "Create(Self)" is allowed, before "Self" is fully created. (Because it needs the list for all the owned components / Though if that list is created on request / if needed, then it should be ok).

The other way is to check in "Resize".

Quote
Seems like csLoading is not set.
It is only set, if someone sets it.
TReader does set it.

You can call Self.Loading to set it.
And then Self.Loaded to clear it.

But if you register your component, and it gets created from LFM, then you may want to check, if it was set before. So you don't interfere with TReader.
Because TReader may defer it until other components have also been loaded. So components can fix up all their relationships.

Not sure, if the docs have anything on the topic....

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9754
  • Debugger - SynEdit - and more
    • wiki
Re: Why is this crashing?
« Reply #5 on: April 15, 2021, 09:55:43 pm »
Also for some reason TReader is not always setting it (or not always at the same time). Depends on inlining, which IIRC is e.g. used by frames....
But I do not recall all details....

MarkMLl

  • Hero Member
  • *****
  • Posts: 6646
Re: Why is this crashing?
« Reply #6 on: April 16, 2021, 12:20:19 am »
Also for some reason TReader is not always setting it (or not always at the same time). Depends on inlining, which IIRC is e.g. used by frames....
But I do not recall all details....

Aaah... I've been meaning to ask for some while why Lazarus/LCL frames required a certain minimum compiler version. (Assume you've just answered that question, or at least provided a broad hint).

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9754
  • Debugger - SynEdit - and more
    • wiki
Re: Why is this crashing?
« Reply #7 on: April 16, 2021, 12:32:55 am »
How old a compiler version? And does not the rest of the IDE need an up todate compiler too (unless you use an old IDE)?

MarkMLl

  • Hero Member
  • *****
  • Posts: 6646
Re: Why is this crashing?
« Reply #8 on: April 16, 2021, 08:34:24 am »
How old a compiler version? And does not the rest of the IDE need an up todate compiler too (unless you use an old IDE)?

Assuming that's for me: the IDE tends to have a bit of slop in compiler tolerance, but my recollection is that when frames were introduced they required that it be built with a specific version of the compiler because of some detail of code generation... it was somewhere around 2.6.4. I never asked why and didn't experiment.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

kupferstecher

  • Hero Member
  • *****
  • Posts: 583
Re: Why is this crashing?
« Reply #9 on: April 16, 2021, 11:10:30 am »
If the owner (i.e Tchild.Create(self)) works, then you can move the  ".Parent" assignment after the initialized.

Of course that breaks the code into small bits...
I just tried that, it works. Yes, as you said, its not ideal for compact code. Also the 'self' thing I don't like. For such things I always fear that something gets broken in deeper levels without a direct crash. Thats also why I opened this topic. But seems at least one can relay on the crash because the children are automatically initialized with nil.

What should be possible regarding 'self', is to use the owner of the Form (TheOwner passed in Create) instead of 'self' and then deleting the Child manually in Destroy instead of relying on the Owner.

Quote
Quote
Seems like csLoading is not set.
It is only set, if someone sets it.
TReader does set it.

You can call Self.Loading to set it.
And then Self.Loaded to clear it.

Understood, the Application actually has no chance to set csLoading, as I create the control on runtime. Even if it would be set while the "FormCreate" handler is called, it would still be crashing in cases, the control is created in any other circumstance.

So I understand it is how it is :-)

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9754
  • Debugger - SynEdit - and more
    • wiki
Re: Why is this crashing?
« Reply #10 on: April 16, 2021, 12:38:22 pm »
Also the 'self' thing I don't like. For such things I always fear that something gets broken in deeper levels without a direct crash. Thats also why I opened this topic. But seems at least one can relay on the crash because the children are automatically initialized with nil.

What should be possible regarding 'self', is to use the owner of the Form (TheOwner passed in Create) instead of 'self' and then deleting the Child manually in Destroy instead of relying on the Owner.
It is perfectly legal to do
Code: Pascal  [Select][+][-]
  1. LabelPanel:= TLabelPanel.Create(Owner);
 
or IIRC even
Code: Pascal  [Select][+][-]
  1. LabelPanel:= TLabelPanel.Create(nil);
 

In the first case, you can destroy it if you do so before the owner gets destroyed. But if you get destroyed be the owner, then LabelPanel may have already gone.
If you destroy it, that will remove it from the owners list.
So you might better use
Code: Pascal  [Select][+][-]
  1. TLabelPanel.FreeNotification(self); // and back you are with self
and in the current class, that holds the reference
Code: Pascal  [Select][+][-]
  1. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
Which allows you to nil TLabelPanel, when it gets destroyed.

In any case you do need to set the parent. (which might be self, or might have a reference to self)

kupferstecher

  • Hero Member
  • *****
  • Posts: 583
Re: Why is this crashing?
« Reply #11 on: April 16, 2021, 07:42:21 pm »
In the first case, you can destroy it if you do so before the owner gets destroyed. But if you get destroyed be the owner, then LabelPanel may have already gone.
Right, I missed that.

Thank you!

 

TinyPortal © 2005-2018