CFFI (C Foreign Function Interface) is a mechanism that allows programs written in high‑level languages (such as Python, Lua, or Common Lisp) to call functions and use data types defined in C libraries without requiring a compiler or a manual wrapper in C. It provides a direct binding to shared libraries (e.g., .so, .dll, .dylib) at runtime, enabling performance‑critical code reuse and system‑level programming while maintaining the convenience of the host language. In the Python ecosystem, the cffi library is a popular alternative to ctypes, offering both “API mode” (compiled with a C compiler) and “ABI mode” (interpreted without compilation) for flexibility.

1 History and motivation

1.1 Predecessors: libffi and ctypes

The concept of a foreign function interface (FFI) for C dates back to the need for high‑level languages to reuse C‑based system libraries without rewriting them. The libffi library (Foreign Function Interface) provides a portable, high‑level API for calling C functions dynamically. It handles the low‑level details of argument passing and register usage according to the platform’s calling convention. In Python, the built‑in ctypes module, introduced in Python 2.5, uses libffi at its core to enable runtime C binding without a compiler. However, ctypes often suffers from verbose syntax and limited support for complex C constructs (e.g., callbacks, struct members).

1.2 Development of CFFI for Python

1.2.1 Origin in PyPy project (2007–2012)

CFFI was originally developed as part of the PyPy project (a JIT‑compiled Python interpreter). The PyPy team needed a convenient way to interface with C libraries that could leverage the interpreter’s JIT for performance. The first version of cffi was released in 2012, offering a simpler and more Pythonic syntax than ctypes, while also allowing both interpreted (ABI) and compiled (API) modes.

1.2.2 Standalone release and adoption

After its initial success in PyPy, the cffi library was made available as a standalone package for CPython (the reference Python implementation) as well. It gained rapid adoption due to its clean interface, support for automatic struct layout, and the ability to generate C extension modules from C header declarations. By 2015, cffi had become a standard dependency for many Python projects, including cryptography and Pillow.

1.3 Use in other languages (e.g., LuaJIT FFI)

The same FFI pattern inspired other language implementations. LuaJIT, a just‑in‑time compiler for Lua, includes a built‑in FFI module that allows direct calls to C functions and use of C data types. LuaJIT’s FFI parser reads C declarations at runtime, and the JIT compiler can inline the C calls for high performance. Similarly, the Common Lisp community has the cl‑cffi library (often called CFFI), which provides a portable FFI across multiple Common Lisp implementations.

2 Technical overview

2.1 How CFFI works at a high level

2.1.1 Foreign Function Interface (FFI) concept

An FFI allows a program written in one language to call functions written in another language. In the case of CFFI, the host language (e.g., Python) describes the signatures of C functions—their return types and parameter types—using a declarative syntax (often a string containing a C‑like prototype). The FFI layer then marshals arguments and results between the host language’s runtime and the C library’s calling convention.

2.1.2 Role of libffi library

Most CFFI implementations rely on libffi to perform the actual dynamic call. libffi abstracts away platform‑specific details: it packs arguments into the appropriate registers or stack slots, executes the call, and retrieves the return value. This allows the FFI to work on any architecture that libffi supports, without requiring the host language to know the ABI.

2.2 CFFI in Python

2.2.1 ABI mode (interpreted, no compilation)

In ABI mode, the user provides C function signatures as strings. The cffi library parses these strings and uses libffi to call the functions directly from shared libraries. No C compiler is required. This mode is convenient for quick experiments or when the C library’s exact layout is known. However, it can be slower than the compiled mode because argument marshaling is done at runtime and type checking is limited.

2.2.2 API mode (requires C compiler, generates C extension)

In API mode, cffi reads a C header file or a set of C declarations and generates a C source file that wraps the library’s functions. This C source is compiled into a Python extension module (a .so or .pyd file) using a C compiler. The resulting module runs at nearly the speed of a hand‑written C extension, because all type information is fixed at compile time.

2.2.2.1 Out‑of‑line API vs. inline API
  • Out‑of‑line API: The CFFI declarations are stored in a separate ffi_build.py script. Running that script generates a standalone extension module. This is the recommended approach for production use.
  • Inline API: Declarations are placed directly in the Python source code. The C source is generated and compiled on the fly when the module is imported. This is simpler for small projects but leads to longer import times.

2.2.3 Data types and pointer handling

2.2.3.1 cffi.CData and cffi.CType

All C values (integers, pointers, structs, arrays) are represented in Python as objects of type cffi.CData. The corresponding type objects are cffi.CType. Arithmetic on CData objects is handled by the FFI layer: for example, adding an integer to a pointer performs pointer arithmetic. This allows idiomatic C operations to be expressed in Python.

2.2.3.2 Struct, union, and array emulation

CFFI can represent structs, unions, and arrays as CData objects. Fields can be accessed using Python attribute syntax (e.g., p.x). Arrays can be indexed and sliced. For nested structs, CFFI automatically handles alignment and padding based on the platform’s ABI.

2.2.4 Callbacks from C to Python

CFFI supports registering Python functions as callbacks that can be called from C code. The user defines a callback type (e.g., int (*)(int)) and then wraps a Python function with ffi.callback(ctype). The C library receives a function pointer that, when invoked, calls back into Python. This requires careful management of the Python GIL (Global Interpreter Lock) to avoid deadlocks.

2.3 Performance characteristics

2.3.1 Compared to ctypes

In general, cffi tends to be faster than ctypes in both ABI and API modes. The API mode, being compiled, has the smallest overhead—often within 5–10% of a pure C extension. The ABI mode is slower than API mode but still often outperforms ctypes due to more efficient argument marshaling and reduced Python‑level object creation.

2.3.2 Compared to C extensions

Hand‑written C extensions (using the Python C API) can be slightly faster than cffi API mode, because they avoid the CFFI wrapper layer. However, the difference is usually marginal for most use cases. CFFI’s advantage lies in its ease of development: it eliminates the need to write boilerplate C code and manage reference counting manually.

3 Implementations and bindings

3.1 Python: cffi module

3.1.1 Versions and compatibility (Python 2.7, 3.x, PyPy)

The cffi library is available on PyPI. It supports CPython 2.7, CPython 3.x (3.5 through 3.13 as of 2025), and PyPy (which uses its own JIT‑aware FFI). The library is maintained under the Python Software Foundation umbrella. Version 1.0 (released 2014) introduced the current distinction between ABI and API modes; later versions added improved struct alignment and Windows support.

3.1.2 Typical workflow: parsing header files, building objects

A common workflow using the API out‑of‑line mode involves:

  1. Creating a build script (e.g., build_my_lib.py) that reads C header files or inline declarations.
  2. Calling ffibuilder.cdef() to declare the C types and functions.
  3. Calling ffibuilder.set_source() to specify the shared library to link against.
  4. Running the build script (usually via python build_my_lib.py build).
  5. Importing the resulting extension module in application code.

3.2 Lua: LuaJIT FFI

3.2.1 Syntax and integration

LuaJIT’s FFI is built into the interpreter (no external package). The user writes C declarations as Lua strings and then calls ffi.load("library") to open a shared library. Functions are called directly on the returned object. For example:

local ffi = require("ffi")
ffi.cdef("int printf(const char *fmt, ...);")
ffi.C.printf("Hello %s\n", "world")

LuaJIT can JIT‑compile the FFI calls, making them extremely fast—often as fast as native C code.

3.3 Common Lisp: CFFI library (cl‑cffi)

3.3.1 Differences from Python CFFI

The Common Lisp library also called CFFI (or cl‑cffi) is a portable FFI that works across multiple Lisp implementations (SBCL, CCL, ECL, etc.). Unlike Python’s cffi, it does not rely on libffi; instead it uses the host Lisp’s native FFI capabilities. It provides macros like defcfun (define C function) and defcstruct (define C struct). The syntax is Lisp‑centric (S‑expressions) rather than C‑like declarations. While the name is the same, the two libraries are unrelated.

4 Use cases and applications

4.1 Wrapping existing C libraries (e.g., OpenGL, libcurl)

CFFI is widely used to create Python bindings for C libraries that lack official Python wrappers. Projects like cryptography (wrapping OpenSSL), Pillow (wrapping libjpeg, libpng), PyOpenGL (though some parts use ctypes), and curl‑cffi (wrapping libcurl) rely on CFFI for performance and correctness.

4.1.1 Example: using cffi to call a simple math function

from cffi import FFI
ffi = FFI()
ffi.cdef("double sin(double x);")
lib = ffi.dlopen("libm.so.6")   # on Linux
result = lib.sin(1.0)
print(result)

4.2 High‑performance computing and system programming

Because CFFI can call C functions with minimal overhead, it is used in scientific computing (e.g., numpy uses its own C API, but CFFI can wrap Fortran/C numerical libraries). System programming tasks such as accessing ioctl calls, raw device interfaces, or kernel‑level data structures are also common with CFFI.

4.3 Integration with embedded systems and hardware interfaces

On embedded platforms (Raspberry Pi, BeagleBone, etc.), CFFI can be used to interface with C‑based hardware libraries (e.g., WiringPi, libgpiod). The ability to run without a compiler in ABI mode is especially useful on resource‑constrained systems.

5 Comparison with alternatives

5.1 ctypes (Python)

ctypes is part of the Python standard library and also uses libffi. It provides a more object‑oriented API but has a steeper learning curve for complex types (e.g., callbacks, nested structs). ctypes tends to be slower than cffi due to more Python‑level overhead. CFFI’s API mode (compiled) is not available in ctypes; ctypes always works in an interpreted ABI mode.

5.2 Cython / C++ extension modules

Cython is a superset of Python that compiles to C extensions. It allows direct inclusion of C/C++ headers and functions, offering performance comparable to hand‑written C. However, Cython requires learning a new syntax and a separate compilation step. CFFI is simpler for quick wrapping (especially ABI mode) but may be slower than optimized Cython code.

5.3 SWIG

SWIG (Simplified Wrapper and Interface Generator) generates bindings for multiple languages from a single interface file. It is more complex to set up than CFFI but can produce bindings for languages beyond Python (e.g., Java, Ruby). CFFI is Python‑specific (or language‑specific for Lua/CL) and is easier to use for small to medium projects.

5.4 CGO (Go)

CGO is Go’s mechanism to call C functions. It compiles both Go and C code together and handles marshaling. Unlike CFFI, CGO requires the C compiler and cross‑compilation for target architectures. CGO is more tightly integrated with Go’s build system, but it does not offer an interpreted (no‑compiler) mode as CFFI does.

6 Limitations and considerations

6.1 Platform dependence

CFFI bindings depend on the availability of the target shared library and its ABI. Compiled API‑mode extensions must be built for each platform (e.g., Linux x86_64, Windows amd64). ABI mode can be more portable if the library’s functions are correctly declared, but the user must ensure that the C type declarations match the library’s actual binary interface.

6.2 Memory management and ownership

CFFI does not automatically manage memory allocated by C code. The user is responsible for calling ffi.free(), ffi.new(), or relying on the original C library’s free functions. Structs returned by value are copied, but pointers to heap‑allocated memory must be freed explicitly to avoid leaks.

6.3 Thread safety and GIL interaction

When calling C functions from Python, the GIL is typically released by CFFI for blocking calls (if the user specifies ffi.blocking). Callbacks from C into Python re‑acquire the GIL. If both sides are multithreaded, deadlocks can occur if the GIL is not properly managed. CFFI does not provide built‑in thread safety beyond the underlying library’s guarantees.

6.4 Debugging and error handling

Errors in C code (e.g., segmentation faults, invalid pointers) can crash the Python process without a clear traceback. CFFI’s ABI mode offers less type safety; a mismatched declaration can cause subtle bugs. Debugging typically requires using a C debugger (GDB) or enabling CFFI’s verbose mode to catch type errors early.

7 Community and ecosystem

7.1 Documentation and tutorials

The official documentation for Python cffi is hosted at https://cffi.readthedocs.io/ . It includes a tutorial, API reference, and migration guides from ctypes. LuaJIT FFI is documented in the LuaJIT wiki. Common Lisp CFFI has a comprehensive manual at https://common-lisp.net/project/cffi/ .

7.2 Notable projects using CFFI (e.g., cryptography, Pillow)

  • cryptography – The most widely used Python crypto library; it uses CFFI API mode to wrap OpenSSL.
  • Pillow – The popular Python imaging library uses CFFI to bind to libjpeg‑turbo, libpng, etc.
  • cffi‑libs – Many smaller projects (e.g., SQLAlchemy drivers, h5py for HDF5) use CFFI to wrap C libraries.
  • CFFI in the Standard Library – Python’s ssl module uses a hand‑written C extension, but some newer libraries (e.g., hashlib with OpenSSL 3) have considered CFFI.

The Python cffi library is actively maintained (as of 2025) with releases supporting recent Python versions. The trend in the Python ecosystem is toward built‑in tools (like Python 3.13’s importlib changes) that may reduce reliance on dynamic C extensions, but CFFI remains relevant for projects that need stable, high‑performance C bindings. In Lua, LuaJIT’s FFI is a flagship feature; in Common Lisp, CFFI continues to evolve with new platforms. The FFI concept itself is increasingly integrated into language runtimes (e.g., Java’s Project Panama, Ruby’s fiddle), indicating a lasting role for dynamic C interop.