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扩展的常见疑问与解答

4 回复

这么复杂的东西怎么能三言两语说清楚,楼主去啃啃文档中扩展开发相关的内容吧。


Python C扩展开发核心要点

写C扩展主要就三个关键步骤:

  1. 搭建基础框架
#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);
}
  1. 编译配置(setup.py
from setuptools import setup, Extension

module = Extension(
    'example',
    sources=['example.c'],
    include_dirs=['/usr/local/include']
)

setup(
    name='example',
    ext_modules=[module]
)
  1. 内存管理要小心
  • 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, "?", &amp;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 。

回到顶部