整体流程
- 处理从Python中传入的参数
- 使用C/C++实现功能逻辑
- 将功能逻辑产生的返回值包装成Python所需的格式
- 注册函数
- 注册模块名
- 编译
具体步骤
一、引入头文件
#include <Python.h>
该文件一般位于python安装目录的include文件夹下。
二、处理从Python中传入的参数
图片摘自:https://blog.csdn.net/taiyang1987912/article/details/44779719
// python list -> c++ vector
void pyListToPointVec(PyObject* object, vector<Point>& contour)
{
if(!PyList_Check(object))
return;
int point_num = PyList_Size(object);
contour.clear();
for(int i = 0; i< point_num; i++){
PyObject* rowArr = PyList_GetItem(object,i);
PyObject* py_x = PyList_GetItem(rowArr, 0);
PyObject* py_y = PyList_GetItem(rowArr, 1);
int x = PyInt_AsLong(py_x);
int y = PyInt_AsLong(py_y);
contour.push_back(Point(x, y));
}
}
// python tuple -> c++ parameters
void pyParamToCppType(PyObject* args, int* param1, int* param2)
{
// i|i 表示将参数解析为两个int,同理,s表示字符串
PyArg_ParseTuple(args, "i|i", ¶m1, ¶m2);
}
三、实现功能逻辑
即希望借助C++进行扩展的功能模块,例如:
int my_add(int x, int y)
{
return x + y;
}
四、处理C/C++的返回值
// c++ vector -> python list
PyObject* pointVecToPyList(vector<Point>& contour)
{
int sz = contour.size();
PyObject* res_cnt = PyList_New(sz);
for(int idx1 = 0; idx1 < sz; ++idx1)
{
PyObject* onePoint = PyList_New(2);;
int x = contour[idx1].x;
int y = contour[idx1].y;
PyObject* py_x = PyInt_FromLong(x);
PyObject* py_y = PyInt_FromLong(y);
PyList_SetItem(onePoint, 0, py_x);
PyList_SetItem(onePoint, 1, py_y);
PyList_SetItem(res_cnt, idx1, onePoint);
}
return res_cnt;
}
// c++ int -> python int
PyObject* cppIntToPyInt(int res)
{
// i 表示int类型,同理,s表示字符串
return Py_BuildValue("i", res);
}
五、编写包装函数
即对上述三部分进行包装,函数声明形式一般如下所示,且一般应声明为static类型。函数的第一个参数为默认传入的Python对象,第二个参数为所需参数列表(实际上已被序列化成一个字符串)
// 包装函数
static PyObject* alg(PyObject *self, PyObject *args)
{
// get input parameters [[], [], …]
PyObject* arg_list = PyTuple_GetItem(args, 0);
// python list -> c++ vector
vector<Point> contour;
pyListToPointVec(arg_list, contour);
// 真正要实现的算法
Alg(contour);
// c++ vector -> python list
PyObject* result = pointVecToPyList(contour);
return result;
}
static PyObject* myPyAdd(PyObject *self, PyObject *args)
{
int x, y;
PyArg_ParseTuple(args, "i|i", &x, &y);
return Py_BuildValue("i", x + y);
}
六、注册函数
导出工作的第一步:在类型为PyMethodDef的结构体中注册需要导出的函数。
static PyMethodDef ExtendMethods[] ={
{"alg", alg, METH_VARARGS, "algorithm from cpp"},
{NULL, NULL}
};
几点说明:
- "alg" 为导出到Python中可见的方法名
- alg 为C/C++ 中待导出的函数名
- METH_VARARGS 表示传入函数的是普通参数,还有关键词参数等(未试验)
七、注册模块
导出工作的第二步:定义一个命名为initXXX的函数,XXX必须是模块名。
PyMODINIT_FUNC initdemo(void)
{
Py_InitModule("demo", ExtendMethods);
}
几点说明:
- 调用Py_InitModule注册模块
- 第一个参数为导出模块的模块名
- 第二个参数为上一步中定义的PyMethodDef结构体的名称
八、编译
使用GCC编译生成动态链接库,例如:
gcc main.cpp -I /usr/local/include/python2.7 -shared -fPIC -o demo.so
九、调用
# test.py
import demo
res = demo.alg(xxx)