[Pyrex] Callbacks
Daniele Pianu
muogoro at gmail.com
Fri May 9 11:42:34 CEST 2008
I'm trying to wrapp a C callback system. I want to call python methods (with
python arguments) passing them as function callbacks to C function. A simple
example
###### foo.h #######
define MAX_FUNCS 4
typedef void(*VoidFunc)(void*);
typedef struct FooStruct{
VoidFunc funcs[MAX_FUNCS];
void* data[MAX_FUNCS];
} FooStruct;
FooStruct* Foo_new(void);
int Foo_call(FooStruct* self, int indexFunc);
int Foo_addFunc(FooStruct* self, VoidFunc func, void* data);
int Foo_fire(FooStruct* self);
void Foo_destroy(FooStruct* self);
###### foo.c #######
#include <stdlib.h>
#include <foo.h>
FooStruct* Foo_new(void){
int i;
FooStruct* tmp = malloc(sizeof(FooStruct));
for (i=0; i<MAX_FUNCS; i++){
tmp->funcs[i]=NULL;
tmp->data[i] = NULL;
}
return tmp;
}
int Foo_call(FooStruct* self, int indexFunc){
if (self)
if (indexFunc < MAX_FUNCS && indexFunc >= 0 ){
(*(self->funcs[indexFunc]))(self->data[indexFunc]);
return 0;
}
return 1;
}
int Foo_addFunc(FooStruct* self, VoidFunc func, void* data){
if (self){
int i;
for (i=0; i<MAX_FUNCS; i++)
if (self->funcs[i] == NULL){
self->funcs[i] = func;
self->data[i] = data;
break;
}
if (i != MAX_FUNCS)
return 0;
}
return 1;
}
int Foo_fire(FooStruct* self){
if (self){
int i;
for (i=0; i<MAX_FUNCS; i++)
if (self->funcs[i])
(*(self->funcs[i]))(self->data[i]);
return 0;
}
return 1;
}
void Foo_destroy(FooStruct* self){
if (self)
free(self);
}
FooStruct structure stores a vector of void(*)(void*) function pointers and
a vector of generic data (void pointers). The i function will be executed
with the i data (void pointer) as argument. The Foo_fire method executes all
function stored in a FooStruct instance with appropriate arguments. The
bar.pyx wraps this functions in a pyrex extension PyFooStruct
cdef extern from 'foo.h':
struct FooStruct:
pass
FooStruct* Foo_new()
int Foo_call(FooStruct* self, int indexFunc)
int Foo_addFunc(FooStruct* self, void(*func)(void*), void* data)
int Foo_fire(FooStruct* self)
void Foo_destroy(FooStruct* self)
cdef void PyCallback( object funcAndData):
cdef object func, data
func = funcAndData[0]
data = funcAndData[1]
func(*data)
cdef class PyFooStruct:
cdef FooStruct* selfPtr
def __init__(self):
self.selfPtr = Foo_new()
def PyFoo_call(self, i):
return Foo_call(self.selfPtr, i)
def PyFoo_addFunc(self, func, data):
cdef object tmpFuncAndData
tmpFuncAndData = (func, data)
return Foo_addFunc(self.selfPtr, <void(*)(void*)>PyCallback, \
<void*>tmpFuncAndData)
def PyFoo_fire(self):
return Foo_fire(self.selfPtr)
def __dealloc__(self):
Foo_destroy(self.selfPtr)
The PyCallback C function gets a tuple funcAndData as argument. The first
element is a pointer to a Python function, the second is a tuple of generic
data. PyCallback executes the function (first element of the tuple argument)
with the data arguments (second item of the tuple argument). To add a python
function reference to the PyFooStruct pyrex extension (using the
Foo_addFunction C function), the PyFoo_addFunc pass the PyCallback method as
the function pointer (casting its type to void(*)(void*)), and a tuple with
two element as generic data (casting it to void*). This tuple
(tmpFuncAndData), contains the python method reference as first argument
(func) and the python func's argument as second argument (data). The steps
through which a python function is executed by the C wrapped function are:
- PyFoo_addFunc store a callback to a func python function that use a tuple
of data arguments, passing to Foo_addFunc PyCallback as the function pointer
and a tuple (func, data) as argument (func is the python function, data the
tuple of arguments);
- when PyFoo_fire is called, all stored python function will be executed
- a generic i function is executed calling the PyCallback function with i
void-casted data.
- PyCallback gets reference to python function and function data from the
tuple argument, and call the function with the tuple argument.
I've written this simple python test:
from bar import *
def printFoo(*notUsedArgs):
print 'foo'
def printBar(*notUsedArgs):
print 'bar'
def printFooBar(*notUsedArgs):
print 'foobar'
def printSomething( *tupleOfString ):
for foo in tupleOfString:
print foo
if __name__ == '__main__':
foo = PyFooStruct()
foo.PyFoo_addFunc(printFoo, (None,))
foo.PyFoo_addFunc(printBar, (None,))
foo.PyFoo_addFunc(printFooBar, (None,))
foo.PyFoo_addFunc(printSomething, ('Foo', 'Bar', 'FooBar'))
foo.PyFoo_fire()
But when I execute the script, the interpreter raises those exceptions
python test.py
Exception exceptions.TypeError: "'tuple' object is not callable" in
'bar.PyCallback' ignored
Exception exceptions.TypeError: "'tuple' object is not callable" in
'bar.PyCallback' ignored
Exception exceptions.TypeError: "'tuple' object is not callable" in
'bar.PyCallback' ignored
Exception exceptions.TypeError: "'tuple' object is not callable" in
'bar.PyCallback' ignored
python: Objects/obmalloc.c:929: PyObject_Free: Assertion `pool->ref.count >
0' failed.
Abortito
What is the problem? Could be a problem inherent to local scope of
tmpFuncAndData
variable in PyFoo_addFunc method? I've rewritten the wrapper importing the
Py_XINCREF macro to increment the tmpFuncAndData reference count and all
seems to work
######## bar.pyx with Py_XINCREF ######
cdef extern from 'Python.h':
struct PyObject:
pass
void Py_XINCREF(object)
cdef extern from 'foo.h':
struct FooStruct:
pass
FooStruct* Foo_new()
int Foo_call(FooStruct* self, int indexFunc)
int Foo_addFunc(FooStruct* self, void(*func)(void*), void* data)
int Foo_fire(FooStruct* self)
void Foo_destroy(FooStruct* self)
cdef void PyCallback( object funcAndData):
cdef object func, data
func = funcAndData[0]
data = funcAndData[1]
func(*data)
cdef class PyFooStruct:
cdef FooStruct* selfPtr
def __init__(self):
self.selfPtr = Foo_new()
def PyFoo_call(self, i):
return Foo_call(self.selfPtr, i)
def PyFoo_addFunc(self, func, data):
cdef object tmpFuncAndData
tmpFuncAndData = (func, data)
Py_XINCREF(tmpFuncAndData)
return Foo_addFunc(self.selfPtr, <void(*)(void*)>PyCallback, \
<void*>tmpFuncAndData)
def PyFoo_fire(self):
return Foo_fire(self.selfPtr)
def __dealloc__(self):
Foo_destroy(self.selfPtr)
With the test script above, the interpreter gives me the correct output
python test.py
foo
bar
foobar
Foo
Bar
FooBar
Is there a way to solve the problem without importing anything from
Python.h?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.copyleft.no/pipermail/pyrex/attachments/20080509/74cc1efc/attachment.html
More information about the Pyrex
mailing list