Welcome, Guest
Guest Settings
Help

Thread: TComponent::Notification and TComponent::FreeNotification



Permlink Replies: 3 - Last Post: Feb 27, 2017 12:05 PM Last Post By: Remy Lebeau (Te... Threads: [ Previous | Next ]
Jan Dijkstra

Posts: 188
Registered: 11/4/99
TComponent::Notification and TComponent::FreeNotification
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 24, 2017 2:47 AM
I have created two components, and these components have links to each other. One serves as a master, and as such contains an internal TList with references to all it's registered slaves. The other acts as a slave, and has a published property to link it to the master.

In order to manage the lifetime correctly, the slave registers itself with the master using FreeNotification. Examining the VCL source reveals that FreeNotification's implementation registers the reverse as well. So, after the call to FreeNotification, both components are setup to signal each other with Notification if one of them is deleted.

Now my problem.

I've made a test form, and on this form I've placed a list view, the master and slave components, and a page control with 4 tab sheets.

The tab sheets are named LicTabSheet1 through LicTabSheet4, the page control is named LicPageControl1, the list view is called LicListView1, the master is called DocTranslations1 and the slave is called FormTranslations1

The problem I'm having is that closing this test form in the IDE crashes the IDE. To find out why, I've added debug tracing in my master and slave components (after long searching I've determined that the link between these two is causing the crash).

Here is the Notification for the master
void __fastcall TDocTranslations::Notification (TComponent *aComponent, TOperation op)
{
  if (op == opRemove)
  {
DebugPrintf ("Doc translations - notification - %s - observer count = %d", aComponent->Name.c_str (), FObservers->Count);
    FObservers->Remove (aComponent);
DebugPrintf ("observer count = %d", FObservers->Count);    
  }
  
  TComponent::Notification (aComponent, op);
}

and here is the notification for the slave
void __fastcall TFormTranslations::Notification (TComponent *aComponent, TOperation op)
{
  if (op == opRemove )
  {
DebugPrintf ("Form translations - notification - %s", aComponent->Name.c_str ());
    if (aComponent == FTranslations)
    {
DebugPrintf ("Sever link");    
      FTranslations = NULL;
      UpdateTranslations ();
    }
  }
 
  TComponent::Notification (aComponent, op);
}

And now for the reason of this post/question. The debug trace log reveals the following when I close the form in the IDE
Doc translations - notification - LicTabSheet4 - observer count = 1
observer count = 1
Form translations - notification - LicTabSheet4
Doc translations - notification - LicTabSheet3 - observer count = 1
observer count = 1
Form translations - notification - LicTabSheet3
Doc translations - notification - LicTabSheet2 - observer count = 1
observer count = 1
Form translations - notification - LicTabSheet2
Doc translations - notification - LicTabSheet1 - observer count = 1
observer count = 1
Form translations - notification - LicTabSheet1
Doc translations - notification - LicPageControl1 - observer count = 1
observer count = 1
Form translations - notification - LicPageControl1
Doc translations - notification - LicListView1 - observer count = 1
observer count = 1
Form translations - notification - LicListView1
Form translations - destructor
Doc translations - notification - DocTranslations1 - observer count = 1
observer count = 1
Doc translations - destructor
Signal observers : 1

As you can see, the master is notified of the impending delete of each of the components on my form except the one that used FreeNotification to signal that it wants such a notification

And, because of that, the object pointer of the slave component remains in the internal list of the master, after the slave component is deleted. And as soon as you reference it after that, a crash is caused because an invalid pointer is dereferenced.

My question is simple (the answer probably is not). Why is the one component that has requested to be notified NOT notified of the impending delete of the object?

I've not put in a specific compiler version, as this is reproducable with all versions I have (CBuilder 5, XE3, XE8).

Now that I know why the IDE is crashing, I can of course fix it with a workaround by severing the link between master and slave in the respective component's destructors. But I want to know why FreeNotification and Notification do not work as advertised in the documentation.
Remy Lebeau (Te...


Posts: 7,723
Registered: 12/23/01
Re: TComponent::Notification and TComponent::FreeNotification
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 24, 2017 11:53 AM   in response to: Jan Dijkstra in response to: Jan Dijkstra
Jan wrote:

The problem I'm having is that closing this test form in the IDE
crashes the IDE.

Then you are likely not managing your master/slave components correctly.

Here is the Notification for the master

void __fastcall TDocTranslations::Notification (TComponent *aComponent, TOperation 
op)
{
    if (op == opRemove)
    {
        DebugPrintf ("Doc translations - notification - %s - observer count 
= %d", aComponent->Name.c_str (), FObservers->Count);
        FObservers->Remove (aComponent);
        DebugPrintf ("observer count = %d", FObservers->Count);
    }
    TComponent::Notification (aComponent, op);
}


and here is the notification for the slave

void __fastcall TFormTranslations::Notification (TComponent *aComponent, 
TOperation op)
{
    if (op == opRemove )
    {
        DebugPrintf ("Form translations - notification - %s", aComponent->Name.c_str 
());
        if (aComponent == FTranslations)
        {
            DebugPrintf ("Sever link");
            FTranslations = NULL;
            UpdateTranslations ();
        }
    }
    TComponent::Notification (aComponent, op);
}


The only thing that strikes me as a possible failure is UpdateTranslations()
in the slave. What does it do exactly, and are its operations safe to perform
while it is in the process of being destroyed?

Also, what do your slave's property setter look like that is calling FreeNotification()?
Does it call RemoveFreeNotification() when changing masters?

As you can see, the master is notified of the impending delete of each
of the components on my form *except the one that used
FreeNotification to signal that it wants such a notification*

The oddity I see is that when the slave is being destroyed, the master is
logging itself as being destroyed instead. What do your master and slave
destructors look like? Please show more of your relavant code, the problem
is not in your Notification() code itself.

My question is simple (the answer probably is not). Why is the one
component that has requested to be notified NOT notified of the
impending delete of the object?

The notification system works fine when used correctly. So you are clearly
doing something wrong in code we cannot see yet.

--
Remy Lebeau (TeamB)
Jan Dijkstra

Posts: 188
Registered: 11/4/99
Re: TComponent::Notification and TComponent::FreeNotification
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 27, 2017 12:06 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
The only thing that strikes me as a possible failure is UpdateTranslations()
in the slave. What does it do exactly, and are its operations safe to perform
while it is in the process of being destroyed?

Also, what do your slave's property setter look like that is calling FreeNotification()?
Does it call RemoveFreeNotification() when changing masters?

As you can see, the master is notified of the impending delete of each
of the components on my form *except the one that used
FreeNotification to signal that it wants such a notification*

The oddity I see is that when the slave is being destroyed, the master is
logging itself as being destroyed instead. What do your master and slave
destructors look like? Please show more of your relavant code, the problem
is not in your Notification() code itself.

My question is simple (the answer probably is not). Why is the one
component that has requested to be notified NOT notified of the
impending delete of the object?

The notification system works fine when used correctly. So you are clearly
doing something wrong in code we cannot see yet.

--
Remy Lebeau (TeamB)

Here are the relevant functions you asked for

First, the property setter for the Translations property in the slave, to connect it up to the master
void __fastcall TFormTranslations::SetTranslations (TDocTranslations *translations)
{
 
  if (ComponentState.Contains (csLoading))
  {
    FTranslations = translations;
    if (translations) translations->AddObserver (this);
  }
  else if (FTranslations != translations)
  {
    if (FTranslations) FTranslations->RemoveObserver (this);
    FTranslations = translations;
    if (FTranslations) FTranslations->AddObserver (this);
 
    UpdateTranslations ();
  }
}

Next, the two management routines at the master, to manipulate the list of slaves
bool __fastcall TDocTranslations::AddObserver (TFormTranslations *observer)
{
  if (! observer) return false;
 
  int index = FObservers->IndexOf (observer);
  if (index < 0)
  {
    FObservers->Add (observer);
    FreeNotification (observer);
 
    return true;
  }
 
  return false;
}
 
bool __fastcall TDocTranslations::RemoveObserver (TFormTranslations *observer)
{
  if (! observer) return false;
 
//DebugPrintf ("Remove observer - %s", observer->Name.c_str ());
 
  int index = FObservers->IndexOf (observer);
  if (index >= 0)
  {
    FObservers->Remove (observer);
    RemoveFreeNotification (observer);
 
    return true;
  }
 
  return false;
}


The UpdateTranslations routine at the slave you asked for
void __fastcall TFormTranslations::UpdateTranslations (void)
{
  // Crawl the owner's list of components, and signal each to grab the
  // translations they need.
//DebugPrintf ("Form translations - update translations");
  TComponent *owner = Owner;
  if (! owner) return;
 
  if (owner->ComponentState.Contains (csDestroying)) return;
 
  int index = 0;
  int count = owner->ComponentCount;
//DebugPrintf ("  Signalling %d components", count);
 
  while (index < count)
  {
    TComponent *component = owner->Components [index];
    TControl   *control   = dynamic_cast<TControl *> (component);
 
    if (control)
    {
      control->Perform (LICM_TRANSLATE, FDocumentTag.Length () ? (int) this : 0, (int) FTranslations);
    }
 
    index++;
  }
 
  UpdateFormCaption ();
}
 
void __fastcall TFormTranslations::UpdateFormCaption (void)
{
  TForm *form = dynamic_cast<TForm *> (Owner);
  if (form)
  {
    if (FTranslations && FTranslations->Active)
    {
      form->Caption = FTranslations->TranslateDef (FDocumentTag, FTranslationTag, FCaption);
    }
    else
    {
      form->Caption = FCaption;
    }
  }
}


And finally the destructors for both master and slave components.
__fastcall TFormTranslations::~TFormTranslations (void)
{
// Commented out section below cleans up if Notification fails.
/*
  if (FTranslations)
  {
    FTranslations->RemoveObserver (this);
    FTranslations = NULL;
  }
*/
 
//DebugPrintf ("Form translations - destructor");
}
 
__fastcall TDocTranslations::~TDocTranslations (void)
{
//DebugPrintf ("Doc translations - destructor");
  Clear ();
 
// Commented out section below cleans up if Notification fails.
/*
  int index = FObservers->Count;
  while (--index >= FObservers->Count)
  {
    TFormTranslations *trans = (TFormTranslations *) FObservers->Items [index];
    trans->Translations = NULL;
  }
*/
 
//DebugPrintf ("observer count = %d", FObservers->Count);  
  delete FObservers;
  delete FDocTagList;
  delete FLanguageList;
}
Remy Lebeau (Te...


Posts: 7,723
Registered: 12/23/01
Re: TComponent::Notification and TComponent::FreeNotification
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 27, 2017 12:05 PM   in response to: Jan Dijkstra in response to: Jan Dijkstra
Jan wrote:

First, the property setter for the Translations property in the slave,
to connect it up to the master

You don't really need the ComponentState check since FTranslations is initially
NULL when the component instance is created, so the effect of calling SetTranslations()
during DFM loading will just be to call AddObserver() and ignore RemoveObserver().

Next, the two management routines at the master, to manipulate the
list of slaves

You are not calling FreeNotification() correctly. Try this instead:

bool __fastcall TDocTranslations::AddObserver (TFormTranslations *observer)
{
    if (observer)
    {
        if (FObservers->IndexOf (observer) < 0)
        {
            FObservers->Add (observer);
            observer->FreeNotification (this);
            return true;
        }
    }
    return false;
}
 
bool __fastcall TDocTranslations::RemoveObserver (TFormTranslations *observer)
{
    if (observer)
    {
        //DebugPrintf ("Remove observer - %s", observer->Name.c_str ());
        if (FObservers->Remove (observer) >= 0)
        {
            observer->RemoveFreeNotification (this);
            return true;
        }
    }
    return false;
}


The UpdateTranslations routine at the slave you asked for

The only problem I see is that when calling control->Perform(), you are type-casting
your input values to 'int'. That is fine for a 32-bit compilation, but will
fail on a 64bit compilation due to pointer truncation. Perform() expects
WPARAM and LPARAM values, which are appropriately sized based on architecture,
so cast accordingly:

control->Perform (LICM_TRANSLATE, (WPARAM) (FDocumentTag.Length () ? this 
: NULL), (LPARAM) FTranslations);


And finally the destructors for both master and slave components.

The loop in the TDocTranslations destructor is wrong (if you were to re-enable
it, which you don't need to since TFormTranslations::Notification() should
be handling everything for you when used correctly). You are comparing the
decrementing index to the current FObservers->Count when you should be comparing
it to 0 instead:

__fastcall TDocTranslations::~TDocTranslations (void)
{
    //DebugPrintf ("Doc translations - destructor");
 
    int index = FObservers->Count;
    //while (--index >= FObservers->Count)
    while (--index >= 0)
    {
        TFormTranslations *trans = (TFormTranslations *) FObservers->Items 
[index];
        trans->Translations = NULL;
    }
 
    //DebugPrintf ("observer count = %d", FObservers->Count);
    delete FObservers;
    delete FDocTagList;
    delete FLanguageList;
}


Alternatively, don't even use an index at all:

while (FObservers->Count > 0)
{
    TFormTranslations *trans = (TFormTranslations *) FObservers->Last(); 
// or ->First()
    trans->Translations = NULL;
}


Otherwise, your index value is NEVER >= the Count, so the loop body is NEVER
entered to set the TFormTranslations::Translations properties to NULL. And
if your Notification() handling is not setup correctly, then you would not
be clearing out the Translation pointers correctly, would could lead to the
crashes you were experiencing.

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

Server Response from: ETNAJIVE02