Search code examples
pythoncmemory-managementctypescalloc

Free calloc in shared library using ctypes


I have this struct:

struct result {
    int position;
    int *taken;
};

struct result get_result(int pos, int take[]){
    struct result res;
    res.position = pos;
    res.taken = take;
    return res;
}

Which I call in a function:

struct result get_taken_values(){
    //some code
    int position = 0;
    int* chosen = calloc(9, sizeof(int));     
    //some more code
    struct result res;
    res.position = position;
    res.taken = chosen;
    return res;
}

If I were to use this in main, I would simply call free(res.taken) in the end.

But I'm making this into a shared library using:

gcc -fPIC -c get_taken_values.c
ld -shared -soname libtest.so.1 -o my_library.so -lc get_taken_values.o

I'll be using this library in Python using ctypes.

Do I need to free the calloc, if so how do I do it?


Solution

  • Assuming you need a variable sized array for taken, then you can create a ctypes.Structure, give it a __del__ method that will call free. You can call free by loading libc. __del__ is called after the last reference to an object falls out of scope. Using __del__ can cause problems if your objects contain circular references as python will not know which objects to call __del__ on first (so it doesn't, it just keeps the objects hanging around in memory).

    from ctypes import Structure, c_int, POINTER, CDLL
    from ctypes.util import find_library
    
    __all__ = ["get_taken_values", "Result"]
    
    libc = CDLL(find_library("c"))
    libmy_library = CDLL("./my_library.so")
    
    class Result(Structure):
        _fields_ = [
            ("position", c_int),
            ("taken", POINTER(c_int)),
            ("size", c_int)
        ]
    
        def __str__(self):
            return "result(position={}, taken={})".format(
                self.position, self.taken[:self.size])
    
        def __del__(self):
            libc.free(self.taken)
    
    get_taken_values = libmy_library.get_taken_values
    get_taken_values.argtypes = ()
    get_taken_values.restype = Result
    
    if __name__ == "__main__":
        result = get_taken_values()
        print("position is {} and value at position is {}".format(
            result.position, result.taken[result.position]))
        print(result)
    

    This makes the assumption that the instance of the python Result is the owner of the memory. Also, if taken really is variable sized then you will need to include the size member in your struct that Result references. If taken is a fixed size then you can just declare taken as an array in the struct, forget about using free, and use c_int * 9 rather than POINTER(c_int) when declaring the type of taken in python.