Overriding Virtual Methods

From WxPerl Wiki
Jump to: navigation, search

To allow the wxPerl user to override the virtual methods of a C++ class, we must implement a C++ class that overides the base class methods and makes calls into the user Perl code where a user implementation exists.

For a fairly transparent case we will look at Wx::Frame.

The standard XS code can be found in the source file XS/Frame.xs wher you will find the constructor taking a full parameter list:

wxFrame*
newFull( CLASS, parent, id, title, pos = wxDefaultPosition, size = wxDefaultSize, style = wxDEFAULT_FRAME_STYLE, name = wxFrameNameStr )
    char* CLASS
    wxWindow* parent
    wxWindowID id
    wxString title
    wxPoint pos
    wxSize size
    long style
    wxString name
  CODE:
    RETVAL = new wxPliFrame( CLASS , parent, id, title, pos,
         size, style, name );
 OUTPUT: RETVAL

The important thing to consider here is that the constructor does not return an instance of wxFrame, but an instance of our own implementation wxPliFrame.

The actual C++ code for wxPliFrame can be found in source file cpp/frame.h

class wxPliFrame:public wxFrame
{
    WXPLI_DECLARE_DYNAMIC_CLASS( wxPliFrame );
    WXPLI_DECLARE_V_CBACK();
public:
    WXPLI_DEFAULT_CONSTRUCTOR( wxPliFrame, "Wx::Frame", true );
    WXPLI_CONSTRUCTOR_7( wxPliFrame, "Wx::Frame", true,
                         wxWindow*, wxWindowID, const wxString&,
                         const wxPoint&, const wxSize&, long, 
                         const wxString& );

    virtual wxStatusBar* OnCreateStatusBar( int, long, wxWindowID,
                                            const wxString& );
    virtual wxToolBar* OnCreateToolBar( long, wxWindowID, const wxString& );
};

inline wxStatusBar* wxPliFrame::OnCreateStatusBar( int number, long style,
                                                   wxWindowID id,
                                                   const wxString& name ) 
{
    dTHX;
    if( wxPliVirtualCallback_FindCallback( aTHX_ &m_callback,
                                           "OnCreateStatusBar" ) ) 
    {
        SV* ret = wxPliVirtualCallback_CallCallback
            ( aTHX_ &m_callback, G_SCALAR, "illP",
              number, style, id, &name );
        wxStatusBar* retval =
            (wxStatusBar*)wxPli_sv_2_object( aTHX_ ret, "Wx::StatusBar" );
        SvREFCNT_dec( ret );

        return retval;
    } else
        return wxFrame::OnCreateStatusBar( number, style, id, name );
}

inline wxToolBar* wxPliFrame::OnCreateToolBar( long style, wxWindowID id,
                                               const wxString& name )
{
    dTHX;
    if( wxPliVirtualCallback_FindCallback( aTHX_ &m_callback,
                                           "OnCreateToolBar" ) ) 
    {
        SV* ret = wxPliVirtualCallback_CallCallback
            ( aTHX_ &m_callback, G_SCALAR, "llP", style, id, &name );
        wxToolBar* retval =
            (wxToolBar*)wxPli_sv_2_object( aTHX_ ret, "Wx::ToolBar" );
        SvREFCNT_dec( ret );

        return retval;
    } else
        return wxFrame::OnCreateToolBar( style, id, name );
}
    
WXPLI_IMPLEMENT_DYNAMIC_CLASS( wxPliFrame, wxFrame );

The main purpose of the implementation is to allow Perl code to override OnCreateStatusBar and OnCreateToolBar. It will help explanation if we expand the macros for the class definition and implementaion. (macro definitions can be found in cpp/helpers.h )

class wxPliFrame:public wxFrame
{
public:
    static wxPliClassInfo ms_classInfo;
    virtual wxClassInfo *GetClassInfo() const
         { return &ms_classInfo; }
 
    wxPliVirtualCallback m_callback;
    
    wxPliFrame( const char* package ) :m_callback( "Wx::Frame" )
      {                                                          
         m_callback.SetSelf( wxPli_make_object( this, package ), true );
      }
    
    wxPliFrame( const char* package, wxWindow* parent, wxWindowID id, 
                const wxString& title, const wxPoint& pos, 
                const wxSize& size, long style, 
                const wxString& name )  :m_callback( "Wx::Frame" )
      {                                                          
         m_callback.SetSelf( wxPli_make_object( this, package ), true );
         Create( parent, id, title, pos, size, style, name ); 
      }   
   
    virtual wxStatusBar* OnCreateStatusBar( int, long, wxWindowID,
                                            const wxString& );
    virtual wxToolBar* OnCreateToolBar( long, wxWindowID, const wxString& );
};
 
.... // OnCreateStatusBar / OnCreateToolBar implementations missed out
    
wxPliSelfRef* wxPliGetSelfForwxPliFrame(wxObject* object) 
  { return &((wxPliFrame *)object)->m_callback; }
 
 
wxPliClassInfo wxPliFrame::ms_classInfo((wxChar *) wxT("wxPliFrame"),
        &wxFrame::ms_classInfo, NULL, (int) sizeof(wxPliFrame), NULL,
       (wxPliGetCallbackObjectFn) wxPliGetSelfForwxPliFrame);


The class has a member m_callback defined which will be deleted when the instance of our class is deleted. You can check the source files cpp/helpers.h, cpp.helpers.cpp, cpp/v_cback.h and cpp/v_cback.cpp for full implementation details but the type wxPliVirtualCallback inherits from class wxPliSelfRef which has a member m_self. This member is accessed through m_callback.SetSelf and m_callback.GetSelf.

SV* wxPli_make_object( void* object, const char* classname )

This Wx API function will return an SV containing a reference to a hash based object blessed into the class given in classname. It will also use the Perl magic of the SV to store a pointer to the wxWidgets.

The end result is that the C++ class of type wxPliFrame holds a reference to the associated Perl object. When the C++ class is destroyed, the reference count on the Perl SV is decreased. This reference to the Perl object is used within the C++ class to check for overrides of virtual methods in Perl code.

The API function wxPli_object_2_sv also takes advantage of this. When converting a wxWidgets object to a Perl object, wxPli_object_2_sv will first check if the wxWidgets object has a perl self reference. If yes it will simply return the reference after increasing its ref count. In this way, if a call to wxWindow::GetParent is returning a window created in your Perl code, you will get back the same hash based object you created.

The implementation for the calls to OnCreateToolBar and OnCreateStatusBar is explained at Virtual Callback Argument Types

The above implementation is for our custom C++ class wxPliFrame. We still need a Perl XS implementation so that within our Perl code we could call $frame->OnCreateStatusBar if we wished.

The following is in the source file XS/Frame.xs

wxStatusBar*
wxFrame::OnCreateStatusBar( number, style, id, name )
   int number
   long style
   wxWindowID id
   wxString name
 CODE:
   RETVAL = THIS->wxFrame::OnCreateStatusBar( number, style, id, name );
 OUTPUT: RETVAL

Note the CODE line

 RETVAL = THIS->wxFrame::OnCreateStatusBar( number, style, id, name );

If we have our own Frame class in the standard manner:

package MyFrame;
use Wx;
use base qw( Wx::Frame );

.....

The we will actually get back an instance of class wxPliFrame. In the wxPerl code we have instructed that wxPliFrame isa wxFrame ( see source file Constants.xs ). If in an instance of MyClass we call

$self->SUPER::OnCreateStatusBar( .... );

our Perl binding is defined in XS/Frame.xs as wxFrame::OnCreateStatusBar( ... ). If we had the default

 RETVAL = THIS->OnCreateStatusBar( number, style, id, name );

'THIS' is a wxPliFrame so due to C++ virtual method calling we would enter our custom C++ class override method. This is not what we want in most cases. In most cases we want to make $self->SUPER::OnCreateStatusBar( .... ) work as on many occasions we may wish to have the base class do its work and make adjustments in our own code, rather than having to write a complete implementation.

The code

RETVAL = THIS->wxFrame::OnCreateStatusBar( number, style, id, name );

solves this issue by specifying the interface ( wxFrame ) explicitly. However, there is still one problem with this implementation.

There may be instances where you actually want to call the implementation in our own C++ class. With the above scheme you cannot do so.

An alternative implementation that overcomes this is available via the use of ExtUtils::XSpp plugins as described in Using ExtUtils::XSpp and Plugins