Home | News | Documentation | Download

Proper destruction of GUI objects

Hello,

I am trying to understand how to correctly destroy GUI objects. Attached you can find a minimal example of a standalone application, which follows e.g. tutorials/gui/guitest.C:
gui.tar.gz (1.2 KB)

Right now if one closes the window, the application terminates, but the destructor of GuiTest is not called. This can be fixed by passing true as the argument to theApp.Run(). The problem is that in my actual application that I am trying to debug, this causes a segfault due to double deletion of something, most probably due to unnecessary calls to delete in destructors of various objects in the application.

Starting from TGMainFrame derivatives, looks like that they are somehow automatically registered with TApplication and it’s destructor is responsible for deleting the main frames.

Which class in the hierarchy is actually responsible for this registration - is it TGMainFrame or one of it’s bases? If it is one of bases, does it mean that I should not try to delete on my own any objects that derive from this base?

There is also TGCompositeFrame::SetCleanup(). How does it correspond to the above deletion by TApplication? It’s documentation mentions something about possibility of double deletion…

In tutorials/gui/guitest.C TestMainFrame::~TestMainFrame() there are deletions of some GUI objects, but not others that are created in the constructor. Do I understand correctly, that some of the objects there are added to fMain with AddFrame so are deleted due to fMain->SetCleanup(kDeepCleanup), while others have gClient->GetRoot() as parent and therefore need explicit deletion in the destructor?

Cheers,
Antoni

I think @bellenot can help.

If you use TGCompositeFrame::SetCleanup(kDeepCleanup), the child frames will be recursively deleted. And you’re right, the GUI elements for which the parent is gClient->GetRoot() or gClient->GetDefaultRoot() (or even 0) need to be deleted, since they are not added to a TGCompositeFrame. See for example the TGPopupMenu in test/guitest.cxx

And it’s not the TApplication, but the TGClient which is keeping a list of created Windows and deletes them when quitting ROOT.

And IIRC, don’t call delete for TGMainFrame, the destructor should be called when the Window Manager closes it. See the documentation of TGMainFrame::CloseWindow()

Hi @bellenot,

thanks for a prompt reply!

OK, so TGClient keeps the list and makes the cleanup. But then wouldn’t it also cleanup the members of tutorials/gui/guitest.C TestMainFrame that are constructed with gClient->GetRoot() as parent?

And since things added to the composite with AddFrame() are deriving from TGWindow, wouldn’t they also get deleted by TGClient even if I didn’t call TGCompositeFrame::SetCleanup(kDeepCleanup)? Or is the registration in TGClient done strictly based on parent-child relationship, not that every TGWindow gets registered in its constructor (as it happens with e.g. histograms and gDirectory)?

Regarding your last comment, as you can see in my tiny example, I overload the TGMainFrame::CloseWindow() to call gApplication->Terminate(0). So it is then the TGClient destroying my main frame.

Cheers,
Antoni

Well, there is more than the Windows themselves, there is the TGFrameElement with its TGLayoutHints and TGFrame. And in absolute, you can leave the system deleting frames for you, but there is the risk to leak resources and grow memory if you close/open many times in the same application. And I agree it’s not simple…

OK, so from what you write I understand that indeed TGClient would cleanup everything anyway, irrespective of the TGCompositeFrame::SetCleanup or explicit deletes. Right?

The explicit deletes and TGCompositeFrame::SetCleanup are meant to assure that certain things die before the actual termination of the application. Right?

But then is it possible that one of the explicit deletes happens after TGClient erases something? I mean what is the sequence in which TGClient erases things?

It looks as if explicit deletes were fine in methods like CloseWindow, but not in destructors of classes that inherit from something that TGClient erases.

Yes (IIRC)

Well, it ensures to properly delete frames and sub-frames when explicitly deleting (closing) a composite frame.

TGClient delete its list of Windows (that are not already deleted) at destruction time.

It might be the case in some occasion.

@bellenot thanks for all the explanations.

It seems I fixed most of problems in my code, but then I came across a segfault in TRootEmbeddedCanvas destructor that you can observe in the new example: gui.tar.gz (1.3 KB)

Is it a bug somewhere (e.g. wrong sequence of automatic destruction) or am I doing something wrong?

Try with:

int main(int argc, char **argv)
{
  TApplication *theApp = new TApplication("theApp", &argc, argv);
  new GuiTest(gClient->GetRoot(), 640, 400);
  theApp->Run(true);
  return 0;
}

BTW, the correct code (for the layout) would be something like this:

GuiTest::GuiTest(const TGWindow* p, int w, int h) :
  TGMainFrame(p, w, h)
{
  SetCleanup(kDeepCleanup);

  TRootEmbeddedCanvas *can = new TRootEmbeddedCanvas("3DPlot", this);
  AddFrame(can, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 5, 5, 5, 5));
  MapSubwindows();
  Layout();
  SetWindowName("GuiTest");
  MapWindow();
  cout << "GuiTest constructor" << endl;
}

What you propose indeed removes the segfault, but it also removes the destruction of GuiTest that I need to take place (in my actual app this e.g. closes some server connections). Actually I think I understood the problem: the destructor of TApplication causes GuiTest to be destroyed, but earlier it causes gROOT to make some cleanup which includes all the TCanvas. So when GuiTest gets finally destroyed, the TRootEmbeddedCanvas try to delete TCanvas that were already deleted by gROOT.

I managed to work around this by storing the pointer to the GuiTest and adding delete for it before the end of main. In this way GuiTest gets destroyed before gROOT cleans up, but the latter actually knows the canvas were removed.

So the question is if the problem I encountered should be fixed in some way on the side of ROOT or in the user code by destroying the parent object in time?

If it should be the fix in the user code, can you please update docs/examples for GUI so that all the things get properly destroyed? I have an impression that while ROOT docs describe very well how to construct and use various things, they lack somehow in clear explanations regarding destruction. Given that ROOT has a tendency of owning and deleting various objects constructed by the user, it can get very confusing.

Regarding the layout, thanks! I didn’t know I have to call Layout().

I’ll see what can be done, but note that calling gApplication->Run(true) is not the most common use case…

But shouldn’t it be made common? I guess most users are actually not aware of the fact that they are leaking resources.

I don’t think so. Calling TApplication::Run(true) means that you still want to do stuff after TApplication::Terminate(), which is usually not the case.

Yes, you are right… The only thing I really want here is to delete the main frame and terminate. But is this:

void
GuiTest::CloseWindow()
{
  cout << "GuiTest CloseWindow" << endl;
  delete this;
  gApplication->Terminate(0);
}

legal?

But still, isn’t it just wrong that TApplication::Terminate() omits destruction of GUI objects?

Shouldn’t it just behave like a destructor of TApplication? Then still there would be a conflict with gROOT cleanup and GUI cleanup, but maybe this is actually solvable?

Hard to tell, it depends on the OS/Window Manager.

Believe me or not, the atexit cleanup mechanism is very complex, but I can try to take a look and see if I can fix those issues without breaking backward (and forward) compatibility.
Now I have a question: Why do you need to return from TApplication::Run()?

As I wrote above, I only need to terminate (finish by any mechanism) the application calling the destructor (deleting) the main frame object. So I do not really need to return from TApplication::Run() if only I get the proper deletion of all objects involved. I.e. apart from proper destruction there is nothing that my code should do after returning from TApplication::Run() .

In the current situation what seems to work according to my expectations is 1) return from Run(), 2) delete the main frame object explicitly before the TApplication object goes out of scope and destructs. That said, maybe this is simply the way the whole system was designed to operate? So instead of changing any code on the ROOT side, maybe it is enough to fix the tutorials so they present the correct sequence and scope of creation and destruction?

OK, so I will have to do deep investigations to understand what’s happening in reality. I’ll keep you in touch. But again, the most common use case is not to return from TApplication::Run(). So I’m definitively not going to changes all the tutorials

A typical “main” application first creates some objects, then runs the “ROOT event loop” using TApplication::Run(kTRUE), and finally (when it returns to the “main”) actions are taken to deal with the objects created in the beginning.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.