0%

Cython调用C++函数例子

最近的的科研工作涉及到Cython的应用。以前没有接触过Cython,对这个基本不了解。如果不涉及到代码接口的改变,那么我可以学习前辈们的代码,照葫芦画瓢来改代码。可是那样的代码不够简介美观,Pull Request的Reviewer指出代码接口需要调整简化。

没办法,只能硬着头皮改。然而前辈们的代码中没有找到合适的参照,自己假象着改几下结果错误越來越多。不能像无头苍蝇一样改了,必须得学会Cython的基础使用方法。

我需要实现的功能是:有一个C++的程序,写一个Cython的接口,在python中调用C++中的函数。参考相关的官方文档,这是一个简单的几何方面的库。按照官方文档走,结果还是走了不少弯路,有写东西官方文档上也没有写的很详细。

下面就将我学习这个例子的过程及需要注意的细节总结一下。

C++代码部分

h文件

1
2
3
4
5
6
7
8
9
10
11
12
namespace shapes {
class Rectangle {
public:
int x0, y0, x1, y1;
Rectangle();
Rectangle(int x0, int y0, int x1, int y1);
~Rectangle();
int getArea();
void getSize(int* width, int* height);
void move(int dx, int dy);
};
}

cpp文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "Rectangle.h"

namespace shapes {

Rectangle::Rectangle() { }

Rectangle::Rectangle(int X0, int Y0, int X1, int Y1) {
x0 = X0;
y0 = Y0;
x1 = X1;
y1 = Y1;
}

Rectangle::~Rectangle() { }

int Rectangle::getArea() {
return (x1 - x0) * (y1 - y0);
}

void Rectangle::getSize(int *width, int *height) {
(*width) = x1 - x0;
(*height) = y1 - y0;
}

void Rectangle::move(int dx, int dy) {
x0 += dx;
y0 += dy;
x1 += dx;
y1 += dy;
}

}

pyx文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cdef extern from "Rectangle.h" namespace "shapes":
cdef cppclass Rectangle:
Rectangle() except +
Rectangle(int, int, int, int) except +
int x0, y0, x1, y1
int getArea()
void getSize(int* width, int* height)
void move(int, int)

cdef class PyRectangle:
cdef Rectangle c_rect # hold a C++ instance which we're wrapping
def __cinit__(self, int x0, int y0, int x1, int y1):
self.c_rect = Rectangle(x0, y0, x1, y1)
def get_area(self):
return self.c_rect.getArea()
def get_size(self):
cdef int width, height
self.c_rect.getSize(&width, &height)
return width, height
def move(self, dx, dy):
self.c_rect.move(dx, dy)

setup.py文件

1
2
3
4
5
6
7
8
from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules = cythonize(
"rect.pyx", # our Cython source
sources=["Rectangle.cpp"], # additional source file(s)
language="c++", # generate C++ code
))

编译

前面的文件内容在官方文档上写的还算清楚,但编译和使用部分,官方文档写的就很不清楚了。尤其是官方文档这里没有介绍编译cpp文件,只说不链接这个东西会出现undefine symbol的错误。这部分内容参考了这里

编译C++文件

编译C++文件生成.o文件,C++ Objects在这个文件里面。

1
$ gcc -c Rectangle.cpp

运行setup

1
$ python setup.py build_ext --inplace

这一步会生成rect.so,但是这个文件暂时还不能使用,在python中import rect的话会出现ImportError: ./rect.so: undefined symbol错误。

链接rect.so与Rectangle.o

1
$ gcc -shared -fPIC -I/usr/include/python2.7 rect.cpp Rectangle.o -lstdc++ -o rect.so

测试运行

下一个脚本run.py,放入如下内容:

1
2
import rect
print(rect.PyRectangle(0, 0, 1, 2).get_area())

运行该脚本,如果流程正确,应该会得到输出:

1
2

总结反思

通过这个例子,不难看出:

  1. Cython连接C++的函数是是通过Python类的函数->cppclass->C++函数实现的。
  2. Python类的函数名称不一定要和C++函数名称一直,只要接口定义好到底调用哪个函数就行。
  3. Python类的函数数目也不一定要和C++函数一致,只需要保证Python想调用的C++函数已经定义且能够找到就行。