[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