簡體   English   中英

在C中編寫python擴展時,如何將python函數傳遞給C函數?

[英]When writing a python extension in C, how does one pass a python function in to a C function?

首先,為令人困惑的標題道歉。

我想要實現的是:假設我有一些函數foo ,它接受一個函數和一個整數作為輸入。 例如

int foo(int(*func)(), int i) {
    int n = func() + i;
    return n;
}

現在,我想將此函數包裝在python擴展模塊中。 所以我開始編寫我的界面:

#include <Python.h>

extern "C" {
    static PyObject* foo(PyObject* self, PyObject* args);
}

static PyMethodDef myMethods[] = {
    {"foo", foo, METH_VARARGS, "Runs foo"},
    {NULL, NULL, 0, NULL}
}

// Define the module
static struct PyModuleDef myModule = {
    PyModuleDef_HEAD_INIT,
    "myModule",
    "A Module",
    -1,
    myMethods
};

// Initialize the module
PyMODINIT_FUNC PyInit_BSPy(void) {
    return PyModule_Create(&myModule);
}

//Include the function
static PyObject* foo(PyObject* self, PyObject* args){
    // Declare variable/function pointer
    int(*bar)(void);
    unsigned int n;

    // Parse the input tuple
    if (!PyArg_ParseTuple(args, ..., &bar, &n)) {
        return NULL;
    }
}

現在,當需要解析輸入元組時,我感到困惑,因為我不確定如何解析它。 這個想法很簡單:我需要能夠在python中調用foo(bar(), n) 但我可以在如何實現這一點上使用一些幫助。

傳入的Python可調用對象是一個與Callable協議匹配的Python對象。 因此,為了將它傳遞給你的C函數,你必須創建另一個C函數作為代理,它匹配函數指針所需的簽名。

舉個例子:

static PyObject* foo_cb_callable;
static int py_foo_callback(void) {
    PyObject* retval;
    int result;

    // Call the python function/object saved below
    retval = PyObject_CallObject(foo_cb_callable, NULL);

    // Convert the returned object to an int if possible
    if (retval && PyInt_Check(retval))
        result = (int)PyInt_AsLong(retval);
    else
        result = -1;
    Py_XDECREF(retval);
    return result;
}

// NOTE: I renamed this to avoid conflicting with your "foo"
// function to be called externally.
static PyObject* py_foo(PyObject* self, PyObject* args) {
    unsigned int n;
    int result;

    // Parse the input tuple
    if (!PyArg_ParseTuple(args, "OI", &foo_cb_callable, &n)) {
        return NULL;
    }
    // Ensure the first parameter is a callable (e.g. function)
    if (!PyCallable_Check(foo_cb_callable)) {
        return NULL;
    }

    // Call foo with our custom wrapper
    result = foo(&py_foo_callback, n);
    return Py_BuildValue("i", result);
}

請注意,我在示例中使用了一個全局回調對象指針,因為您的原始函數指針沒有自定義用戶數據的位置。 如果向func回調添加通用用戶參數,則可以將wrap_foo_callback傳遞給它,而不是創建全局變量。 例如:

int foo(int(*func)(void*), void* user_data, int i) {
    int n = func(user_data) + i;
    return n;
}

// ...

static int py_foo_callback(void* callback) {
    PyObject* retval;
    int result;

    // Call the python function/object saved below
    retval = PyObject_CallObject((PyObject*)callback, NULL);

    // Convert the returned object to an int if possible
    if (retval && PyInt_Check(retval))
        result = (int)PyInt_AsLong(retval);
    else
        result = -1;
    Py_XDECREF(retval);
    return result;
}

static PyObject* py_foo(PyObject* self, PyObject* args) {
    PyObject* callback;
    unsigned int n;
    int result;

    // Parse the input tuple
    if (!PyArg_ParseTuple(args, "OI", &callback, &n)) {
        return NULL;
    }
    // Ensure the first parameter is a callable (e.g. function)
    if (!PyCallable_Check(callback)) {
        return NULL;
    }

    // Call foo with our custom wrapper
    result = foo(&py_foo_callback, callback, n);
    return Py_BuildValue("i", result);
}

首先,當你有一個Python“擴展方法”,用C實現,並且該函數接收Python可調用作為參數時,接下來是你如何接收參數,以及如何調用callable:

/* this code uses only C features */
static PyObject *
foo(PyObject *self, PyObject *args)
{
    PyObject *cb;    

    // Receive a single argument which can be any Python object
    // note: the object's reference count is NOT increased (but it's pinned by
    // the argument tuple).
    if (!PyArg_ParseTuple(args, "O", &cb)) {
        return 0;
    }
    // determine whether the object is in fact callable
    if (!PyCallable_Check(cb)) {
        PyErr_SetString(PyExc_TypeError, "foo: a callable is required");
        return 0;
    }
    // call it (no arguments supplied)
    // there are a whole bunch of other PyObject_Call* functions for when you want
    // to supply arguments
    PyObject *rv = PyObject_CallObject(cb, 0);
    // if calling it returned 0, must return 0 to propagate the exception
    if (!rv) return 0;
    // otherwise, discard the object returned and return None
    Py_CLEAR(rv);
    Py_RETURN_NONE;
}

使用這樣的邏輯來包裝bsp_init是指向Python可調用的指針是一個數據指針。 如果您將該指針直接傳遞給bsp_initbsp_init將嘗試將數據作為機器代碼調用,它會崩潰。 如果bsp_init通過一個數據指針傳遞給它調用的函數,你可以使用“粘合”過程解決這個問題:

/* this code also uses only C features */
struct bsp_init_glue_args {
   PyObject *cb;
   PyObject *rv;
};
static void
bsp_init_glue(void *data)
{
   struct bsp_init_glue_args *args = data;
   args->rv = PyObject_CallObject(args->cb, 0);
}

static PyObject *
foo(PyObject *self, PyObject *args)
{
    bsp_init_glue_args ba;
    if (!PyArg_ParseTuple(args, "O", &ba.cb)) {
        return 0;
    }
    if (!PyCallable_Check(ba.cb)) {
        PyErr_SetString(PyExc_TypeError, "foo: a callable is required");
        return 0;
    }
    bsp_init(bsp_init_glue, (void *)&ba, ...);
    if (ba->rv == 0) return 0;
    Py_CLEAR(ba->rv);
    Py_RETURN_NONE;
}

不幸的是, bsp_init沒有這個簽名,所以你不能這樣做。 但是另一個接口BSPLib::Classic::Init采用了std::function<void()> ,這是一個圍繞上述模式的面向對象的包裝器,所以你可以這樣做:

/* this code requires C++11 */
static PyObject *
foo(PyObject *self, PyObject *args)
{
    PyObject *cb;
    PyObject *rv = 0;
    if (!PyArg_ParseTuple(args, "O", &cb)) {
        return 0;
    }
    if (!PyCallable_Check(cb)) {
        PyErr_SetString(PyExc_TypeError, "foo: a callable is required");
        return 0;
    }

    std::function<void()> closure = [&]() {
       rv = PyObject_CallObject(cb, 0);
    };
    BSPLib::Classic::Init(closure, ...);

    if (rv == 0) return 0;
    Py_CLEAR(rv);
    Py_RETURN_NONE;
}

這里的神奇之處在於[&]() { ... }表示法,它是用於定義和創建本地類實例的語法糖,它“捕獲”變量cbrv以便花括號中的代碼,它將被編譯為一個單獨的函數,可以與foo正確通信。 這是一個名為“lambdas”的C ++ 11特性,這是一個行話術語,一直追溯到理論CS的早期階段,並由Lisp永生化。 這是一個教程 ,但我不確定它有多好,因為我已經知道了內部和外部的概念。

在普通的C中不可能這樣做,但是也不可能從普通的C調用BSPLib::Classic::Init (因為你無法在普通的C中完全定義一個std::function對象。好吧,不是沒有逆向工程C ++標准庫和ABI,無論如何)所以沒關系。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM