OK, I have merged the content of the Wrapping_C++ wiki into the user
guide Wrapping_C++. I did only minimal changes to the content of the
wiki. Therefore, the style could do with some improving, but at least
all the infos should be included.
I slightly changed the structure, so that there is now only 4 main chapters:
- Overview
- A simple Tutorial (covering the basics of wrapping a C++ class)
- Advanced C++ features (covering everything that was not discussed in
the tutorial)
- Caveats and limitations ( extern "C" functions, references, ...)
Compared to the wiki version, I added the following:
- nested class declaration syntax
- operators in cython.operator (only dereference and preincrement; if
someone know more of them, please tell me)
- pre-declared pxds for the standard library
Now, to do the "documentation switch", it should just be enough to:
-- update the user guide with the attached file
-- update the wiki files
Do you want to do the switch now or wait for the cython0.13 release?
Of course, anybody, feel free to first check the new file. It can
certainly do with some revisions.
*******
I have also a couple of questions that would deserved to be answered in the doc.
1- The original user guide recommend the following for initializing an
extension class wrapping the C++ class Rectangle:
def __cinit__(self, int x0, int y0, int x1, int y1):
self.thisptr = new_Rectangle(x0, y0, x1, y1)
Should we recommend "def __cinit__(self, int x0, int y0, int x1, int
y1) except +MemoryError" instead?
2- Am I correct that it is not possible to use stack-allocated C++
objects as Python extension class members?
3- The whole C++ reference thing still seems to be a bit messy, so I
did not discuss it much in the doc. I am curious about one small
detail, though. In the STL pxds, the push_back function for vector is,
for example, declared like this:
void push_back(&T)
Why not just "void push_back(T)" ? More precisely: is there any point
to tell python which arguments of a declared function are references?.
It seems the code that cython must generate is the same in any case...
4- I think there is no support for templated functions. Am I right?
That's all for now.
Toki
# HG changeset patch
# User fab...@laptop
# Date 1278336267 -32400
# Node ID b4f6d17816d7bd4f4e103b90432efb781bd8d0a9
# Parent 310d04bdec7a600afd78f831355812dc50e32fdd
Updated wrapping_CPlusPlus.html for Cython v0.13
diff -r 310d04bdec7a -r b4f6d17816d7 src/userguide/wrapping_CPlusPlus.rst
--- a/src/userguide/wrapping_CPlusPlus.rst Sat Jul 03 10:23:07 2010 +0200
+++ b/src/userguide/wrapping_CPlusPlus.rst Mon Jul 05 22:24:27 2010 +0900
@@ -9,33 +9,36 @@
Overview
=========
-This page aims to get you quickly up to speed so you can wrap C++ interfaces
-with a minimum of pain and 'surprises'.
+Cython v0.13 introduces native support for most of the C++ language. This means that the previous tricks that were used to wrap C++ classes (as described in http://wiki.cython.org/WrappingCPlusPlus_ForCython012AndLower) are no longer useful.
-In the past, Pyrex only supported wrapping of C APIs, and not C++. To wrap
-C++, one had to write a pure-C shim, containing functions for
-constructors/destructors and method invocations. Object pointers were passed
-around as opaque void pointers, and cast to/from object pointers as needed.
-This approach did work, but it got awfully messy and error-prone when trying
-to wrap APIs with large class hierarchies and lots of inheritance.
+Wrapping C++ classes with Cython is now much more straightforward. This document describe in details the new way of wrapping C++ code.
-These days, though, Pyrex offers an adequate bare minimum of C++ support,
-which Cython has inherited. The approach described in this document will help
-you wrap a lot of C++ code with only moderate effort. There are some
-limitations, which we will discuss at the end of the document.
+What's new in Cython v0.13 about C++
+---------------------------------------------------
+
+For users of previous Cython versions, here is a brief overview of the main new features of Cython v0.13 regarding C++ support:
+
+* C++ objects can now be dynamically allocated with ``new`` and ``del`` keywords.
+* C++ objects can now be stack-allocated.
+* C++ classes can be declared with the new keyword ``cppclass``.
+* Templated classes are supported.
+* Overloaded functions are supported.
+* Overloading of C++ operators (such as operator+, operator[],...) is supported.
Procedure Overview
-====================
+-------------------
+The general procedure for wrapping a C++ file can now be described as follow:
* Specify C++ language in :file:`setup.py` script
-* Create ``cdef extern from`` blocks and declare classes as
- ``ctypedef struct`` blocks
-* Create constructors and destructors
-* Add class methods as function pointers
-* Create Cython wrapper class
+* Create ``cdef extern from`` blocks with the optional namespace (if exists) and the namespace name as string
+* Declare classes as ``cdef cppclass`` blocks
+* Declare public attributes (variables, methods and constructors)
+
+A simple Tutorial
+==================
An example C++ API
-===================
+-------------------
Here is a tiny C++ API which we will use as an example throughout this
document. Let's assume it will be in a header file called
@@ -43,17 +46,19 @@
.. sourcecode:: c++
- class Rectangle {
- public:
- int x0, y0, x1, y1;
- Rectangle(int x0, int y0, int x1, int y1);
- ~Rectangle();
- int getLength();
- int getHeight();
- int getArea();
- void move(int dx, int dy);
- };
-
+ namespace shapes {
+ class Rectangle {
+ public:
+ int x0, y0, x1, y1;
+ Rectangle(int x0, int y0, int x1, int y1);
+ ~Rectangle();
+ int getLength();
+ int getHeight();
+ int getArea();
+ void move(int dx, int dy);
+ };
+ }
+
and the implementation in the file called :file:`Rectangle.cpp`:
.. sourcecode:: c++
@@ -98,11 +103,11 @@
This is pretty dumb, but should suffice to demonstrate the steps involved.
Specify C++ language in setup.py
-=================================
+---------------------------------
In Cython :file:`setup.py` scripts, one normally instantiates an Extension
object. To make Cython generate and compile a C++ source, you just need
-to add a keyword to your Extension construction statement, as in::
+to add the keyword ``language="c++"`` to your Extension construction statement, as in::
ext = Extension(
"rectangle", # name of extension
@@ -114,103 +119,85 @@
cmdclass = {'build_ext': build_ext}
)
-and Cython will generate and compile the :file:`rectangle.cpp` file (from the
+Cython will generate and compile the :file:`rectangle.cpp` file (from the
:file:`rectangle.pyx`), then it will compile :file:`Rectangle.cpp`
(implementation of the ``Rectangle`` class) and link both objects files
together into :file:`rectangle.so`, which you can then import in Python using
``import rectangle`` (if you forget to link the :file:`Rectangle.o`, you will
get missing symbols while importing the library in Python).
-With the language="c++" keyword, Cython distutils will generate a C++ file.
-Create cdef extern from block
-==============================
+Alternatively, one can also use the ``cython`` command-line utility to generate a C++ ``.cpp`` file, and then compile it into a python extension. C++ mode for the ``cython`` command is turned on with the ``--cplus`` option.
+
+Declaring a C++ class interface
+--------------------------------
The procedure for wrapping a C++ class is quite similar to that for wrapping
normal C structs, with a couple of additions. Let's start here by creating the
basic ``cdef extern from`` block::
- cdef extern from "Rectangle.h":
+ cdef extern from "Rectangle.h" namespace "shapes":
-This will make the C++ class def for Rectangle available.
+This will make the C++ class def for Rectangle available. Note the namespace declaration.
-Declare class as a ctypedef struct
------------------------------------
+Declare class with cdef cppclass
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Now, let's add the Rectangle class to this extern from block -- just copy the
-class def from :file:`Rectangle.h` and adjust for Cython syntax, so now it
-becomes::
+Now, let's add the Rectangle class to this extern from block - just copy the class name from Rectangle.h and adjust for Cython syntax, so now it becomes::
- cdef extern from "Rectangle.h":
- # known in Cython namespace as 'c_Rectangle' but in C++ as 'Rectangle'
- ctypedef struct c_Rectangle "Rectangle":
- int x0, y0, x1, y1
+ cdef extern from "Rectangle.h" namespace "shapes":
+ cdef cppclass Rectangle:
+
+Add public attributes
+^^^^^^^^^^^^^^^^^^^^^^
-We don't have any way of accessing the constructor/destructor or methods, but
-we'll cover this now.
+We now need to declare the attributes for use on Cython::
-Add constructors and destructors
-----------------------------------
-
-We now need to expose a constructor and destructor into the Cython
-namespace. Again, we'll be using C name specifications::
-
- cdef extern from "Rectangle.h":
- ctypedef struct c_Rectangle "Rectangle":
- int x0, y0, x1, y1
- c_Rectangle *new_Rectangle "new Rectangle" (int x0, int y0, int x1, int y1)
- void del_Rectangle "delete" (c_Rectangle *rect)
-
-Add class methods
--------------------
-
-Now, let's add the class methods. You can circumvent Cython syntax
-limitations by declaring these as function pointers. Recall that in the C++
-class we have:
-
-.. sourcecode:: c++
-
- int getLength();
- int getHeight();
- int getArea();
- void move(int dx, int dy);
-
-So if we convert each of these to function pointers and stick them in our
-extern block, we now get::
-
- cdef extern from "Rectangle.h":
- ctypedef struct c_Rectangle "Rectangle":
+ cdef extern from "Rectangle.h" namespace "shapes":
+ cdef cppclass Rectangle:
+ Rectangle(int, int, int, int)
int x0, y0, x1, y1
int getLength()
int getHeight()
int getArea()
- void move(int dx, int dy)
- c_Rectangle *new_Rectangle "new Rectangle" (int x0, int y0, int x1, int y1)
- void del_Rectangle "delete" (c_Rectangle *rect)
+ void move(int, int)
-This will fool Cython into generating C++ method calls even though
-Cython is mostly oblivious to C++.
+Declare a var with the wrapped C++ class
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-In Pyrex you must explicitly declare these as function pointers, i.e.
-``(int *getArea)()``.
+Now, we use cdef to declare a var of the class with the know C++ ``new`` statement::
+
+ cdef Rectangle *rec = new Rectangle(1, 2, 3, 4)
+ cdef int recLength = rec.getLength()
+ ...
+ del rec #delete heap allocated object
+
+It's also possible to declare a stack allocated object, but it's necessary to have a "default" constructor::
+
+ cdef extern from "Foo.h":
+ cdef cppclass Foo:
+ Foo()
+
+ cdef Foo foo
+
+Note that, like C++, if the class has only one constructor and it is a default one, it's not necessary to declare it.
Create Cython wrapper class
-=============================
+----------------------------
-At this point, we have exposed into our pyx file's namespace a struct which
-gives us access to the interface of a C++ Rectangle type. Now, we need to make
+At this point, we have exposed into our pyx file's namespace the interface of the C++ Rectangle type. Now, we need to make
this accessible from external Python code (which is our whole point).
Common programming practice is to create a Cython extension type which
holds a C++ instance pointer as an attribute ``thisptr``, and create a bunch of
forwarding methods. So we can implement the Python extension type as::
- cdef class Rectangle:
- cdef c_Rectangle *thisptr # hold a C++ instance which we're wrapping
+ cdef class PyRectangle:
+ cdef Rectangle *thisptr # hold a C++ instance which we're wrapping
def __cinit__(self, int x0, int y0, int x1, int y1):
- self.thisptr = new_Rectangle(x0, y0, x1, y1)
+ self.thisptr = new Rectangle(x0, y0, x1, y1)
def __dealloc__(self):
- del_Rectangle(self.thisptr)
+ del self.thisptr
def getLength(self):
return self.thisptr.getLength()
def getHeight(self):
@@ -229,62 +216,141 @@
def __set__(self, x0): self.thisptr.x0 = x0
...
-Caveats and Limitations
-========================
-In this document, we have discussed a relatively straightforward way of
-wrapping C++ classes with Cython. However, there are some limitations in
-this approach, some of which could be overcome with clever workarounds (anyone
-here want to share some?), but some of which will require new features in
-Cython.
+Advanced C++ features
+======================
-The major limitations I'm most immediately aware of (and there will be many
-more) include:
+We describe here all the C++ features that were not discussed in the above tutorial.
Overloading
------------
-Presently, it's not easy to overload methods or constructors, but there may be
-a workaround if you try some creative C name specifications
+Overloading is very simple. Just declare the method with different parameters and use any of them::
-Access to C-only functions
----------------------------
+ cdef extern from "Foo.h":
+ cdef cppclass Foo:
+ Foo(int)
+ Foo(bool)
+ Foo(int, bool)
+ Foo(int, int)
-Whenever generating C++ code, Cython generates declarations of and calls
-to functions assuming these functions are C++ (ie, not declared as extern "C"
-{...} . This is ok if the C functions have C++ entry points, but if they're C
-only, you will hit a roadblock. If you have a C++ Cython module needing
-to make calls to pure-C functions, you will need to write a small C++ shim
-module which:
-
-* includes the needed C headers in an extern "C" block
-* contains minimal forwarding functions in C++, each of which calls the
- respective pure-C function
-
-Inherited C++ methods
+Overloading operators
----------------------
-If you have a class ``Foo`` with a child class ``Bar``, and ``Foo`` has a
-method :meth:`fred`, then you'll have to cast to access this method from
-``Bar`` objects.
+Cython uses C++ for overloading operators::
+
+ cdef extern from "foo.h":
+ cdef cppclass Foo:
+ Foo()
+ Foo* operator+(Foo*)
+ Foo* operator-(Foo)
+ int operator*(Foo*)
+ int operator/(int)
+
+ cdef Foo* foo = new Foo()
+ cdef int x
+
+ cdef Foo* foo2 = foo[0] + foo
+ foo2 = foo[0] - foo[0]
+
+ x = foo[0] * foo2
+ x = foo[0] / 1
+
+ cdef Foo f
+ foo = f + &f
+ foo2 = f - f
+
+ del foo, foo2
+
+Nested class declarations
+--------------------------
+C++ allows nested class declaration. Class declarations can also be nested in Cython::
+
+
+ cdef extern from "<vector>" namespace "std":
+ cdef cppclass vector[T]:
+ cppclass iterator:
+ T operator*()
+ iterator operator++()
+ bint operator==(iterator)
+ bint operator!=(iterator)
+ vector()
+ void push_back(T&)
+ T& operator[](int)
+ T& at(int)
+ iterator begin()
+ iterator end()
+
+ cdef vector[int].iterator iter #iter is declared as being of type vector<int>::iterator
+
+Note that the nested class is declared with a ``cppclass`` but without a ``cdef``.
+
+C++ operators not compatible with Python syntax
+------------------------------------------------
+
+Cython try to keep a syntax as close as possible to standard Python. Because of this, certain C++ operators, like the preincrement ``++foo`` or the dereferencing operator ``*foo`` cannot be used with the same syntax as C++. Cython provides functions replacing these operators in a special module ``cython.operator``. The functions provided are:
+
+* ``cython.operator.dereference`` for dereferencing. ``dereference(foo)`` will produce the C++ code ``*foo``
+* ``cython.operator.preincrement`` for pre-incrementation. ``preincrement(foo)`` will produce the C++ code ``++foo``
+* ...
+
+These functions need to be cimported. Of course, one can use a ``from ... cimport ... as`` to have shorter and more readable functions. For example: ``from cython.operator cimport dereference as deref``.
+
+Templates
+----------
+
+Cython uses a bracket syntax for templating. A simple example for wrapping C++ vector::
+
+ from cython.operator cimport dereference as deref, preincrement as inc #dereference and increment operators
+
+ cdef extern from "<vector>" namespace "std":
+ cdef cppclass vector[T]:
+ cppclass iterator:
+ T operator*()
+ iterator operator++()
+ bint operator==(iterator)
+ bint operator!=(iterator)
+ vector()
+ void push_back(T&)
+ T& operator[](int)
+ T& at(int)
+ iterator begin()
+ iterator end()
+
+ cdef vector[int] *v = new vector[int]()
+ cdef int i
+ for i in range(10):
+ v.push_back(i)
+
+ cdef vector[int].iterator it = v.begin()
+ while it != v.end():
+ print deref(it)
+ inc(it)
+
+ del v
+
+Multiple template parameters can be defined as a list, such as [T, U, V] or [int, bool, char].
+
+Standard library
+-----------------
+
+Most of the containers of the C++ Standard Library have been declared in pxd files located in ``/Cython/Includes/libcpp``. These containers are: deque, list, map, pair, queue, set, stack, vector.
+
For example::
- class MyClass:
- Bar *b
- ...
- def myfunc(self):
- ...
- b.fred() # wrong, won't work
- (<Foo *>(self.b)).fred() # should work, Cython now thinks it's a 'Foo'
+ from libcpp.vector cimport vector
-It might take some experimenting by others (you?) to find the most elegant
-ways of handling this issue.
-
-Advanced C++ features
-----------------------
+ cdef vector[int] vect
+ cdef int i
+ for i in range(10):
+ vect.push_back(i)
+ for i in range(10):
+ print vect[i]
+
+The pxd files in ``/Cython/Includes/libcpp`` also work as good examples on how to declare C++ classes.
Exceptions
-^^^^^^^^^^^
+-----------
Cython cannot throw C++ exceptions, or catch them with a try-except statement,
but it is possible to declare a function as potentially raising an C++
@@ -310,57 +376,51 @@
raise_py_error does not actually raise an exception a RuntimeError will be
raised.
-Templates
-^^^^^^^^^^
-Cython does not natively understand C++ templates but we can put them to use
-in some way. As an example consider an STL vector of C ints::
+Caveats and Limitations
+========================
- cdef extern from "some .h file which includes <vector>":
- ctypedef struct intvec "std::vector<unsigned int>":
- void (* push_back)(int elem)
- intvec intvec_factory "std::vector<unsigned int>"(int len)
+Access to C-only functions
+---------------------------
-now we can use the vector like this::
+Whenever generating C++ code, Cython generates declarations of and calls
+to functions assuming these functions are C++ (ie, not declared as extern "C"
+{...} . This is ok if the C functions have C++ entry points, but if they're C
+only, you will hit a roadblock. If you have a C++ Cython module needing
+to make calls to pure-C functions, you will need to write a small C++ shim
+module which:
- cdef intvec v = intvec_factory(2)
- v.push_back(2)
+* includes the needed C headers in an extern "C" block
+* contains minimal forwarding functions in C++, each of which calls the
+ respective pure-C function
-Overloading
-^^^^^^^^^^^^
+Inherited C++ methods
+----------------------
-To support function overloading simply add a different alias to each
-signature, so if you have e.g.
+If you have a class ``Foo`` with a child class ``Bar``, and ``Foo`` has a
+method :meth:`fred`, then you'll have to cast to access this method from
+``Bar`` objects.
+For example::
-.. sourcecode:: c++
+ cdef class MyClass:
+ Bar *b
+ ...
+ def myfunc(self):
+ ...
+ b.fred() # wrong, won't work
+ (<Foo *>(self.b)).fred() # should work, Cython now thinks it's a 'Foo'
- int foo(int a);
- int foo(int a, int b);
-
-in your C++ header then interface it like this in your ::
-
- int fooi "foo"(int)
- int fooii "foo"(int, int)
-
-Operators
-^^^^^^^^^^
-
-Some operators (e.g. +,-,...) can be accessed from Cython like this::
-
- ctypedef struct c_Rectangle "Rectangle":
- c_Rectangle add "operator+"(c_Rectangle right)
+It might take some experimenting by others (you?) to find the most elegant
+ways of handling this issue.
Declaring/Using References
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+---------------------------
Question: How do you declare and call a function that takes a reference as an argument?
-Conclusion
-============
+C++ left-values
+----------------
-A great many existing C++ classes can be wrapped using these techniques, in a
-way much easier than writing a large messy C shim module. There's a bit of
-manual work involved, and an annoying maintenance burden if the C++ library
-you're wrapping is frequently changing, but this recipe should hopefully keep
-the discomfort to a minimum.
+C++ allows functions returning a reference to be left-values. This is currently not supported in Cython. ``cython.operator.dereference(foo)`` is also not considered a left-value.
+
_______________________________________________
Cython-dev mailing list
[email protected]
http://codespeak.net/mailman/listinfo/cython-dev