2

I'm trying to get a test project working that calls a C function, with a single integer parameter that needs to be passed by reference, from Python:

test.cpp:

#include <iostream>
using namespace std;

void testFn(int &val);

void testFn(int &val)
{
    cout << "val: " << val << endl;
    val = -1;
} 

caller.pyx:

cdef extern from "test.cpp":
    void testFn(int &val)

def myTest(int[:] val):
    testFn(&val[0])

def main():
    val = [0];
    myTest(val)
    print(val)

setup.caller.py:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

sourcefiles = ['caller.pyx']
ext_modules = [Extension("caller", sourcefiles)]

setup(
    name = 'test app',
    cmdclass = {'build_ext': build_ext},
    ext_modules = ext_modules
)

But when I try to build the project I get an error:

$ python setup.caller.py build_ext --inplace
Compiling caller.pyx because it changed.
[1/1] Cythonizing caller.pyx

Error compiling Cython file:
------------------------------------------------------------
...


def myTest(int[:] val):
    testFn(&val[0])
          ^
------------------------------------------------------------

caller.pyx:11:20: Cannot assign type 'int *' to 'int'

I understand that passing an integer parameter in Python won't work because such parameters are immutable.

gornvix
  • 3,154
  • 6
  • 35
  • 74

2 Answers2

2

As the error says: you're passing a pointer where it expects the plain type (C++ don't really behave like pointers - they can't be dereferenced etc.). I think the following should work:

def myTest(int[:] val):
   testFn(val[0])

Your main function will also have issues since list is not a buffer type (so cannot be used with a memoryview). Fix that by converting it to a numpy array or Python array

def main():
   val = np.array([0],dtype=np.int) # or array.array
   myTest(val)
   print(val)
DavidW
  • 29,336
  • 6
  • 55
  • 86
2

&int (needed for testFn) and int * (your &val[0]) are not quite the same, that explains the compiler error.

Even if one could change a python-integer in-place, one should not do it. A possible solution is to pass a python-integer to the wrapper-function and return the result of the wrapped function as a new python-integer:

def myTest(int val):
    testFn(val)
    return val 

Behind the scenes cython casts python-integer to a c-style integer and after the call of myTest back to a (new) python-integer. However, val is an independent (local) variable and not a reference to the original python-integer, so by changing it we don't change the original:

>>> a=6
>>> b=myTest(a)
val: 6
>>> a,b
(6,-1)
ead
  • 32,758
  • 6
  • 90
  • 153
  • 1
    I don't think that'll work - Python integers should be immutable (so if it does work it might break Python since it treats small integers as singletons). I'm not able to test it (or my own answer...) currently though, so I could be wrong – DavidW Oct 31 '17 at 17:52
  • 1
    @DavidW I guess I was not clear enough, my solution doesn't try to change the python-integer in-place, but returns a new integer - the is no danger of changing the singleton-integers. This solution does not provide, what OP wanted, but putting a python-integer into `array.array` in order to be able to use the buffer-protocol and to replace it in-place does not sound compelling to me. – ead Nov 05 '17 at 19:47