4. Case 2 - Hierarchy of DBo Derived Classes

Now we want to export the following C++ class hierarchy into Python:

PyEntity <-- PyComponent <-+- PyContact
                           +- PySegment <-+- PyHorizontal
                                          +- PyVertical

4.1 Base Class Header

Remark: this is only a partial description of the tree for the sake of clarity.

One important fact to remember is that PyEntity and PyComponent being related to C++ abstract classes, no objects of those types will be created, only PyContact, PyHorizontal or PyVertical will.

The consequence is that there is no PyEntity_Link() like in 3.1 Class Associated Header File but instead two functions:

  1. PyEntity_NEW() which create the relevant PyEntity derived object from the Entity one. For example, if the Entity* given as argument is in fact a Horizontal*, then the function will return a PyHorizontal*.
  2. EntityCast() do the reverse of PyEntity_NEW() that is, from a PyEntity, return the C++ derived object. Again, if the PyEntity* is a PyHorizontal*, the function will cast it as a Horizontal* then return it as an Entity*.
#ifndef ISOBAR_PY_ENTITY_H
#define ISOBAR_PY_ENTITY_H

#include "hurricane/isobar/PyHurricane.h"
#include "hurricane/Entity.h"

namespace  Isobar {
  extern "C" {

    typedef struct {
        PyObject_HEAD
        Hurricane::Entity* _object;
    } PyEntity;

    extern  PyObject*     PyEntity_NEW        ( Hurricane::Entity* entity );
    extern  void          PyEntity_LinkPyType ();
    extern  PyTypeObject  PyTypeEntity;
    extern  PyMethodDef   PyEntity_Methods[];


#define IsPyEntity(v)    ( (v)->ob_type == &PyTypeEntity )
#define PYENTITY(v)      ( (PyEntity*)(v) )
#define PYENTITY_O(v)    ( PYENTITY(v)->_object )

  }  // extern "C".

  Hurricane::Entity*  EntityCast ( PyObject* derivedObject );

}  // Isobar namespace.

#endif  // ISOBAR_PY_ENTITY_H

4.2 Base Class File

Changes from 3.2 Class Associated File are:

  1. No call to DBoLinkCreateMethod() because there must be no PyEntity_Link(), but the definitions of PyEntity_NEW() and EntityCast.
  2. For defining the PyTypeEntity Python type, we call a different macro: PyTypeRootObjectDefinitions, dedicated to base classes.
#include "hurricane/isobar/PyCell.h"
#include "hurricane/isobar/PyHorizontal.h"
#include "hurricane/isobar/PyVertical.h"
#include "hurricane/isobar/PyContact.h"

namespace Isobar {
  using namespace Hurricane;

  extern "C" {

#if defined(__PYTHON_MODULE__)

#define  METHOD_HEAD(function)   GENERIC_METHOD_HEAD(Entity,entity,function)

    DBoDestroyAttribute(PyEntity_destroy ,PyEntity)

    static PyObject* PyEntity_getCell ( PyEntity *self )
    {
      Cell* cell = NULL;
      HTRY
        METHOD_HEAD( "Entity.getCell()" )
        cell = entity->getCell();
      HCATCH
      return PyCell_Link( cell );
    }

    PyMethodDef PyEntity_Methods[] =
      { { "getCell", (PyCFunction)PyEntity_getCell, METH_NOARGS
                   , "Returns the entity cell." }
      , { "destroy", (PyCFunction)PyEntity_destroy, METH_NOARGS
                   , "Destroy associated hurricane object, the python object remains." }
      , {NULL, NULL, 0, NULL}  /* sentinel */
      };


    DBoDeleteMethod(Entity)
    PyTypeObjectLinkPyType(Entity)

#else  // End of Python Module Code Part.

    PyObject* PyEntity_NEW ( Entity* entity )
    {
      if (not entity) {
        PyErr_SetString ( HurricaneError, "Invalid Entity (bad occurrence)" );
        return NULL;
      }

      Horizontal* horizontal = dynamic_cast<Horizontal*>(entity);
      if (horizontal) return PyHorizontal_Link( horizontal );

      Vertical* vertical = dynamic_cast<Vertical*>(entity);
      if (vertical) return PyVertical_Link( vertical );

      Contact* contact = dynamic_cast<Contact*>(entity);
      if (contact) return PyContact_Link( contact );

      Py_RETURN_NONE;
    }

    PyTypeRootObjectDefinitions(Entity)

#endif  // Shared Library Code Part (1).

}  // extern "C".


#if !defined(__PYTHON_MODULE__)

Hurricane::Entity* EntityCast ( PyObject* derivedObject ) {
  if (IsPyHorizontal(derivedObject)) return PYHORIZONTAL_O(derivedObject);
  if (IsPyVertical  (derivedObject)) return PYVERTICAL_O(derivedObject);
  if (IsPyContact   (derivedObject)) return PYCONTACT_O(derivedObject);
  return NULL;
}

#endif  // Shared Library Code Part (2).

}  // Isobar namespace.

4.3 Intermediate Class Header

Changes from 3.1 Class Associated Header File are:

  1. As for PyEntity, and because this is still an abstract class, there is no PyComponent_Link() function.
  2. The definition of the PyComponent struct is differs. There is no PyObject_HEAD (it is a Python derived class). The only field is of the base class type PyEntity and for use with Coriolis macros, it must be named _baseObject (note that this is not a pointer but a whole object).
#ifndef ISOBAR_PY_COMPONENT_H
#define ISOBAR_PY_COMPONENT_H

#include "hurricane/isobar/PyEntity.h"
#include "hurricane/Component.h"

namespace  Isobar {
  extern "C" {

    typedef struct {
        PyEntity  _baseObject;
    } PyComponent;

    extern  PyTypeObject  PyTypeComponent;
    extern  PyMethodDef   PyComponent_Methods[];
    extern  void          PyComponent_LinkPyType ();

#define IsPyComponent(v) ((v)->ob_type == &PyTypeComponent)
#define PYCOMPONENT(v)   ((PyComponent*)(v))
#define PYCOMPONENT_O(v) (static_cast<Component*>(PYCOMPONENT(v)->_baseObject._object))

  }  // extern "C".
}  // Isobar namespace.

#endif

4.4 Intermediate Class File

Changes from 3.2 Class Associated File are:

  1. Redefinition of the default macros ACCESS_OBJECT and ACCESS_CLASS.
    • The pointer to the C++ encapsulated object (attribute _object) is hold by the base class PyEntity. The ACCESS_OBJECT macro which is tasked to give access to that attribute is then _baseObject._object as PyComponent is a direct derived class of PyEntity.
    • ACCESS_CLASS is similar to ACCESS_OBJECT for accessing the base class, that is a pointer to PyEntity.

  1. For defining the PyTypeComponent Python type, we call a yet different macro: PyTypeInheritedObjectDefinitions(), dedicated to derived classes. For this this macro we need to give as argument the derived class and the base class.
#include "hurricane/isobar/PyComponent.h"
#include "hurricane/isobar/PyNet.h"

namespace  Isobar {
  using namespace Hurricane;

  extern "C" {

#undef   ACCESS_OBJECT
#undef   ACCESS_CLASS
#define  ACCESS_OBJECT           _baseObject._object
#define  ACCESS_CLASS(_pyObject)  &(_pyObject->_baseObject)
#define  METHOD_HEAD(function)   GENERIC_METHOD_HEAD(Component,component,function)

#if defined(__PYTHON_MODULE__)

    DirectGetLongAttribute(PyComponent_getX,getX,PyComponent,Component)
    DirectGetLongAttribute(PyComponent_getY,getY,PyComponent,Component)
    DBoDestroyAttribute(PyComponent_destroy,PyComponent)

    static PyObject* PyComponent_getNet ( PyComponent *self )
    {
      Net* net = NULL;
      HTRY
        METHOD_HEAD( "Component.getNet()" )
        net = component->getNet( );
      HCATCH
      return PyNet_Link( net );
    }

    PyMethodDef PyComponent_Methods[] =
      { { "getX"   , (PyCFunction)PyComponent_getX   , METH_NOARGS
                   , "Return the Component X value." }
      , { "getY"   , (PyCFunction)PyComponent_getY   , METH_NOARGS
                   , "Return the Component Y value." }
      , { "getNet" , (PyCFunction)PyComponent_getNet , METH_NOARGS
                   , "Returns the net owning the component." }
      , { "destroy", (PyCFunction)PyComponent_destroy, METH_NOARGS
                   , "destroy associated hurricane object, the python object remains." }
      , {NULL, NULL, 0, NULL}    /* sentinel */
      };

    DBoDeleteMethod(Component)
    PyTypeObjectLinkPyType(Component)

#else  // Python Module Code Part.

    PyTypeInheritedObjectDefinitions(Component, Entity)

#endif  // Shared Library Code Part.

  }  // extern "C".
}  // Isobar namespace.

4.5 Terminal Class Header

The contents of this file is almost identical to 4.3 Intermediate Class Header, save for the presence of a PyContact_Link() function. She is present at this level because the class is a concrete one and can be instanciated.

#ifndef ISOBAR_PY_CONTACT_H
#define ISOBAR_PY_CONTACT_H

#include "hurricane/isobar/PyComponent.h"
#include "hurricane/Contact.h"

namespace  Isobar {
  extern "C" {

    typedef struct {
      PyComponent _baseObject;
    } PyContact;

    extern PyTypeObject  PyTypeContact;
    extern PyMethodDef   PyContact_Methods[];
    extern PyObject*     PyContact_Link       ( Hurricane::Contact* object );
    extern void          PyContact_LinkPyType ();

#define IsPyContact(v)    ( (v)->ob_type == &PyTypeContact )
#define PYCONTACT(v)      ( (PyContact*)(v) )
#define PYCONTACT_O(v)    ( PYCONTACT(v)->_baseObject._baseObject._object )

  }  // extern "C".
}  // Isobar namespace.

#endif  // ISOBAR_PY_CONTACT_H

4.6 Terminal Class File

Changes from 4.4 Intermediate Class File are:

  1. As previously, we have to redefine the macros ACCESS_OBJECT and ACCESS_CLASS. But, as we are one level deeper into the hierarchy, one more level of indirection using _baseObject must be used.
    • ACCESS_OBJECT becomes _baseObject._baseObject._object.
    • ACCESS_CLASS becomes &(_pyObject->_baseObject._baseObject).
  2. For defining the PyTypeContact Python type, we call again PyTypeInheritedObjectDefinitions(). It is the same whether the class is terminal or not.
  3. And, this time, as the Python class is concrete, we call the macro DBoLinkCreateMethod() to create the PyContact_Link() function.
#include "hurricane/isobar/PyContact.h"

namespace  Isobar {
  using namespace Hurricane;

  extern "C" {

#undef  ACCESS_OBJECT
#undef  ACCESS_CLASS
#define ACCESS_OBJECT           _baseObject._baseObject._object
#define ACCESS_CLASS(_pyObject) &(_pyObject->_baseObject._baseObject)
#define METHOD_HEAD(function)   GENERIC_METHOD_HEAD(Contact,contact,function)

#if defined(__PYTHON_MODULE__)

    DirectGetLongAttribute(PyContact_getWidth , getWidth , PyContact,Contact)
    DirectGetLongAttribute(PyContact_getHeight, getHeight, PyContact,Contact)
    DBoDestroyAttribute(PyContact_destroy, PyContact)

    static PyObject* PyContact_create ( PyObject*, PyObject *args )
    {
      Contact* contact = NULL;
      HTRY
        // Usual signature then arguments parsing.
      HCATCH
      return PyContact_Link(contact);
    }

    PyMethodDef PyContact_Methods[] =
      { { "create"   , (PyCFunction)PyContact_create   , METH_VARARGS|METH_STATIC
                     , "Create a new Contact." }
      , { "destroy"  , (PyCFunction)PyContact_destroy  , METH_NOARGS
                     , "Destroy associated hurricane object, the python object remains." }
      , { "getWidth" , (PyCFunction)PyContact_getWidth , METH_NOARGS
                     , "Return the contact width." }
      , { "getHeight", (PyCFunction)PyContact_getHeight, METH_NOARGS
                     , "Return the contact height." }
      , {NULL, NULL, 0, NULL}  /* sentinel */
      };

    DBoDeleteMethod(Contact)
    PyTypeObjectLinkPyType(Contact)

#else  // Python Module Code Part.

    DBoLinkCreateMethod(Contact)
    PyTypeInheritedObjectDefinitions(Contact, Component)

#endif  // Shared Library Code Part.

  }  // extern "C".
}  // Isobar namespace.

4.8 Python Module

DL_EXPORT(void) initHurricane ()
{
  PyEntity_LinkPyType();  // step 1.
  PyComponent_LinkPyType();
  PyContact_LinkPyType();

  PYTYPE_READY( Entity )  // step 2.
  PYTYPE_READY_SUB( Component, Entity )
  PYTYPE_READY_SUB( Contact  , Component )

  __cs.addType( "ent"    , &PyTypeEntity   , "<Entity>"   , false ); // step 3.
  __cs.addType( "comp"   , &PyTypeComponent, "<Component>", false, "ent"  );
  __cs.addType( "contact", &PyTypeContact  , "<Contact>"  , false, "comp" );

  PyObject* module = Py_InitModule( "Hurricane", PyHurricane_Methods );
  if (module == NULL) {
    cerr << "[ERROR]\n"
         << "  Failed to initialize Hurricane module." << endl;
    return;
  }

  Py_INCREF( &PyTypeContact );                                        // step 4.
  PyModule_AddObject( module, "Contact", (PyObject*)&PyTypeContact ); // step 4.
}