Search code examples
pythonpython-cffi

AttributeError: free when taking ownership or freeing cdata


If I want to take ownership of a pointer that was malloced in C, the docs for the Python cffi package and this answer say to use ffi.gc with lib.free as the destructor. However, when I do this, I get AttributeError: free on the call to lib.free. Where is lib.free defined?

from tempfile import TemporaryDirectory
from weakref import WeakKeyDictionary

from cffi import FFI

common_header = """
typedef struct {
  int32_t length;
  double* values;
} my_struct;
"""

# FFI
ffi = FFI()

ffi.cdef(common_header + """
int func(my_struct*);
""")
ffi.set_source('_temp', common_header + """
int func(my_struct *input) {
  double* values = malloc(sizeof(double) * 3);
  input->length = 3;
  input->values = values;
  return 0;
}
""")

with TemporaryDirectory() as temp_dir:
    lib_path = ffi.compile(tmpdir=temp_dir)

    lib = ffi.dlopen(lib_path)

    func = lib.func

# Using the library
my_struct = ffi.new('my_struct*')
func(my_struct)

# Taking ownership of the malloced member
global_weakkey = WeakKeyDictionary()
global_weakkey[my_struct] = ffi.gc(my_struct.values, lib.free)
# AttributeError: free

Solution

  • The docs don't hammer this point, but you need to expose free as part of your lib's cdefs just like any other function in order to access it on the Python side. Put void free(void *ptr); in the call to ffi.cdef and the free function will be available via lib.free after compilation:

    from tempfile import TemporaryDirectory
    from weakref import WeakKeyDictionary
    
    from cffi import FFI
    
    common_header = """
    typedef struct {
      int32_t length;
      double* values;
    } my_struct;
    """
    
    # FFI
    ffi = FFI()
    
    ffi.cdef(common_header + """
    int func(my_struct*);
    void free(void *ptr); // <-- Key to lib.free working later
    """)
    ffi.set_source('_temp', common_header + """
    int func(my_struct *input) {
      double* values = malloc(sizeof(double) * 3);
      input->length = 3;
      input->values = values;
      return 0;
    }
    """)
    
    with TemporaryDirectory() as temp_dir:
        lib_path = ffi.compile(tmpdir=temp_dir)
    
        lib = ffi.dlopen(lib_path)
    
        func = lib.func
    
    # Using the library
    my_struct = ffi.new('my_struct*')
    func(my_struct)
    
    # Taking ownership of the malloced member
    global_weakkey = WeakKeyDictionary()
    global_weakkey[my_struct] = ffi.gc(my_struct.values, lib.free)