Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Gwenaël Samain
cython
Commits
cc9744c1
Commit
cc9744c1
authored
May 04, 2008
by
Gabriel Gellner
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added in wrapping C++ description.
parent
cc3b4267
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
306 additions
and
3 deletions
+306
-3
docs/wrapping_CPlusPlus.rst
docs/wrapping_CPlusPlus.rst
+306
-3
No files found.
docs/wrapping_CPlusPlus.rst
View file @
cc9744c1
.. _wrapping-cplusplus-label:
Wrapping C++
============
Wrapping C++ Classes in Cython
====================================
Overview
--------
This page aims to get you quickly up to speed so you can wrap C++ interfaces
with a minimum of pain and 'surprises'.
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.
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.
Procedure Overview
------------------
* 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
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
:file:`Rectangle.h`::
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);
};
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::
ext = Extension(
"rectangle", # name of extension
["rectangle.pyx"], # filename of our Cython source
language="c++", # this causes Cython to create C++ source
include_dirs=[...], # usual stuff
libraries=[...], # ditto
extra_link_args=[...], # if needed
cmdclass = {'build_ext': build_ext}
)
With the language="c++" keyword, Cython distutils will generate a C++ file.
Create cdef extern from block
-----------------------------
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":
This will make the C++ class def for Rectangle available.
Declare class as a ctypedef struct
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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::
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
We don't have any way of accessing the constructor/destructor or methods, but
we'll cover this now.
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::
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":
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)
This will fool Cython into generating C++ method calls even though
Cython is mostly oblivious to C++.
In Pyrex you must explicitly declare these as function pointers, i.e.
``(int *getArea)()``.
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
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
def __cinit__(self, int x0, int y0, int x1, int y1):
self.thisptr = new_Rectangle(x0, y0, x1, y1)
def __dealloc__(self):
del_Rectangle(self.thisptr)
def getLength(self):
return self.thisptr.getLength()
def getHeight(self):
return self.thisptr.getHeight()
def getArea(self):
return self.thisptr.getArea()
def move(self, dx, dy):
self.thisptr.move(dx, dy)
And there we have it. From a Python perspective, this extension type will look
and feel just like a natively defined Rectangle class. If you want to give
attribute access, you could just implement some properties::
property x0:
def __get__(self): return self.thisptr.x0
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.
The major limitations I'm most immediately aware of (and there will be many
more) include:
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
Access to C-only functions
^^^^^^^^^^^^^^^^^^^^^^^^^^
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
^^^^^^^^^^^^^^^^^^^^^
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::
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'
It might take some experimenting by others (you?) to find the most elegant
ways of handling this issue.
Advanced C++ features
^^^^^^^^^^^^^^^^^^^^^
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++
exception and converting it into a Python exception. For example, ::
cdef extern from "some_file.h":
cdef int foo() except +
This will translate try and the C++ error into an appropriate Python exception
(currently an IndexError on std::out_of_range and a RuntimeError otherwise
(preserving the what() message). ::
cdef int bar() except +MemoryError
This will catch any C++ error and raise a Python MemoryError in its place.
(Any Python exception is valid here.) ::
cdef int raise_py_error()
cdef int something_dangerous() except +raise_py_error
If something_dangerous raises a C++ exception then raise_py_error will be
called, which allows one to do custom C++ to Python error "translations." If
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::
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)
now we can use the vector like this::
cdef intvec v = intvec_factory(2)
v.push_back(2)
Overloading
"""""""""""
To support function overloading simply add a different alias to each
signature, so if you have e.g. ::
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)
Declaring/Using References
""""""""""""""""""""""""""
Question: How do you declare and call a function that takes a reference as an argument?
Conclusion
----------
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.
TODO
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment