How to pass python function as an argument to c++ function using Cython
Asked Answered
W

1

6

Here are my settings: I have next c++ class which I want to wrap:

// Foo.h
class Foo
{
public:
  typedef int MyType;
  typedef int ArgType1;
  typedef int ArgType2;
  ...
  typedef MyType (*FooFunction) (ArgType1 a, ArgType2 b);
  ...
  void setFooFunction(FooFunction f);

Example of using this class in c++:

#include "Foo.h"
...
int fooFun(int a, int b)
{
  if (a > b) return a;
  else return b;
}
...
int main(int argc, char **argv)
{
  ...
  fooObj->setFooFunction(&fooFun);
  ...
}  

Cython wrapper:

 # .pyx
 cdef extern from "Foo.h":
   cdef cppclass Foo:
     void setFooFunction(int *) except +

 def bar(fooFun):
   ...
   fooobj.setFooFunction(fooFun)
   ...

And I want to be able to do this:

 # python file
 ...
 def pyfun(x, y):
   return x + y
 ...
 def main():
   bar(pyfun)

I'm not familiar with Cython completely, but I've already tried to do some magic and it doesn't work:

 # .pyx
 cdef extern from "Foo.h":
   cdef cppclass Foo:
     void setFooFunction(int *) except +

 ctypedef int (*myFun) (int, int)

 def bar(fooFun):
   cdef myFun funpointer
   funpointer = (<myFun*><size_t>id(smoothfun))[0]
   ...
   fooobj.setFooFunction(<int*>fooFun)
   ...

Is it even possible to do such things?

Wolfy answered 25/5, 2015 at 12:2 Comment(1)
If you managed to do this, could you share the result here? Thanks.Voight
B
3

You can't easily: a C++ function pointer just stores the location in memory where the code for that function begins (or something similar, implementation specific) while a Python function is a full Python object with a dictionary storing the bytecode, (possibly the uncompiled Python code), documentation strings and a few other bits. It also isn't in a form that the machine can run on its own - it requires a Python interpretter to process the bytecode. There really isn't a way to store all that in a C++ function pointer.

What you might be able to do is use the C++11 std::function. This can be used like a function pointer, and can take any callable object (anything definiting operator()). The idea would be that your class stores a std::function instead of a function pointer.

#include <functional> // for std::function

class Foo {
  private:
    std::function<int(int,int)> stored_function;

  public:
    void setFooFunction(std::function<int(int,int)> f) {
      stored_function = f;
    }

    void doSomething() {
      // call it like this
      int result = stored_function(1,2);
    }
};

You then pass setFooFunction a C++ class that stores a PyObject* (of the Python function), and defines operator() to do the call to Python.

If you don't want to write the C++ class yourself, the boost::python::object class (http://www.boost.org/doc/libs/1_58_0/libs/python/doc/v2/object.html#object_operators-spec) has the features you need. You can easily get a PyObject* from Cython

from cpython.ref cimport PyObject
cdef PyObject* pyob_ptr = <PyObject*>some_function

and converting that to the boost c++ class is reasonable simple too (http://www.boost.org/doc/libs/1_58_0/libs/python/doc/tutorial/doc/html/python/object.html#python.creating_python_object)

boost::python::object o(boost::python::handle<>(boost::python::borrowed(pyobj_ptr)));

Since o is callable, you should be able to use it directly in the std::function

Foo foo; // a foo object
foo.setFooFunction(o); // pass it the boost::python::object

(Obviously there's a lot of detail missing here, but hopefully the broad approach will be useful!)

Blanton answered 25/5, 2015 at 21:1 Comment(2)
I'm wondering how I would declare the function when I create the cdef cppclass's method I'm passing this argument to. void map(function<int(int)>) won't work, as that's essentially a syntax error, and I'm not really sure how else to declare it in the pxd file.Quent
Cython uses [ ] brackets for templates instead of < > brackets. (I think this is what you're asking?)Blanton

© 2022 - 2024 — McMap. All rights reserved.