Embedding CPython: how do you constuct Python callables to wrap C callback pointers?

2024/9/20 18:13:46

Suppose I am embedding the CPython interpreter into a larger program, written in C. The C component of the program occasionally needs to call functions written in Python, supplying callback functions to them as arguments.

Using the CPython extending and embedding APIs, how do I construct a Python "callable" object that wraps a C pointer-to-function, so that I can pass that object to Python code and have the Python code successfully call back into the C code?

Note: this is a revised version of a question originally posted by user dhanasubbu, which I answered, but which was then deleted. I think it was actually a good question, so I have converted what I wrote into a self-answer to my own statement of the question. Alternative answers are welcome.

Answer

To define an extension type that is “callable” in the sense Python uses that term, you fill the tp_call slot of the type object, which is the C equivalent of the __call__ special method. The function that goes in that slot will be a glue routine that calls the actual C callback. Here’s code for the simplest case, when the C callback takes no arguments and returns nothing.

typedef struct {PyObject_HEAD/* Type-specific fields go here. */void (*cfun)(void);  /* or whatever parameters it actually takes */
} CallbackObj;static PyObject *Callback_call(PyObject *self, PyObject *args, PyObject *kw)
{/* check that no arguments were passed */const char no_kwargs[] = { 0 };if (!PyArg_ParseTupleAndKeywords(args, kw, "", no_kwargs))return 0;CallbackObj *cself = (CallbackObj *)self;cself->cfun();Py_RETURN_NONE;
}static PyTypeObject CallbackType = {PyVarObject_HEAD_INIT(NULL, 0).tp_name = "mymodule.Callback",.tp_doc = "Callback function passed to foo, bar, and baz.",.tp_basicsize = sizeof(CallbackObj),.tp_itemsize = 0,.tp_flags = Py_TPFLAGS_DEFAULT,.tp_new = PyType_GenericNew,.tp_call = Callback_call,
};

Instantiate the type object with PyType_Ready as usual. Don’t put it in any module visible to Python, though, because Python code can’t correctly create instances of this type. (Because of this, I haven’t bothered with a tp_init function; just make sure you always initialize ->cfun after creating instances from C, or Callback_call will crash.)

Now, suppose the actual function you need to call is named real_callback, and the Python function that you want to pass it to is named function_to_call. First you create one of the callback objects, by calling the type object, as usual, and initialize its ->cfun field:

    PyObject *args = PyTuple_New(0);CallbackObj *cb = (CallbackObj *)PyObject_CallObject((PyObject *)CallbackType, args);Py_DECREF(args);cb->cfun = real_callback;

Then you put cb into an argument tuple, and call the Python function object with that, as usual.

    args = Py_BuildValue("(O)", cb);PyObject *ret = PyObject_CallObject(function_to_call, args);Py_DECREF(args);Py_DECREF(cb);// do stuff with ret, here, perhapsPy_DECREF(ret);

Extending this to more complex cases, where the C callback needs to take arguments and/or return values and/or raise Python exceptions on error and/or receive “closure” information from the outer context, is left as an exercise.

https://en.xdnf.cn/q/72445.html

Related Q&A

python - beautifulsoup - TypeError: sequence item 0: expected string, Tag found

Im using beautifulsoup to extract images and links from a html string. It all works perfectly fine, however with some links that have a tag in the link contents it is throwing an error.Example Link:<…

Python evdev detect device unplugged

Im using the great "evdev" library to listen to a USB barcode reader input and I need to detect if the device suddenly gets unplugged/unresponsive because otherwise the python script reading …

python: urllib2 using different network interface

I have the following code:f = urllib2.urlopen(url) data = f.read() f.close()Its running on a machine with two network interfaces. Id like to specify which interface I want the code to use. Specifically…

RuntimeError: as_numpy_iterator() is not supported while tracing functions

while i was using function as_numpy_iterator() got error--------------------------------------------------------------------------- RuntimeError Traceback (most recent call…

Pandas assert_frame_equal error

Im building test cases and I want to compare 2 dataframes. Even though dataframe have the same columns and values assert_frame_equal reports are not equal. Column order is different, I tried reordering…

Multiple lines on line plot/time series with matplotlib

How do I plot multiple traces represented by a categorical variable on matplotlib or plot.ly on Python? I am trying to replicate the geom_line(aes(x=Date,y=Value,color=Group) function from R.Is there …

Python ABCs: registering vs. subclassing

(I am using python 2.7) The python documentation indicates that you can pass a mapping to the dict builtin and it will copy that mapping into the new dict:http://docs.python.org/library/stdtypes.html#…

python - ensure script is activated only once

Im writing a Python 2.7 script. In summary, this script is being run every night on Linux and activates several processes.Id like to ensure this script is not run multiple times in parallel (basically …

How to set up auto-deploy to AppEngine when pushing to Git Repository

Ive heard that other platforms support auto-deployment of their code to production when they push changes to their Git repository.Can I set up something similar to this for AppEngine? How?Im using Py…

#include zbar.h 1 error generated when running pip install zbar

Im trying to run pip install zbar and for some reason I cant seem to find an answer to solve this dependency issue. Any help would be extremely appreciated. See traceback below:Downloading/unpacking zb…