Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: Component events



Permlink Replies: 3 - Last Post: Sep 21, 2015 11:59 AM Last Post By: Remy Lebeau (Te...
Jan Dijkstra

Posts: 206
Registered: 11/4/99
Component events
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 17, 2015 4:20 AM
First up, I'm not quite sure if this (sub)forum is the best place for my question, as it involves both using components, writing components and the c++ language extensions to deal with the VCL.

Anyways, here it goes.

I've written lots of components. With one of them, I bumped into an issue this week. And what I really want to know, if there is a real solution for it, instead of a patchwork of work arounds which I've applied to date to fix it.

The problem: My component (but this is, to an extend, also true for standard VCL components like the TCheckBox or TRadioButton) reacts to changes to some of it's properties by firing off an event. Within the component itself, I've taken care of the fact that, during steaming of the component, the event goes of while not all properties are streamed in. The event firing is deferred to the Loaded member function in this case.

That solves the (possibly) inconsistent state problem within the component.

However, it does not solve the issue I had to repair this week. The event fired, as designed, from within the Loaded member function. In the event handler on the form, I also check for the csLoading state in ComponentState, and bail out directly if it's still set. That prevents premature access to component pointers who haven't been streamed in (yet).

In this case, however, the csLoading state on the form was already cleared when the event arrived from my component (which was fired from it's Loaded member function). The event handler needed, in response to the event, to manipulate a string list, which is declared as a private member of the form, and created in the form's constructor.

And this is where it went wrong. The form's ComponentState no longer contained csLoading to indicate the form is still in it's build-up phase, but the constructor (my constructor, not TForm.Create) hadn't been executed yet when the event fired. And thus was accessing a pointer which wasn't initialised yet, causing an access violation.

I know this is a rare circumstance which only can show itself like this because pascal and c++ deal differently with constructors. But I was wondering if the VCL engineers have, in one way or another, taken this into account, and provide some sort of mechanism I can check (besides ComponentState) in runtime to make sure the form's constructor (or any constructor for that matter that I write in c++ instead of pascal) has completed execution so that implementations of event handlers don't refer prematurely to stuff that hasn't been created yet.
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Component events
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 17, 2015 10:00 AM   in response to: Jan Dijkstra in response to: Jan Dijkstra
Jan wrote:

First up, I'm not quite sure if this (sub)forum is the best place for my
question,
as it involves both using components, writing components and the c++ language
extensions to deal with the VCL.

You could have posted separate questions, each one to an appropriate forum.

The problem: My component (but this is, to an extend, also true for
standard VCL components like the TCheckBox or TRadioButton) reacts to
changes to some of it's properties by firing off an event. Within the
component itself, I've taken care of the fact that, during steaming of
the component, the event goes of while not all properties are streamed
in. The event firing is deferred to the Loaded member function in this
case.

As you should be.

However, it does not solve the issue I had to repair this week. The event
fired, as designed, from within the Loaded member function. In the event
handler on the form, I also check for the csLoading state in ComponentState,
and bail out directly if it's still set. That prevents premature access to
component pointers who haven't been streamed in (yet).

Loaded() is the last chance for your component to do anything after it has
finished being streamed. If your Form chooses to ignore the events because
it is not ready for them then it is the Form's responsibility, not your component's,
to remember that the events were fired and process that information at a
later time when ready. Either by caching the data that the events provide
(if any), or going back to the component to retreive the latest data available
at that time.

The event handler needed, in response to the event, to manipulate a string
list, which is declared as a private member of the form, and created in the
form's constructor.

And this is where it went wrong. The form's ComponentState no longer
contained csLoading to indicate the form is still in it's build-up
phase, but the constructor (my constructor, not TForm.Create) hadn't
been executed yet when the event fired. And thus was accessing a pointer
which wasn't initialised yet, causing an access violation.

In Delphi, that is very easy to fix. DFM streaming is performed in the TCustomForm
constructor. A derived TFormX constructor can create the TStringList object
before then calling the base TForm constructor.

However, in C++, you don't have that option. And worse, since DFM streaming
is being performed in a base class constructor, it is not safe for derived-class
event handlers to access derived-class members at all. So best not to trigger
the events during DFM streaming at all.

I know this is a rare circumstance which only can show itself like
this because pascal and c++ deal differently with constructors. But I
was wondering if the VCL engineers have, in one way or another,
taken this into account, and provide some sort of mechanism I can
check (besides ComponentState) in runtime to make sure the form's
constructor (or any constructor for that matter that I write in c++
instead of pascal) has completed execution so that implementations
of event handlers don't refer prematurely to stuff that hasn't been
created yet.

No, they have not. Of course, the answer is simple - don't access your TStringList
object until it has been constructed first. In this scenario, when your
event handlers are triggered, the TStringList pointer will be NULL (TObject-derived
classes are guaranteed to have all class members initialized to zero before
any constructors are invoked), so check for that and ignore the TStringList.
When your derived Form constructor is eventually called, it can create the
TStringList and initialize it with the already-streamed data from your component
as needed.

--
Remy Lebeau (TeamB)
Jan Dijkstra

Posts: 206
Registered: 11/4/99
Re: Component events
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 21, 2015 4:21 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Jan wrote:

The problem: My component (but this is, to an extend, also true for
standard VCL components like the TCheckBox or TRadioButton) reacts to
changes to some of it's properties by firing off an event. Within the
component itself, I've taken care of the fact that, during steaming of
the component, the event goes of while not all properties are streamed
in. The event firing is deferred to the Loaded member function in this
case.

As you should be.

However, it does not solve the issue I had to repair this week. The event
fired, as designed, from within the Loaded member function. In the event
handler on the form, I also check for the csLoading state in ComponentState,
and bail out directly if it's still set. That prevents premature access to
component pointers who haven't been streamed in (yet).

Loaded() is the last chance for your component to do anything after it has
finished being streamed. If your Form chooses to ignore the events because
it is not ready for them then it is the Form's responsibility, not your component's,
to remember that the events were fired and process that information at a
later time when ready. Either by caching the data that the events provide
(if any), or going back to the component to retreive the latest data available
at that time.

The event handler needed, in response to the event, to manipulate a string
list, which is declared as a private member of the form, and created in the
form's constructor.

And this is where it went wrong. The form's ComponentState no longer
contained csLoading to indicate the form is still in it's build-up
phase, but the constructor (my constructor, not TForm.Create) hadn't
been executed yet when the event fired. And thus was accessing a pointer
which wasn't initialised yet, causing an access violation.

In Delphi, that is very easy to fix. DFM streaming is performed in the TCustomForm
constructor. A derived TFormX constructor can create the TStringList object
before then calling the base TForm constructor.

However, in C++, you don't have that option. And worse, since DFM streaming
is being performed in a base class constructor, it is not safe for derived-class
event handlers to access derived-class members at all. So best not to trigger
the events during DFM streaming at all.

Remy Lebeau (TeamB)

I'm left with one remaining question though.

I've implemented literally dozens of components to date. A lot of these components create private storage for various properties on the heap inside their respective constructors. To date, for all of these components, when the streaming process starts, that local storage is always there. Which means that my c++ constructor must have been called before the streaming process started.

Why doesn't this work for forms in the same way?
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Component events
Click to report abuse...   Click to reply to this thread Reply
  Posted: Sep 21, 2015 11:59 AM   in response to: Jan Dijkstra in response to: Jan Dijkstra
Jan wrote:

I've implemented literally dozens of components to date. A lot of
these components create private storage for various properties on
the heap inside their respective constructors. To date, for all of
these components, when the streaming process starts, that local
storage is always there. Which means that my c++ constructor
must have been called before the streaming process started.

Component constructors are called during DFM streaming, as each component
object is discovered in the DFM stream. A component's properties are not
streamed in until after the component constructor has finished first.

The TCustomForm constructor is called before DFM streaming begins, as it
is the one who is invoking the streaming system to begin with.

In Delphi, a derived TFormX (or TDataModuleX or TFrameX) constructor is called
before DFM streaming begins. Objects in Delphi are constructed from derived
class down to base class. Thus, a base class constructor can safely call
any derived virtual methods as the derived VTable is already in place, and
can safely access any derived data members that have already been constructed
by the derived constructor.

In Delphi, the sequence for creating a Form object is:

- Create TForm1 object
- which calls TForm1.Create()
- whose body explicitly calls TCustomForm.Create()
- whose body performs DFM streaming
- any events triggered during DFM streaming can access TFormX data members
and call TFormX methods.

In C++, a derived TFormX (or TDataModuleX or TFrameX) constructor is called
after DFM streaming finishes. Objects in C++ are constructed base class
up to derived class. Thus, a base class constructor CANNOT call any derived
virtual methods as the derived VTable is not available yet, can cannot access
any derived data members as they have not been constructed yet. The derived
class effectively does not exist yet when a base class is being constructed.

In C++, the sequence for creating a Form object is:

- Create TForm1 object
- which calls TForm1 constructor
- whose intialization list calls TCustomForm constructor
- whose body performs DFM streaming
- any events triggered during DFM streaming cannot access TFormX data members
or call TFormX methods.
- then TForm1 constructor body is entered after TForm1 *initialization
list* is finished.

In either case, a derived TFormX constructor body can access DFM-streamed
component objects.

What makes this more complicated is that the DFM streaming needs to update
TFormX published component pointers after creating each component object
in the DFM stream. Obviously, this would be illegal in C++ by the above
rule that a base class constructor cannot access derived class data members.
However, TForm derives from TObject, and in C++ TObject-derived classes
adopt certain characteristics from Delphi for compatibility reasons.

--
Remy Lebeau (TeamB)
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02