1

I try to write Python wrapper for C library using ctypes. So far I have:

C.h


    typedef struct
    {
        int erorrCode;
        char * Key;

    } A;

    #ifdef __cplusplus
    extern "C" {
    #endif

    EXPORT void __stdcall DestroyA(A &input);

    #ifdef __cplusplus
    }
    #endif

C.cpp


    EXPORT void __stdcall DestroyA(A &input)
    {
        delete []input.Key;
    }

Python.py


    import sys
    import ctypes

    class A(ctypes.Structure):
        _fields_ = [
            ("erorrCode", ctypes.c_int),
            ("Key", ctypes.c_char_p)]


    try:
      libapi = ctypes.cdll.LoadLibrary('./lib.so')
    except OSError:
      print("Unable to load RAPI library")
      sys.exit()

    DestroyA = libapi.DestroyA
    libapi.DestroyA.argtypes = [ctypes.POINTER(A)]
    libapi.DestroyA.restype = None

    a = A(1,b'random_string')

    DestroyA(ctypes.byref(a)) #!!!here is segmentation fault 

So, how can I fix the segmentation fault error?

Note: I cannot change the code on the C ++ side as long as there is a way to fix it on the Python side.

  • 1
    Why are your functions *\_\_stdcall*? – CristiFati Mar 25 '20 at 08:19
  • 1
    Are you sure that you need to call ```DestroyA```? Because you are allocating a ```c_char_p``` in Python here ```a = A(1,b'random_string')```. Thus Python should handle the memory for its allocated objects. This means your library function does not manage the address it should "delete" and gives you a segmentation fault. – Johnny_xy Mar 25 '20 at 08:21
  • 1
    Another thing is that you are trying to call a function that is flagged with ```__stdcall```. But your library is loaded as CDLL ```ctypes.cdll.LoadLibrary``` which means dll exports should have ```__cdecl``` (see here https://docs.python.org/3/library/ctypes.html#loading-dynamic-link-libraries). – Johnny_xy Mar 25 '20 at 08:31

1 Answers1

1

Listing [Python.Docs]: ctypes - A foreign function library for Python.

You have here Undefined Behavior (UB).

Python has builtin memory management for its objects, including CTypes ones.
So, every time an object (PyObject which is basically anything - including a Python int), Python invokes one of the malloc functions family under the hood in order to allocate memory. Conversely, when the object is destroyed (manually or by GC), free is called.

What happened:

  1. You created the object (behind the scenes, Python allocated some memory)
  2. You called free on the object allocated by Python (which is wrong, not to mention that you also crossed the .dll boundary)

You need to call free only on pointers that you allocated. One such example: [SO]: python: ctypes, read POINTER(c_char) in python (@CristiFati's answer).

If you want to get rid of the object (and thus free the memory that it uses), let Python do it for you:

del a

Additional remarks:

  • You're using __stdcall functions with ctypes.CDLL. Again, that's UB (on 32bit). Use the "regular" calling convention (__cdecl)

  • You're passing a reference. That's C++ specific (although it's only a const ptr). To be C compatible, use:

    EXPORT void destroyA(A *pInput);
    
CristiFati
  • 38,250
  • 9
  • 50
  • 87