Python调用C语言的函数主要分为三部分:标准的C语言函数库、C语言Python调用模块(wrapper)、和标准的Python调用。
里面有一系列的C语言函数。
int add(int m, int n)
{
return m + n;
}
$ python
> import calculate
> calculate.add(3, 4)
C语言Python wrapper,python原生代码可以通过这个wrapper来调用C语言函数文件里的函数。
这个模块分为三部分:导出函数、方法列表和初始化函数。
/* Exported function */
static
PyCal_add(PyObject *self, PyObject *args)
{
/* Define variables */
int m, n;
int ret;
PyObject *obj;
/* Parse Python args to C args */
PyArg_ParseTuple(args, "ii", &m, &n);
/* Call C native function */
ret = add(m, n);
/* Convert C variable to Python */
obj = Py_BuildValue("i", ret);
/* Return (converted)Python Object */
return obj;
}
/* Method list */
static PyMethodDef CalMethods[] = {
{"add", PyCal_add, METH_VARARGS, "Add two integer values."},
{NULL, NULL, 0, NULL}
};
/* Initialization function */
PyMODINIT_FUNC
initcalcalate(void)
{
PyObject *m;
m = Py_InitModule("calculate", CalMethods);
if(m == NULL) {
return;
}
}
连接C语言函数和Python函数的中间件,由它来进行相互转换。把Python的参数转成C的参数传递过去,然后再把C的输出转换成Python格式返回。
- 转换Python参数为C语言类型
该函数的第二个参数args
就是原生Python调用传递过来的参数,是一个C的Python Tuple Object,需要转换成标准的C语言数据类型。简单的参数可以使用PyArg_ParseTuple
函数来进行解析,其中第二个参数是C语言中的类型表示符,示例中的意思是这个Python传过来的参数包含两个整型(int)数值并且分别赋值给m和n。
复杂点的参数就需要用PyTuple_GetItem
进行解析了,比如args分别包含了一个int型和一个字典类型数据就需要如下操作:
int m;
PyObject *dict = NULL;
PyObject int_m = NULL; /* int_m is a PyInt_Type */
int_m = PyTuple_GetItem(args, 0);
m = PyInt_AsLong(int_m);
dict = PyTuple_GetItem(args, 1);
此处的PyObject dict又可以使用PyDict_GetItemString(dict, key)来解析,如果这个dict的key不是字符串,可以使用PyDict_GetItem(dict, PyObjectKey)。
- 转换C语言数据为Python类型并返回
简单的C语言数据可以使用Py_BuildValue
来转换,示例中obj = Py_BuildValue("i", ret)
意思就是将ret转换成Python的int Object。
复杂的(比如返回值类型是list)可以用PyList_SetItem
来转换,如这个返回的list中第一个值是字符串第二个是dict:
PyObject *list = NULL;
PyObject *dict = NULL;
PyDict_SetItemString(dict, 'key', PyObjectValue);
PyList_SetItem(list, 0, PyObjectStringValue); /* use PyString_FromString to
convert c string to python String */
PyList_SetItem(list, 1, dict);
return list;
在ParseTuple和Py_BuildValue中可以使用的C类型
你C函数库中所有函数都需要写到这里面,如下:
static PyMethodDef CalMethods[] = {
{"add", PyCal_add, METH_VARARGS, "Add two integer values."},
{"square", PyCal_square, METH_VARARGS, "Square one integer value."},
/* ... */
{NULL, NULL, 0, NULL}
};
$ gcc -fpic -c -I /usr/include/python2.7 \
-I /usr/lib/python2.7/config-x86_64-linux-gnu calculate_module.c
$ gcc -shared -o calculate.so calculate_module.o
$ python
> import calculate
> calculate.add(3, 4)
gdb默认优化了编译选项,这会导致你调试时出现行号不对的问题,这时你需要在gcc编译时加上-O0
来禁用优化来保证调试时行号与源文件保持一致。
# gdb python
(gdb) b PyCal_add
(gdb) dir /path/to/caculate_module_dir
(gdb) run test.py
(gdb) l
(gdb) n
(gdb) n
(gdb) p xx