I am trying to use PyBindGen to create a python extension module for my C library which has a callback. Even though PyBindGen frontpage says that callbacks are a missing feature, the current source code and this discussion and archive have examples of how to setup PyBindGen to pass a callback function from python into the C library.
The problem is that the example defines the callback method to have the PyObject*
as a void *
argument to carry the callback function pointer information. Since my API does not have such a context pointer, I am trying to keep the reference to the callback inside the C bindings code similar to standard python example. The problem is that I cannot figure out how to get PyBindGen to set the global PyObject * my_callback. How can I do that? Here's my code
My library header file
typedef void (*CallbackType) (int value);
void register_cb(CallbackType cb);
void set_num(int n);
My library source file
#include "c.h"
static CallbackType cb_;
void register_cb(CallbackType cb)
{
cb_ = cb;
}
void set_num(int n)
{
cb_(n);
}
My PyBindGen modulegen.py with a commented section of what needs to be added to auto-generated code for this to work
import sys
import pybindgen
from pybindgen import ReturnValue, Parameter, Module, Function, FileCodeSink
from pybindgen import CppMethod, CppConstructor, CppClass, Enum
from pybindgen.typehandlers.base import ForwardWrapperBase
class CallbackTypeParam(Parameter):
DIRECTIONS = [Parameter.DIRECTION_IN]
CTYPES = ['CallbackType']
def convert_python_to_c(self, wrapper):
assert isinstance(wrapper, ForwardWrapperBase)
py_cb = wrapper.declarations.declare_variable("PyObject*", self.name)
wrapper.parse_params.add_parameter('O', ['&'+py_cb], self.name)
wrapper.before_call.write_error_check("!PyCallable_Check(%s)" % py_cb, """PyErr_SetString(PyExc_TypeError, "CallbackType parameter must be callable");""")
#####
# NEED TO INSERT THE FOLLOWING TWO LINES INTO AUTOGENERATED OUTPUT HERE
# Py_XDECREF(my_callback); // NEEDS TO BE AUTO-GENERATED !!!
# my_callback = cb; // NEEDS TO BE AUTO-GENERATED !!!
#####
wrapper.call_params.append("_wrap_callback")
wrapper.before_call.write_code("Py_INCREF(%s);" % py_cb)
wrapper.before_call.add_cleanup_code("Py_DECREF(%s);" % py_cb)
def convert_c_to_python(self, wrapper):
raise NotImplementedError
def my_module_gen(out_file):
mod = Module('c')
mod.add_include('"c.h"')
mod.header.writeln("""
void _wrap_callback(int value);
static PyObject *my_callback = NULL;
""")
mod.body.writeln("""
void _wrap_callback(int value)
{
int arg;
PyObject *arglist;
arg = value;
printf("@@@@ Inside the binding: %d %p\\n", value, my_callback); fflush(NULL);
arglist = Py_BuildValue("(i)", arg);
PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
}
""")
mod.add_function("register_cb", None, [Parameter.new("CallbackType", "cb")])
mod.add_function("set_num", None, [Parameter.new("int", "n")])
mod.generate(FileCodeSink(out_file))
if __name__ == '__main__':
my_module_gen(sys.stdout)
My setup.py file for building the extension module
#!/usr/bin/env python
import os
from distutils.core import setup, Extension
from modulegen import my_module_gen as generate
try:
os.mkdir("build")
except OSError:
pass
os.environ["CC"] = "g++"
module_fname = os.path.join("build", "autogen-binding.c")
with open(module_fname, "wt") as file_:
print("Generating file {}".format(module_fname))
generate(file_)
mymodule = Extension('c',
sources = [module_fname, 'c.cc'],
include_dirs=['.'])
setup(name='PyBindGen-example',
version="0.0",
description='PyBindGen example',
author='xxx',
author_email='yyy@zz',
ext_modules=[mymodule],
)
My python script using the extension module
import c
def my_callback(value):
print("In Callback: " + str(value))
c.register_cb(my_callback)
c.set_num(10);
c.set_num(20);
I figured it out. In hind sight, it is obvious. I simply had to put the desired lines into a call to wrapper.before_call.write_code()
at the place where I expected the actual C lines to appear.