Skip to content

ctypes & CFFI

Overview

ctypes and CFFI enable calling C libraries:

  • ctypes: Built-in, low-level C interface
  • CFFI: Higher-level, more Pythonic
  • Performance: Call optimized C code directly
  • Integration: Use existing C libraries in Python

ctypes Basics

Loading C Libraries

import ctypes
import platform

# Load system library
if platform.system() == "Windows":
 libc = ctypes.CDLL("msvcrt")
elif platform.system() == "Darwin": # macOS
 libc = ctypes.CDLL("libc.dylib")
else: # Linux
 libc = ctypes.CDLL("libc.so.6")

# Call C function
result = libc.abs(-42)
print(result) # 42

Argument and Return Types

import ctypes
import math

# Load math library
libm = ctypes.CDLL("libm.so.6")

# Specify argument and return types
libm.sqrt.argtypes = [ctypes.c_double]
libm.sqrt.restype = ctypes.c_double

# Call with proper types
result = libm.sqrt(16.0)
print(result) # 4.0

Working with C Structs

import ctypes

# Define C struct
class Point(ctypes.Structure):
 _fields_ = [("x", ctypes.c_double),
 ("y", ctypes.c_double)]

# Create instance
p = Point(x=3.0, y=4.0)
print(p.x, p.y) # 3.0 4.0

# Nested struct
class Rect(ctypes.Structure):
 _fields_ = [("top_left", Point),
 ("bottom_right", Point)]

r = Rect()
r.top_left.x = 0.0
r.top_left.y = 10.0

Arrays and Pointers

import ctypes
import numpy as np

# Create array
arr = (ctypes.c_int * 5)(1, 2, 3, 4, 5)
print(arr[0]) # 1

# Pointer to array
ptr = ctypes.pointer(arr)

# NumPy integration
np_arr = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
ptr = np_arr.ctypes.data_as(ctypes.POINTER(ctypes.c_double))

CFFI: More Pythonic Interface

Basic CFFI Usage

from cffi import FFI

ffi = FFI()

# Define C interface
ffi.cdef("""
 double sqrt(double x);
 int abs(int x);
""")

# Load library
C = ffi.dlopen("libm.so.6")

# Use C functions
print(C.sqrt(16.0)) # 4.0
print(C.abs(-42)) # 42

CFFI Structs and Functions

from cffi import FFI

ffi = FFI()

# Define struct and function
ffi.cdef("""
 struct Point {
 double x;
 double y;
 };

 double distance(struct Point* p1, struct Point* p2);
""")

# Implement in Python (or use existing C library)
@ffi.callback("double(struct Point*, struct Point*)")
def distance_callback(p1, p2):
 dx = p1.x - p2.x
 dy = p1.y - p2.y
 return (dx**2 + dy**2)**0.5

# Create structs
p1 = ffi.new("struct Point*")
p1.x = 0.0
p1.y = 0.0

p2 = ffi.new("struct Point*")
p2.x = 3.0
p2.y = 4.0

# Compute distance
dist = distance_callback(p1, p2)
print(dist) # 5.0

NumPy Integration

Calling C with NumPy Arrays

import numpy as np
import ctypes

def process_array(arr):
 """Process NumPy array in C."""
 arr = np.asarray(arr, dtype=np.float64)

 # Get pointer to data
 data_ptr = arr.ctypes.data_as(ctypes.POINTER(ctypes.c_double))

 # Call C function with pointer
 # libm_c.process_float_array(data_ptr, len(arr))

 return arr

# Use it
data = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
result = process_array(data)

Real-World ML Example

Calling BLAS from ctypes

import numpy as np
import ctypes

# Load BLAS library
libblas = ctypes.CDLL("libblas.so.3")

# Define DGEMM (matrix multiply)
libblas.dgemm_.argtypes = [
 ctypes.c_char_p, # TRANSA
 ctypes.c_char_p, # TRANSB
 ctypes.POINTER(ctypes.c_int), # M
 ctypes.POINTER(ctypes.c_int), # N
 ctypes.POINTER(ctypes.c_int), # K
 ctypes.POINTER(ctypes.c_double), # ALPHA
 ctypes.c_void_p, # A
 ctypes.POINTER(ctypes.c_int), # LDA
 ctypes.c_void_p, # B
 ctypes.POINTER(ctypes.c_int), # LDB
 ctypes.POINTER(ctypes.c_double), # BETA
 ctypes.c_void_p, # C
 ctypes.POINTER(ctypes.c_int), # LDC
]

def matrix_multiply_blas(A, B):
 """Multiply matrices using BLAS."""
 A = np.asarray(A, dtype=np.float64, order='F')
 B = np.asarray(B, dtype=np.float64, order='F')

 m, k = A.shape
 k, n = B.shape

 C = np.zeros((m, n), dtype=np.float64, order='F')

 one = ctypes.c_double(1.0)
 zero = ctypes.c_double(0.0)
 m_c = ctypes.c_int(m)
 n_c = ctypes.c_int(n)
 k_c = ctypes.c_int(k)

 libblas.dgemm_(
 b'N', b'N',
 ctypes.byref(m_c),
 ctypes.byref(n_c),
 ctypes.byref(k_c),
 ctypes.byref(one),
 A.ctypes.data_as(ctypes.c_void_p),
 ctypes.byref(m_c),
 B.ctypes.data_as(ctypes.c_void_p),
 ctypes.byref(k_c),
 ctypes.byref(zero),
 C.ctypes.data_as(ctypes.c_void_p),
 ctypes.byref(m_c),
)

 return C

Summary: ctypes vs CFFI

Aspect ctypes CFFI
Pythonicity Low High
Type Safety Manual Automatic
Complexity Simple Medium
Performance Good Excellent
NumPy Integration Good Good

-

  • [03 Jit Compilation & Optimization](/05-py3/09-bytecode-and-execution/(03-jit-compilation-optimization/) - Alternative to C extensions
  • 00 Readme - Performance considerations
  • 05 Custom Operators - Custom CUDA kernels