5

I'm trying to call a C++ function from my Python code, if I pass a Boolean or an int it works perfectly, but if I send a string, it only prints the first character.
I am compiling with:

g++ -c -fPIC foo.cpp -Wextra -Wall -o foo.o
g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
python3 fooWrapper.py

Here is the C++ and Python code:

Python:

from ctypes import cdll
lib = cdll.LoadLibrary("./libfoo.so")
lib.Foo_bar("hello")

c++:

#include <iostream>
#include <string>
#include <unistd.h>

void bar(char* string){
    printf("%s", string);
}

extern "C" {
    void Foo_bar(char* aString){
        bar(aString);
    }
}

I'm aware of the Boost Library, but i couldn't manage to download it, and this way works well excepts for strings. Thank you for your help

Chronoxx
  • 107
  • 1
  • 8
  • 2
    Tell me, what does `%i` do? – Jonathon Reinhart Oct 21 '18 at 14:01
  • Also, why are you claiming this is C++ when you're essentially writing C? – Jonathon Reinhart Oct 21 '18 at 14:05
  • 1
    Just changed to `%s` , was an old try with ints. – Chronoxx Oct 21 '18 at 14:05
  • Because this is only a small part of the code, it's a test, but i saw I had to declare it as `extern "C"` to be able to cal it, but I can use any c++ in the `bar` function – Chronoxx Oct 21 '18 at 14:07
  • You should consider filing a documentation bug against Python. [\[Python 3\] Extending Python with C or C++](https://docs.python.org/3/extending/extending.html) in their manual does not appear treat the subject. `wchar_t` is shown in one place but not explained. `wchar_t` and wide chars are not explained. – jww Oct 21 '18 at 14:29
  • @jww that's not the place. I've added that into my code – Antti Haapala -- Слава Україні Oct 21 '18 at 16:16
  • **To those voting to close:** This is not a typo, it was a lack of understanding what's needed for calling C++ functions from Python. Even if you consider it a typo, it is not one that was "resolved in a manner unlikely to help future readers". The accepted answer is quite likely to help future visitors with a similar problem. While there could in all likelihood be a duplicate for this question (I haven't looked), it should not be closed as "typo: unlikely to help future readers". – Makyen Nov 04 '18 at 00:53

2 Answers2

5

The problem is that strings are passed as pointers to wchar_t wide characters in Python 3. And in little-endian system your string can be coded in binary as

"h\0\0\0e\0\0\0l\0\0\0l\0\0\0o\0\0\0\0\0\0\0"

Which, when printed with %s will stop at the first null terminator.


For UTF-8-encoded byte strings (char *) you need a bytes object. For example:

lib.Foo_bar("hello".encode())

or use bytes literals:

lib.Foo_bar(b"hello")

Even better if you had specified the correct argument types:

from ctypes import cdll, c_char_p
foo_bar = cdll.LoadLibrary("./libfoo.so").Foo_bar
foo_bar.argtypes = [c_char_p]
foo_bar(b"hello\n")
foo_bar("hello\n")

when run will output the following:

hello
Traceback (most recent call last):
  File "foo.py", line 5, in <module>
    foo_bar("hello\n")
ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type

i.e. the latter call that uses a string instead of bytes would throw.

1

You may also process Python3 strings in C++ directly using the wchar_t type. In that case, you need to do any necessary conversions in C++ like this:

#include <iostream>
#include <locale>
#include <codecvt>

void bar(wchar_t const* aString)
{
    // Kudos: https://stackoverflow.com/a/18374698
    std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> convert;

    std::cout << convert.to_bytes(aString) << std::endl;
}

extern "C" {
    void Foo_bar(wchar_t const* aString)
    {
        bar(aString);
    }
}

You will lose Python2 compatibility, however.

Adrian W
  • 4,563
  • 11
  • 38
  • 52