Python中编写C扩展的常见疑问与解答
有如下一个用 C 写的扩展模块: mytest. 我的问题是:在 PY 中如何给实参呢?
#include <stdio.h>
#include "Python.h"
char* gets_s(char s[],int n) {
int c;
char *cs;
if((cs=s)==NULL) return NULL;
while(--n>0 && (c=getc(stdin))!=EOF && c!='\n')
*cs++=c;
*cs='\0';
return (c==EOF && cs==s) ||(c=='\n' && cs==s)? NULL : s ;
}
static PyObject* mytest_get(PyObject *self, PyObject *args) {
int n;
char * s=NULL;
if(!PyArg_ParseTuple(args, "si", &s, &n))
return NULL;
return (PyObject*)Py_BuildValue("s", gets_s(s,n));
}
#python 中:
import mytest
from ctypes import *
arr = (c_byte * 10)() # c_char*10
print('input: ')
mytest.gets_s(arr,10)# TypeError: must be str, not c_byte_Array_10
//另外在C中如何写这样的导出函数:
struct demo {
char name[10];
int age;
};
void GetString(struct demo *p) {
strcpy(p->name, "My Test");
p->age = 1;
}
static PyObject* wrap_GetString(PyObject *self, PyObject *args) {
struct demo* s;
if(!PyArg_ParseTuple(args, "?", &s)) //这里的类型如何写?
Python中编写C扩展的常见疑问与解答
这么复杂的东西怎么能三言两语说清楚,楼主去啃啃文档中扩展开发相关的内容吧。
Python C扩展开发核心要点
写C扩展主要就三个关键步骤:
- 搭建基础框架
#include <Python.h>
static PyObject* example_func(PyObject* self, PyObject* args) {
// 你的C代码逻辑
return Py_BuildValue("i", 42); // 返回Python整数
}
static PyMethodDef ExampleMethods[] = {
{"example_func", example_func, METH_VARARGS, "示例函数"},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef examplemodule = {
PyModuleDef_HEAD_INIT,
"example",
NULL,
-1,
ExampleMethods
};
PyMODINIT_FUNC PyInit_example(void) {
return PyModule_Create(&examplemodule);
}
- 编译配置(setup.py)
from setuptools import setup, Extension
module = Extension(
'example',
sources=['example.c'],
include_dirs=['/usr/local/include']
)
setup(
name='example',
ext_modules=[module]
)
- 内存管理要小心
- 用
Py_INCREF()/Py_DECREF()管理引用计数 - 返回新引用时确保正确递增计数
- 错误处理用
PyErr_SetString()设置异常
常见坑点:
- 忘记处理Python/C API的返回值检查
- 引用计数没配对导致内存泄漏
- 线程安全没考虑(GIL问题)
一句话建议:先确保C代码本身正确,再处理Python接口。
用 cffi 多简单,为啥要用 CPython 的接口
第一个问题,从原理上来说, Python 中字符串是不可变的。而根据 C 模块的代码,"si"表示接收的是字符串,而 Python 中字符串是不可变的,因此不能像 C 语言中那样先创建一个长度为 10 的空字符串,然后传入函数,在函数中修改其中的内容(这只是原理,你的代码中并没有修改)。因此要么用字符列表,要么就改变思维方式。
又看了下你的代码,我觉得可以直接使用 input()函数,获取字符串对象,然后传到 C 层面中并解析给一个 const char*对象。如果仅仅想实验一些 C API 的功能,还是要注意 Python 和 C 在思维上的差别。
PS :你的 C 代码写的不错啊!看上去是有经验的。
第二个问题, Python 中所有都是对象,这个对象在 C 层面指的是 PyObject ,所以从 Python 层面传来的参数不能直接通过if(!PyArg_ParseTuple(args, "?", &s))解析给 C 层面的纯 struct 对象。
有几种解决方案:
1 、弄个中间的结构体,其中含有 PyObject_HEAD :
typedef struct {
PyObject_HEAD
xxx;
} PyXxxxObject;
然后将这个对象转成 demo 对象。要么直接把 demo 定义成 PyObject 对象。之后就是传递 O 对象了。
2 、使用 struct 模块,位于标准库中,不用担心移植性。具体参考相关文档。
3 、利用 PyDictObject 和 PyListObject 进行一些较为复杂的模拟。应该是可行的,但我没想好细节。就不给出代码了。
对了,强烈、强烈建议不要混用 ctype 和 Python C API 。

