5

I am trying to understand how to pass string values between Python3 and cythonized C++ function. However I am unable to build the library with Cython.

In particular, I didn't understand how to declare the string return value and string parameter in the source.pyx. With the int type it works correctly.

The error I get during build with clang is the following:

candidate function not viable: no known conversion from 'PyObject *' (aka '_object *') to 'char *' for 1st argument

My source.pyx is the following:

cdef extern from "source.cpp":
  cdef str fun(str param)

def pyfun(mystring):
  return fun(mystring)

My source.cpp is:

char * fun(char *string) {
  return string;
}
gc5
  • 9,468
  • 24
  • 90
  • 151
  • 1
    Do you understand this part of the documenatation? http://cython.readthedocs.io/en/latest/src/tutorial/strings.html#c-strings. I'm still working on it. – hpaulj Mar 08 '17 at 18:25
  • Thanks. Actually, I managed to pass the string following this table http://cython.readthedocs.io/en/latest/src/userguide/language_basics.html#automatic-type-conversions . Is the use of `libcpp.string` and `std::string`, that I think you are proposing, better than passing `bytes` to `char*` and then converting the return value (with type `bytes`) to `str`? – gc5 Mar 08 '17 at 18:45
  • The documentation page that I linked seems to have a low opinion of `C` char strings, especially since Py3 uses `unicode`. But what's best may depend on what you are doing with the strings in `C++`. – hpaulj Mar 08 '17 at 19:19
  • The fact is I am interfacing an old legacy code and I have still little experience with cython. If you may put the more suited way to do it with `libcpp.string` as an answer I can accept it. – gc5 Mar 08 '17 at 19:26

1 Answers1

5

Aside from mistakes in the original code, I managed to make it work with the following source.pyx (translation between bytes type in Python3 and char* in C++):

cdef extern from "source.cpp":
  cdef char* fun(char* param)

def pyfun(mystring):
  mystring_b = mystring.encode('utf-8')
  rvalue = fun(mystring_b).decode('utf-8')
  return rvalue

If the memory is allocated using malloc within fun it will also need to be freed, otherwise there will be a memory leak (when working with C pointers it's always worth considering who owns the memory). A modified version that does this is:

from libc.stdlib cimport free

# cdef extern as before

def pyfun(mystring):
    cdef char* value_from_fun
    mystring_b = mystring.encode('utf-8')
    value_from_fun = fun(mystring_b)
    try:    
        return value_from_fun.decode('utf-8')
    finally:
        free(value_from_fun)

The type conversions act in the same way as before.


Edit

As per hpaulj's comment in the original question, here is the version with libcpp.string which maps C++ std::string defined in <string>:

from libcpp.string cimport string

cdef extern from "source.cpp":
  cdef string fun(string param)

def pyfun(mystring):
  mystring_ = mystring.encode('utf-8')
  rvalue = fun(mystring_).decode('utf-8')
  return rvalue
Community
  • 1
  • 1
gc5
  • 9,468
  • 24
  • 90
  • 151
  • Is the memory that `fun` returns allocated using `malloc`? If it is then you may also need to free it too. If not then this should be fine. – DavidW Mar 09 '17 at 09:48
  • In this particular case not, but it may be useful. How can I do it? Feel free to modify the answer accordingly. – gc5 Mar 09 '17 at 10:09
  • 1
    I've edited it how you'd do this. If you want to structure it differently then feel free to change it too – DavidW Mar 09 '17 at 15:06