394 lines
13 KiB
ReStructuredText
394 lines
13 KiB
ReStructuredText
|
.. -*- Mode: rst -*-
|
||
|
|
||
|
|
||
|
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:
|
||
|
|
||
|
.. _3.1 Class Associated Header File:
|
||
|
|
||
|
3.1 Class Associated Header File
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
Here is the typical content of a header file (for ``PyLibrary``):
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
#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:
|
||
|
|
||
|
1. 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``).
|
||
|
|
||
|
2. As Python is written in C, all the wrapper code has to be but inside
|
||
|
an ``extern "C"`` namespace.
|
||
|
|
||
|
3. 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).
|
||
|
|
||
|
4. Declaration of the Python type ``PyTypeLibrary`` (standard).
|
||
|
|
||
|
5. Declaration of the Python type table of methods ``PyLibrary_Methods`` (standard).
|
||
|
|
||
|
.. _3.6:
|
||
|
|
||
|
6. Declaration of ``PyLibrary_Link()``, helper to convert a C++ ``Lybrary`` into
|
||
|
a ``PyLibrary`` (put in the support shared library).
|
||
|
|
||
|
7. Declaration of ``PyLibrary_LinkPyType()``, this function setup the class-level
|
||
|
function of the new Python type (here, ``PyTypeLibrary``).
|
||
|
|
||
|
8. And, lastly, three macros to:
|
||
|
|
||
|
* ``IsPylibrary()``, know if a Python object is a ``PyLibrary``
|
||
|
* ``PYLIBRARY()``, force cast (C style) of a ``PyObject`` into a ``PyLibrary``.
|
||
|
* ``PYLIBRARY_O()``, extract the C++ object (``Library*``) from the Python
|
||
|
object (``PyLibrary``).
|
||
|
|
||
|
|
||
|
.. _3.2 Class Associated File:
|
||
|
|
||
|
3.2 Class Associated File
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
3.2.1 Head of the file
|
||
|
------------------------
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
#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:
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
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 with ``PyCell_Link()``.
|
||
|
This macro is supplied by the ``PyCell.h`` header and this is why
|
||
|
it must be included.
|
||
|
|
||
|
|newpage|
|
||
|
|
||
|
|
||
|
Wrapping of the ``Library::create()`` method:
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
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 use ``Converter``
|
||
|
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()``
|
||
|
and ``PYDATABSE_O()`` macros to perform the conversion.
|
||
|
|
||
|
#. Return the result, encapsulated through a call to ``PyLibrary_Link()``.
|
||
|
|
||
|
|newpage|
|
||
|
|
||
|
|
||
|
Wrapping of the ``Library::destroy()`` method:
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
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:
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
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.
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
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.
|
||
|
|
||
|
.. _PyLibrary_LinkPyType():
|
||
|
|
||
|
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 defined ``PyLibrary_Methods`` table.
|
||
|
* ``PyTypeLibrary.tp_dealloc`` is set to a function that *must* be named ``PyLibrary_DeAlloc``,
|
||
|
this is what ``DBoDeleteMethod`` does. It is *not* done by ``PyTypeObjectLinkPyType``.
|
||
|
|
||
|
Defining the ``PyTypeLibrary`` type:
|
||
|
|
||
|
|
||
|
3.2.4 The Shared Library Part
|
||
|
-------------------------------
|
||
|
|
||
|
This part will be put in a separate supporting shared library, allowing
|
||
|
other Python module to link against it (and make use of its symbols).
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
DBoLinkCreateMethod(Library)
|
||
|
PyTypeObjectDefinitions(Library)
|
||
|
|
||
|
|
||
|
To define ``PyTypeLibrary``, use the ``PyTypeObjectDefinitions()`` macro.
|
||
|
This macro is specific for classes that, as exposed by Python,
|
||
|
are neither *derived* classes nor *base* classes for others.
|
||
|
That is, they are standalone from the inheritance point of view.
|
||
|
|
||
|
The ``DBoLinkCreateMethod()`` macro will define the ``PyLibrary_Link()``
|
||
|
function which is responsible for encapsulating a C++ ``Library`` object
|
||
|
into a Python ``PyLibrary`` one.
|
||
|
|
||
|
|
||
|
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.
|
||
|
|
||
|
.. code-block:: c++
|
||
|
|
||
|
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, with ``addType()``.
|
||
|
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:
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
import Hurricane
|
||
|
lib = Hurricane.Library.create( db, 'root' )
|