BernieD
BernieD

Reputation: 370

Is it possible to pass a C function pointer to a function that is unknown at compile time to a C function using Cython?

I am trying to call a C-function from Python that takes a function pointer as parameter. I need that function to be dynamically determined at runtime. Using ctypes, that is pretty simple.

The C-code could look like this:

// my_c_funcs.h
double mul(double, double);
double add(double, double);
double do_something(double (*)(double, double), double, double);
// my_c_funcs.h
int mul(int a, int b) {
    return a*b;
}
int add(int a, int b) {
    return a + b;
}
int do_something(int (*f)(int, int), int a, int b) {
    return f(a, b);
}

Having compiled that code into a shared library named "libMyCFuncs.so", I can pass a function that is determined at runtime in Python using ctypes:

# foo.py
import ctypes

lib = ctypes.cdll.LoadLibrary("./libMyCfuncs.so")

def foo(func_name, a, b):
    func = getattr(lib, func_name)
    return lib.do_something(func, a, b)

I know I should define the return types but I left that out for brevity and used ints only.

The code above gives the expected result, e.g. calling foo.foo('add', 2, 4) yields 6. However, I'd prefer using Cython because I make a lot of use of two- or higher-dimensional arrays and passing arrays is IMHO a lot easier in Cython. Let's say the Cython code is in "foo.pyx":

# foo.pyx
cdef extern from "my_c_funcs.h":
    int mul(int, int)
    int add(int, int)
    int do_something(int (*)(int, int), int, int)

def foo(func_name, int a, int b):
    # ???

Calling getattr or even eval obviously doesn't work. So how can I realize that in Cython?

Upvotes: 4

Views: 236

Answers (1)

cvanelteren
cvanelteren

Reputation: 1703

You have to provide a wrapper from cython that can be called in your shared object. Cython essentially has three "modes"

  1. def : normal python functions
  2. cpdef: python callable function with possible c/cpp internal variables
  3. cdef: pure c/cpp function

As an example for your code a simple binding would be


cdef extern from "c_funcs.h":
    double mul(double, double)
    double add (double, double)
    double do_something(double(*)(double, double), double, double)

cdef extern from "c_funcs.c":
    pass

# this is callable from python
cpdef double py_do_something_mul(str name, double x,  double y):
    return do_something(mul, x, y)

If you want something like dynamic dispatching, you would have to provide some wrapping to this as well. It won't work with the default python dicts, but an ordered or unorered_map would work for this.

Upvotes: 2

Related Questions