3. Case 1 - DBo Derived, Standalone¶
As example, we take Library
. This a DBo
derived class, but we
choose not to export the parent classes. From Python, it will appear
as a base class.
3.1 Class Associated Header File¶
Here is the typical content of a header file (for PyLibrary
):
#ifndef PY_LIBRARY_H
#define PY_LIBRARY_H
#include "hurricane/isobar/PyHurricane.h"
#include "hurricane/Library.h"
namespace Isobar {
using namespace Hurricane;
extern "C" {
typedef struct {
PyObject_HEAD
Library* _object;
} PyLibrary;
extern PyTypeObject PyTypeLibrary;
extern PyMethodDef PyLibrary_Methods[];
extern PyObject* PyLibrary_Link ( Hurricane::Library* lib );
extern void PyLibrary_LinkPyType ();
#define IsPyLibrary(v) ( (v)->ob_type == &PyTypeLibrary )
#define PYLIBRARY(v) ( (PyLibrary*)(v) )
#define PYLIBRARY_O(v) ( PYLIBRARY(v)->_object )
} // extern "C".
} // Isobar namespace.
#endif // PY_LIBRARY_H
The code is organized as follow:
It must have, as the first include
PyHurricane.h
, which provides the complete bunch of macros needed to build the module. Then the include of the C++ class we want to wrap (Library.h
).As Python is written in C, all the wrapper code has to be but inside an
extern "C"
namespace.Definition of the wrapped struct,
PyLibrary
. It is standard Python here.Note
For our set of macros to work, the name of the pointer to the C++ class must always be _object, and the various functions and macros defined here must take the name of the class (either in lowercase, camel case or capitals).
Declaration of the Python type
PyTypeLibrary
(standard).Declaration of the Python type table of methods
PyLibrary_Methods
(standard).
- Declaration of
PyLibrary_Link()
, helper to convert a C++Lybrary
into aPyLibrary
(put in the support shared library). - Declaration of
PyLibrary_LinkPyType()
, this function setup the class-level function of the new Python type (here,PyTypeLibrary
). - And, lastly, three macros to:
IsPylibrary()
, know if a Python object is aPyLibrary
PYLIBRARY()
, force cast (C style) of aPyObject
into aPyLibrary
.PYLIBRARY_O()
, extract the C++ object (Library*
) from the Python object (PyLibrary
).
3.2 Class Associated File¶
3.2.1 Head of the file¶
#include "hurricane/isobar/PyLibrary.h"
#include "hurricane/isobar/PyDataBase.h"
#include "hurricane/isobar/PyCell.h"
namespace Isobar {
using namespace Hurricane;
extern "C" {
#define METHOD_HEAD(function) GENERIC_METHOD_HEAD(Library,lib,function)
As for the header, all the code must be put inside a extern "C"
namespace.
A convenience macro METHOD_HEAD()
must be defined, by refining
GENERIC_METHOD_HEAD()
. This macro will be used in the method wrappers
below to cast the _object
field of the Python object into the
appropriate C++ class, this is done using a C-style cast.
The parameters of that macro are:
- The C++ encapsulated class (
Library
). - The name of the variable that will be used to store a pointer to the C++ working object.
- The name of the C++ method which is to be wrapped.
3.2.2 The Python Module Part¶
First, we have to build all the wrappers to the C++ methods of the class. For common predicates, accessors, and mutators macros are supplied.
Wrapping of the Library::getCell()
method:
static PyObject* PyLibrary_getCell ( PyLibrary* self, PyObject* args )
{
Cell* cell = NULL;
HTRY
METHOD_HEAD( "Library.getCell()" )
char* name = NULL;
if (PyArg_ParseTuple(args,"s:Library.getCell", &name)) {
cell = lib->getCell( Name(name) );
} else {
PyErr_SetString( ConstructorError
, "invalid number of parameters for Library::getCell." );
return NULL;
}
HCATCH
return PyCell_Link(cell);
}
Key points about this method wrapper:
- The
HTRY
/HCATCH
macros provides an insulation from the C++ exceptions. If one is emitted, it will be catched and transformed in a Python one. This way, the Python program will be cleanly interrupted and the usual stack trace displayed. - The returned value of this method is of type
Cell*
, we have to transform it into a Python one. This is done withPyCell_Link()
. This macro is supplied by thePyCell.h
header and this is why it must be included.
Wrapping of the Library::create()
method:
static PyObject* PyLibrary_create( PyObject*, PyObject* args )
{
PyObject* arg0;
PyObject* arg1;
Library* library = NULL;
HTRY
__cs.init( "Library.create" ); // Step (1).
if (not PyArg_ParseTuple( args, "O&O&:Library.create"
, Converter, &arg0
, Converter, &arg1 )) { // Step (2).
PyErr_SetString( ConstructorError
, "invalid number of parameters for Library constructor." );
return NULL;
}
if (__cs.getObjectIds() == ":db:string") { // Step (3.a)
DataBase* db = PYDATABASE_O(arg0);
library = Library::create( db, Name(PyString_AsString(arg1)) );
} else if (__cs.getObjectIds() == ":library:string") { // Step (3.b)
Library* masterLibrary = PYLIBRARY_O(arg0);
library = Library::create( masterLibrary, Name(PyString_AsString(arg1)) );
} else {
PyErr_SetString( ConstructorError
, "invalid number of parameters for Library constructor." );
return NULL;
}
HCATCH
return PyLibrary_Link( library );
}
Key point about this constructor:
- We want the Python interface to mimic as closely as possible the
C++ API. As such, Python object will be created using a static
.create()
method. So we do not use the usual Python allocation mechanism. - As it is a static method, there is no first argument.
- Python do not allow function overload like C++. To emulate that
behavior we use the
__cs
object (which is a global variable).- Init/reset the
__cs
object: see step (1). - Call
PyArg_ParseTuple()
, read every mandatory or optional argument as a Python object ("O&"
) and useConverter
on each one.Converter
will determine the real type of the Python object given as argument by looking at the encapsulated C++ class. It then update the__cs
object. Done in step (2) - After the call to
PyArg_ParseTuple()
, the function__cs.getObjectIds()
will return the signature of the various arguments. In our case, the valid signatures will be":db:string"
(step (3.a)*a) and ``”:library:string”`` (*step (3.b)). - Call the C++ method after extracting the C++ objects from
the Python arguments. Note the use of the
PYLIBRARY_O()
andPYDATABSE_O()
macros to perform the conversion.
- Init/reset the
- Return the result, encapsulated through a call to
PyLibrary_Link()
.
Wrapping of the Library::destroy()
method:
DBoDestroyAttribute(PyLibrary_destroy, PyLibrary)
For C++ classes that are derived from DBo
, the destroy method
wrapper must be defined using the macro DBoDestroyAttribute()
.
This macro implements the bi-directional communication mechanism
using Hurricane::Property
. It must not be used for
non DBo
derived classes.
Defining the method table of the PyLibrary type:
PyMethodDef PyLibrary_Methods[] =
{ { "create" , (PyCFunction)PyLibrary_create , METH_VARARGS|METH_STATIC
, "Creates a new library." }
, { "getCell" , (PyCFunction)PyLibrary_getCell, METH_VARARGS
, "Get the cell of name <name>" }
, { "destroy" , (PyCFunction)PyLibrary_destroy, METH_NOARGS
, "Destroy associated hurricane object The python object remains." }
, {NULL, NULL, 0, NULL} /* sentinel */
};
This is standard Python/C API. The name of the PyMethodDef
table must be
named from the class: PyLibrary_Methods
.
3.2.3 Python Type Linking¶
Defining the PyTypeLibrary
class methods and the type linking function.
Those are the functions for the Python object itself to work, not the wrapped method from the C++ class.
Note
At this point we do not define the PyTypeLibrary
itself.
Only it’s functions and a function to set them up once the
type will be defined.
DBoDeleteMethod(Library)
PyTypeObjectLinkPyType(Library)
The macro DBoDeleteMethod()
define the function to delete a
PyLibrary
Python object. Again, do not mistake it for the deletion
of the C++ class (implemented by DBoDestroyAttribute()
).
Here again, DBoDeleteMethod()
is specially tailored for
DBo
derived classes.
To define PyLibrary_LinkPyType()
, use the PyTypeObjectLinkPyType()
macro. This macro is specific for DBo
derived classes that are seen as
base classes under Python (i.e. we don’t bother exposing the base
class under Python). PyLibrary_LinkPyType()
setup the class functions
in the PyTypeLibrary
type object, it must be called in the
Python module this class is part of (in this case: PyHurricane.cpp
).
This particular flavor of the macro will define and setup the
following class functions:
PyTypeLibrary.tp_compare
(defined by the macro).PyTypeLibrary.tp_repr
(defined by the macro).PyTypeLibrary.tp_str
(defined by the macro).PyTypeLibrary.tp_hash
(defined by the macro).PyTypeLibrary.tp_methods
sets to the previously definedPyLibrary_Methods
table.PyTypeLibrary.tp_dealloc
is set to a function that must be namedPyLibrary_DeAlloc
, this is whatDBoDeleteMethod
does. It is not done byPyTypeObjectLinkPyType
.
Defining the PyTypeLibrary
type:
3.3 Python Module (C++ namespace)¶
We use the Python module to replicate the C++ namespace. Thus, for the
Hurricane
namespace we create a Python Hurricane
module which is
defined in the PyHurricane.cpp
file, then we add into that module
dictionary all the Python types encapsulating the C++ classes of that
namespace.
DL_EXPORT(void) initHurricane ()
{
PyLibrary_LinkPyType(); // step 1.
PYTYPE_READY( Library ) // step 2.
__cs.addType( "library", &PyTypeLibrary, "<Library>", false ); // step 3.
PyObject* module = Py_InitModule( "Hurricane", PyHurricane_Methods );
if (module == NULL) {
cerr << "[ERROR]\n"
<< " Failed to initialize Hurricane module." << endl;
return;
}
Py_INCREF( &PyTypeLibrary ); // step 4.
PyModule_AddObject( module, "Library", (PyObject*)&PyTypeLibrary ); // step 4.
}
The initHurricane()
initialisation function shown above has
been scrubbed of everything not relevant to the PyLibrary
class.
The integration of the PyLibrary
class into the module needs
four steps:
A call to PyLibrary_LinkPyType() to hook the Python type functions in the Python type object.
A call to the
PYTYPE_READY()
macro (standard Python).Registering the type into the
__cs
object, withaddType()
. The arguments are self explanatory, save for the last which is a boolean to tell if this is a derived class or not.Adding the type object (
PyTypeLibrary
) into the dictionnary of the module itself. This allow to mimic closely the C++ syntax:import Hurricane lib = Hurricane.Library.create( db, 'root' )