Foreign Function Interface for Python calling C code.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Converting between Python and C data representations. These functions handle string conversion, array unpacking, buffer operations, and memory transfers.
Converts C data to Python strings with support for different character encodings and length limits.
def string(self, cdata, maxlen=-1):
"""
Convert C data to Python string.
Parameters:
- cdata: C data object (char*, char[], wchar_t*, single char, enum)
- maxlen (int): Maximum length (-1 for null-terminated or array length)
Returns:
str|bytes: Python string (bytes for char*, str for wchar_t*)
"""Usage Examples:
# Null-terminated strings
c_str = ffi.new("char[]", b"Hello, World!")
py_str = ffi.string(c_str) # b"Hello, World!"
# String with length limit
long_str = ffi.new("char[]", b"This is a very long string")
short_str = ffi.string(long_str, 10) # b"This is a "
# Unicode strings (wchar_t)
ffi.cdef("typedef wchar_t WCHAR;")
wide_str = ffi.new("WCHAR[]", u"Hello, 世界!")
unicode_str = ffi.string(wide_str) # u"Hello, 世界!"
# Single characters
char_val = ffi.new("char *", ord('A'))
char_str = ffi.string(char_val[0]) # b"A"
# Enum values as strings
ffi.cdef("enum status { OK, ERROR, PENDING };")
status = ffi.new("enum status *", 1) # ERROR
status_str = ffi.string(status[0]) # "ERROR" or "1" if out of rangeUnpacks C arrays into Python lists or strings without stopping at null terminators.
def unpack(self, cdata, length):
"""
Unpack C array data to Python collection.
Parameters:
- cdata: C data pointer or array
- length (int): Number of elements to unpack
Returns:
bytes|str|list: Python collection of unpacked data
"""Usage Examples:
# Unpack char array to bytes
char_array = ffi.new("char[6]", b"Hi\x00lo!") # Contains null byte
data = ffi.unpack(char_array, 6) # b"Hi\x00lo!" (preserves null)
# Unpack integer array to list
int_array = ffi.new("int[]", [1, 2, 3, 4, 5])
py_list = ffi.unpack(int_array, 5) # [1, 2, 3, 4, 5]
# Unpack wide character array
wide_array = ffi.new("wchar_t[]", u"Hello")
unicode_data = ffi.unpack(wide_array, 5) # u"Hello"
# Unpack structure array
ffi.cdef("struct point { int x, y; };")
points = ffi.new("struct point[3]")
points[0].x, points[0].y = 1, 2
points[1].x, points[1].y = 3, 4
points[2].x, points[2].y = 5, 6
point_list = ffi.unpack(points, 3) # List of struct point objectsProvides raw memory access through Python's buffer protocol for efficient data operations.
buffer: callable # Buffer property for accessing raw C dataUsage Examples:
# Create buffer from C array
data = ffi.new("char[1024]")
buf = ffi.buffer(data)
# Buffer operations
buf[0:5] = b"Hello"
content = buf[:] # Get entire buffer as bytes
print(len(buf)) # 1024
# Partial buffer access
partial = buf[10:20] # Slice of buffer
partial[:] = b"World "
# Buffer with specific size
limited_buf = ffi.buffer(data, 100) # Only first 100 bytesCreates C data from existing Python buffer objects like bytearray, array.array, or numpy arrays.
def from_buffer(self, cdecl, python_buffer=None, require_writable=False):
"""
Create C data from Python buffer object.
Parameters:
- cdecl (str): C type for the returned data (defaults to 'char[]')
- python_buffer: Python object supporting buffer protocol
- require_writable (bool): Require writable buffer
Returns:
CData object pointing to buffer data
"""Usage Examples:
# From bytearray
source = bytearray(b"Hello, World!")
c_data = ffi.from_buffer(source) # char[] pointing to bytearray
c_data[0] = ord('h') # Modifies original bytearray
# From array.array
import array
int_array = array.array('i', [1, 2, 3, 4, 5])
c_ints = ffi.from_buffer("int[]", int_array)
# From bytes (read-only)
byte_data = b"Read only data"
c_readonly = ffi.from_buffer(byte_data)
# c_readonly[0] = ord('r') # Would raise error
# Require writable buffer
try:
c_writable = ffi.from_buffer(byte_data, require_writable=True)
except TypeError:
print("Buffer is not writable")
# With specific C type
float_array = array.array('f', [1.0, 2.5, 3.7])
c_floats = ffi.from_buffer("float[]", float_array)Copies memory between C data objects and Python buffers with overlap handling.
def memmove(self, dest, src, n):
"""
Copy n bytes of memory from src to dest.
Parameters:
- dest: Destination C data or writable Python buffer
- src: Source C data or Python buffer
- n (int): Number of bytes to copy
Returns:
None
"""Usage Examples:
# Copy between C arrays
src = ffi.new("char[]", b"Hello, World!")
dest = ffi.new("char[20]")
ffi.memmove(dest, src, 13)
print(ffi.string(dest)) # b"Hello, World!"
# Copy from Python buffer to C data
python_data = bytearray(b"Python data")
c_buffer = ffi.new("char[50]")
ffi.memmove(c_buffer, python_data, len(python_data))
# Copy from C data to Python buffer
result = bytearray(20)
ffi.memmove(result, c_buffer, 11)
print(result[:11]) # bytearray(b'Python data')
# Overlapping memory (safe with memmove)
data = ffi.new("char[]", b"Hello, World!")
ffi.memmove(data + 2, data, 5) # Shift "Hello" 2 positions right
print(ffi.string(data)) # b"HeHello..."# Share data between Python and C without copying
class SharedBuffer:
def __init__(self, size):
self.python_buffer = bytearray(size)
self.c_view = ffi.from_buffer(self.python_buffer)
def write_from_python(self, data):
self.python_buffer[:len(data)] = data
def read_from_c_side(self):
return bytes(self.python_buffer)
# Usage
shared = SharedBuffer(1024)
shared.write_from_python(b"Data from Python")
# C side can directly access shared.c_view
# Changes are immediately visible to Python sidedef process_large_array(py_array):
"""Process large Python array through C without copying"""
# Create C view of Python data
c_array = ffi.from_buffer("double[]", py_array)
# Process data through C (example: in-place operations)
ffi.cdef("void process_doubles(double* arr, size_t count);")
lib = ffi.dlopen("./processing_lib.so")
lib.process_doubles(c_array, len(py_array))
# py_array is now modified in-place
return py_arrayclass CStringBuilder:
def __init__(self, initial_size=1024):
self.buffer = ffi.new("char[]", initial_size)
self.size = initial_size
self.length = 0
def append(self, text):
text_bytes = text.encode('utf-8') if isinstance(text, str) else text
needed = self.length + len(text_bytes)
if needed >= self.size:
# Resize buffer
new_size = max(needed * 2, self.size * 2)
new_buffer = ffi.new("char[]", new_size)
ffi.memmove(new_buffer, self.buffer, self.length)
self.buffer = new_buffer
self.size = new_size
ffi.memmove(self.buffer + self.length, text_bytes, len(text_bytes))
self.length += len(text_bytes)
def to_string(self):
return ffi.string(self.buffer, self.length)
# Usage
builder = CStringBuilder()
builder.append("Hello, ")
builder.append("World!")
result = builder.to_string() # b"Hello, World!"def parse_binary_protocol(data):
"""Parse binary protocol data efficiently"""
c_data = ffi.from_buffer("unsigned char[]", data)
# Define protocol structure
ffi.cdef("""
struct header {
unsigned int magic;
unsigned short version;
unsigned short length;
};
struct message {
struct header hdr;
unsigned char payload[];
};
""")
# Cast to structure
msg = ffi.cast("struct message *", c_data)
# Access fields directly
if msg.hdr.magic == 0xDEADBEEF:
payload_len = msg.hdr.length - ffi.sizeof("struct header")
payload = ffi.unpack(msg.payload, payload_len)
return {
'version': msg.hdr.version,
'payload': payload
}
raise ValueError("Invalid magic number")# For large data, buffer operations are most efficient
large_array = ffi.new("char[10000]")
# Fastest: direct buffer access
buf = ffi.buffer(large_array)
data = buf[:] # Single copy
# Slower: string conversion (for char data)
str_data = ffi.string(large_array, 10000)
# Slowest: unpack (creates Python list for non-char data)
int_array = ffi.cast("int *", large_array)
list_data = ffi.unpack(int_array, 2500) # 10000 / 4def create_aligned_buffer(size, alignment=16):
"""Create memory-aligned buffer for SIMD operations"""
# Allocate extra space for alignment
raw_size = size + alignment - 1
raw_buffer = ffi.new("char[]", raw_size)
# Calculate aligned address
addr = ffi.cast("uintptr_t", raw_buffer)
aligned_addr = (addr + alignment - 1) & ~(alignment - 1)
# Return aligned pointer
return ffi.cast("char *", aligned_addr)Install with Tessl CLI
npx tessl i tessl/pypi-cffi