hbQT - GC - Qt Object Destruction

  Previous topic Next topic  

In this section I will try to explain the foundations of hbQT, its variables life-cycle, Harbour GC interaction and Qt's own destruction mechanism.

 

As you all know, we are struggling to balance the Harbour GC behavior and Qt's object destruction, which, at times involves the same pointer. We have not been able to locate how GC should know when a pointer has been released by Qt. This has been all the more difficult because of the fact that Qt does not respect its own documentation where it states that if "new" and "delete" operators are overloaded by the application, the calls to obtain and free the pointers will be routed to application defined operators instead of Qt's own.

 

Strange fact is this that, "new" operator is always routed to Harbour's but "delete" operators behavior is inconsistent. Few objects are passes back to application's "delete" whereas most are not. I will try to explain hereunder how the things are moving inside these two nodes.

 

Let's start with a bare minimum example:

 

REQUEST HB_QT

 

STATIC s_events

STATIC s_slots

 

PROCEDURE Main()

  Local qApp, qWnd, qLabel, qBtn

 

  qApp := QApplication():new()

 

  s_events := QT_EVENTS_NEW()

  s_slots := QT_SLOTS_NEW()

 

  qWnd := QMainWindow():new()

  qWnd:setWindowTitle( "Harbour-Qt GC Diagram" )

  qWnd:resize( 900, 500 )

 

  qLabel := QLabel():new( qWnd )

  qLabel:setText( "Harbour" )

  qLabel:move( 100, 100 )

  qLabel:resize( 100, 50 )

  qLabel:show()

 

  qBtn := QPushButton():new( qWnd )

  qBtn:setText( "Message Box" )

  qBtn:move( 10, 10 )

  qBtn:resize( 100, 30 )

  Qt_Slots_Connect( s_slots, qBtn, "clicked()", ;

                          {|| MyMessageBox( qWnd ) } )

 

  qWnd:Show()

 

  qApp:exec()

 

  Qt_Slots_DisConnect( s_slots, qBtn, "clicked()" )

 

  RETURN

 

 

FUNCTION MyMessageBox( qWnd )

  LOCAL qMB

 

  qMB := QMessageBox():new( qWnd )

  qMB:setInformativeText( "Harbour" )

  qMB:setWindowTitle( "Harbour-QT" )

  qMB:exec()

 

  RETURN NIL

 

In this example I will concentrate on function MyMessageBox() which when invoked by clicking on push button should produce a GPF on many systems with astonishingly different behavior with different C compilers and Qt's releases. If some event happens in a predictable way, it is subject to resolution sooner or later, but what if same is experienced by somebody and just passes away some others.

 

A thousand dollar question is - why GPF will appear for this very innocent code?

Simply because local variable oMB will go out of scope and GC will try to release its resources.

It seems funny but the fact is it happens like this only.

 

To trace down the flow of pointer's life cycle, this code must be compiled with -nohbcppmm hbMK2 flag and including cppmmstub.cpp outlined below. Also place some trace calls in "delete" group of functions to check if any of them is reached anytime.

 

Function new( size_t nSize ) is always reached with every type of Qt object, but delete( void * ptr ) is reached by some of the Qt objects but even those inconsistently depending upon the program flow.

 

If an application is linked with -nohbcppmm then probbaly no GPF will be produced at all. In such case if a lot of objects will be created assigned to local variables, those will never be get released and memory usage will keep up growing.

 

 

/* cppstub.cpp */

 

#include "hbapi.h"

 

const char * __hbmk2_hbcppmm( void )

{

  return "HBCPPMM";

}

 

void operator delete[]( void * ptr )

{

  if( ptr )

  {

     hb_xfree( ptr );

  }

}

 

void operator delete[]( void * ptr, size_t )

{

  if( ptr )

  {

     hb_xfree( ptr );

  }

}

 

void operator delete( void * ptr )

{

  if( ptr )

  {

     hb_xfree( ptr );

  }

}

 

void operator delete( void * ptr, size_t nSize )

{

  if( ptr )

  {

     hb_xfree( ptr );

  }

}

 

void * operator new( size_t nSize )

{

  if( nSize == 0 )

  {

     nSize = 1;

  }

  return hb_xgrab( nSize );

}

 

 

qMB := QMessageBox():new( qWnd )

 

void * hbqt_gcAllocate_QMessageBox( void * pObj, bool bNew )

{

  QGC_POINTER_QMessageBox * p = ( QGC_POINTER_QMessageBox * ) hb_gcAllocate( sizeof( QGC_POINTER_QMessageBox ), hbqt_gcFuncs() );

 

  p->ph = pObj;

  p->bNew = bNew;

  p->func = hbqt_gcRelease_QMessageBox;

 

  if( bNew )

  {

     new( & p->pq ) QPointer< QMessageBox >( ( QMessageBox * ) pObj );

  }

  return p;

}

 

HB_FUNC( QT_QMESSAGEBOX )

{

  void * pObj = NULL;

 

  pObj = ( QMessageBox* ) new QMessageBox() ;     /* This call is routed to our overloaded function - operator new( size_t nSize )  */

 

  hb_retptrGC( hbqt_gcAllocate_QMessageBox( pObj, true ) );

}

 

pPtr instance variable of qMB  is now populated with GC collectible pointer.

When the function will terminate, either pressing "X" on messagebox or clicking "Ok" button, GC will try to free oMB:pPtr and following function will be called:

 

QT_G_FUNC( hbqt_gcRelease_QMessageBox )

{

  QGC_POINTER_QMessageBox * p = ( QGC_POINTER_QMessageBox * ) Cargo;

 

  if( p && p->bNew )

  {

     if( p->ph && p->pq )

     {

        const QMetaObject * m = ( ( QObject * ) p->ph )->metaObject();

        if( ( QString ) m->className() != ( QString ) "QObject" )

        {

          HB_TRACE( HB_TR_DEBUG, ( "YES_rel_QMessageBox   /.\\   ph=%p pq=%p", p->ph, (void *)(p->pq) ) );

 

           delete ( ( QMessageBox * ) p->ph );   /* We will always reach here and then will appear GPF */

                                                                  /* because p->ph gets released by QT which is validated by p->pq becoming NULL */

                                                                  /* but p->ph retains the value as is and hence GC tries to free it again which infact is already released */

                                                                  /* Please note that all our checks to prevent it are intact to have a valid pointer but still... */

 

           /* Infact, at this point Qt's own delete() operator is called ( and must be called ) as delete ( ( QMessageBox * ) ptr ) it would been defined in Qt headers */

 

          HB_TRACE( HB_TR_DEBUG, ( "YES_rel_QMessageBox   \\./   ph=%p pq=%p", p->ph, (void *)(p->pq) ) );     // This is never reached

           p->ph = NULL;

        }

     }

  }

}

 

This is simplest of the examples to trap GPF and its course and it does not use any of the other so-called "hacks" in hbQT implementation.

Probably, Przemek can look into this deeply and can traceout the issue. If we do it here we will be very near the real issue. This will produce GPF on OS/2 for sure, or on certain other *nix systems. I am unable to get one on Windows XP and mingw 4.4.

 



Page url: http://hbide.vouch.info/?hbqt_-_gc_-_qt_object_destruct.htm