CFFI — C Foreign Function Interface for Python
CFFI (C Foreign Function Interface) lets Python code call C libraries by declaring C-like function signatures and types that can often be copy-pasted directly from header files. It supports four modes: ABI/API level each with inline or out-of-line (pre-compiled) preparation. The current stable release is 2.0.0 (released September 2025), requiring Python >=3.9. It is the recommended way to interface with C on PyPy and is used as a foundation by cryptography, bcrypt, and many other major Python packages.
Warnings
- breaking In 2.0.0, reading a C '_Bool'/'bool' field whose underlying byte is not exactly 0 or 1 now raises an exception instead of returning the raw integer. Previously undefined-behavior values were silently returned.
- breaking Python 2.7, 3.6, and 3.7 support was dropped. The minimum supported version is now Python 3.9 (as of 2.0.0 PyPI metadata).
- breaking ffi.string() no longer works on bool[] arrays. It previously returned raw bytes up to the first zero byte, which was rarely meaningful.
- gotcha char* in C corresponds to bytes in Python 3, not str. Passing a Python str to any char* argument raises TypeError or produces garbage. Always encode strings explicitly: s.encode('utf-8').
- gotcha ABI mode (ffi.dlopen) is error-prone: wrong type declarations cause silent data corruption or crashes rather than compile-time errors. API mode (ffi.set_source + ffi.compile) is verified by a real C compiler.
- gotcha On Python 3.12+ any code path that calls ffi.compile() or uses cffi_modules= in setup.py requires setuptools at runtime because distutils was removed. CFFI does not declare this dependency automatically.
- gotcha ffi.dlopen(None) does not work on Python 3 on Windows. It raises OSError or returns an unusable handle.
Install
-
pip install cffi
Imports
- FFI
from cffi import FFI
- FFI (out-of-line)
from _my_compiled_module import ffi, lib
Quickstart
from cffi import FFI
ffi = FFI()
# Declare the C function signature (copy-paste from man page / header)
ffi.cdef("""
size_t strlen(const char *s);
""")
# ABI mode: open the C standard library
C = ffi.dlopen(None) # None = current process / libc on POSIX
# On Windows use ffi.dlopen('msvcrt') instead
# char* arguments must be bytes, not str
result = C.strlen(b"hello, cffi")
print(result) # 11
# Allocate a C buffer
buf = ffi.new("char[]", b"world")
print(ffi.string(buf)) # b'world'