如何从CPython返回函数指针到Python,并用ctypes调用C函数?

3 投票
1 回答
1353 浏览
提问于 2025-04-16 12:45

我有一个CPython的函数:

void my_cfunc(int arg)
{
  printf("Hello from C; arg=%d\n", arg);
}

PyObject *get_fptr(PyObject * /*self*/, PyObject * /*args*/)
{
    return PyCObject_FromVoidPtr(my_cfunc, NULL);
}

然后在Python中我有:

import mymodule

ptr = mymodule.get_fptr() # will return a PyCObject wrapping the C function pointer

接下来:

from ctypes import *

SOMEFUNC_T = CFUNCTYPE(c_void, c_int)
somefunc = SOMEFUNC_T(ptr) # <-- bad!

现在,如果我把get_fptr改成返回:PyLong_FromSize_t(size_t(my_cfunc)),那么'somefunc'就会有效了。

但是我不想把一个函数指针转换成size_t类型。

请给点建议。

1 个回答

1

首先,我不太明白你为什么想从Python扩展中返回一个C函数指针,然后再通过ctypes在Python中调用它。其实更合理的做法是直接通过Python扩展来调用C函数,除非我漏掉了什么。

其次,ctypes似乎根本不支持PyCObject。你调用CFUNCTYPE(None, c_int)时(我把c_void换成了None),如果传入一个PyCObject参数就会失败,因为CFUNCTYPE期望的是一个整数,而不知道该如何处理PyCObject。

为什么不直接为my_cfunc写一个Python包装器呢?这样你就可以在Python中调用它,而不用麻烦ctypes了。例如:

PyObject *call_fptr(PyObject *self, PyObject *args)
{
    int arg;
    if (!PyArg_ParseTuple(args, "i", &arg))
        return NULL;

    my_cfunc(arg);
    Py_RETURN_NONE;
}

另外,如果你喜欢使用ctypes,my_func的Python包装器可以用来实例化一个ctypes外部函数(这点和PyCObject不同)!

from ctypes import *
import foo

SOMEFUNC_T = CFUNCTYPE(None, c_int)
cfunc = SOMEFUNC_T(foo.call_fptr)
cfunc(1)

编辑: 你提到C函数应该接受可变数量的参数,这让你的问题变得更复杂了……你打算怎么用ctypes包装这个可变参数的C函数呢?因为CFUNCTYPE需要一组已知的参数。

这个问题其实归结起来就是把Python的元组转换成C的可变参数列表,这显然不是一件简单的事。实际上,SWIG在它的文档中专门有一节讨论这个问题,里面提到:大多数其他的包装生成工具明智地选择避免这个问题

不过,它确实给出了建议,可以借助libffi以一种可移植的方式动态构建可变参数列表。

最后,我的建议是用SWIG来包装你的可变参数函数,这样可以省去很多麻烦。

撰写回答