3

I am no stranger to the python ctypes module, but this is my first attempt at combining C++, C and Python all in one code. My problem seems to be very similar to Seg fault when using ctypes with Python and C++, however I could not seem to solve the problem in the same way.

I have a simple C++ file called Header.cpp:

#include <iostream>

class Foo{
   public:
      int nbits;
      Foo(int nb){nbits = nb;}
      void bar(){ std::cout << nbits << std::endl; }
};

extern "C" {
   Foo *Foo_new(int nbits){ return new Foo(nbits); }
   void Foo_bar(Foo *foo){ foo->bar(); }
}

which I compile to a shared library using:

g++ -c Header.cpp -fPIC -o Header.o
g++ -shared -fPIC -o libHeader.so  Header.o

and a simple Python wrapper called test.py:

import ctypes as C
lib = C.CDLL('./libHeader.so')

class Foo(object):
    def __init__(self,nbits):
        self.nbits = C.c_int(nbits)
        self.obj = lib.Foo_new(self.nbits)

    def bar(self):
        lib.Foo_bar(self.obj)

def main():
    f = Foo(32)
    f.bar()

if __name__ == "__main__":
    main()

I would expect that when I call test.py, I should get the number 32 printed to screen. However, all I get is a segmentation fault. If I change the constructor to return the class instance on the stack (i.e. without the new call) and then pass around the object, the program performs as expected. Also, if I change the bar method in the Foo class such that it does not use the nbits member, the program does not seg fault.

I have an limited understanding of C++, but the fact that I can make this function as expected in C and in C++ but not in Python is a little confusing. Any help would be greatly appreciated.

Update: Thanks to one of the comments below, the problem has been solved. In this case, an explicit declaration of both restype and argtypes for the C functions was required. i.e the following was added to the python code:

lib.Foo_new.restype  = C.c_void_p
lib.Foo_new.argtypes = [C.c_int32]
lib.Foo_bar.restype  = None
lib.Foo_bar.argtypes = [C.c_void_p]
ebarr
  • 7,704
  • 1
  • 29
  • 40
  • Works for me. Also possible Erratum: I assume that you compiled Header.cpp with the -fPIC switch? – udoprog Dec 06 '12 at 23:14
  • Works here, too. Please provide a gdb backtrace of the segfault. – Bas Wijnen Dec 06 '12 at 23:17
  • 3
    Works for me too. You are passing around the pointer to Foo as C int, which might fail. Try to specify the function argtypes and restype, e.g. `lib.Foo_new.restype = C.c_void_p;lib.Foo_new.argtypes = [C.c_int32];lib.Foo_bar.restype = None;lib.Foo_bar.argtypes = [C.c_void_p]` – cgohlke Dec 07 '12 at 01:02
  • @cgohlke: Hit the nail on the head there, problem solved. Thanks. – ebarr Dec 07 '12 at 10:16
  • Normally passing ints and pointers doesn't need the types declared, but it may be related to passing a c_int(32) to Foo_new instead of just a Python integer. – Mark Tolonen Dec 07 '12 at 17:33
  • Any argument or return type other than C int should be declared. Otherwise stack or memory corruption can occur depending on platform and chance. – cgohlke Dec 07 '12 at 18:05

1 Answers1

0

I would try the following:

extern "C"
{
    Foo *Foo_new(int nbits)
    {
        Foo *foo = new Foo(nbits);
        printf("Foo_new(%d) => foo=%p\n", nbits, foo);
        return foo;
    }
    void Foo_bar(Foo *foo)
    {
        printf("Foo_bar => foo=%p\n", foo);
        foo->bar();
    }
 }

to see if the values of foo match.

Also, you might want to look at Boost.Python to simplify creating Python bindings of C++ objects.

reece
  • 7,945
  • 1
  • 26
  • 28
  • 1
    In this case the amount of code to be wrapped is very small, therefore I think using Boost.Python would be overkill and add unnecessary dependencies. – ebarr Dec 07 '12 at 10:21