CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cffi

Foreign Function Interface for Python calling C code.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

data-conversion.mddocs/

Data Conversion

Converting between Python and C data representations. These functions handle string conversion, array unpacking, buffer operations, and memory transfers.

Capabilities

String Conversion

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 range

Array Unpacking

Unpacks 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 objects

Buffer Interface

Provides raw memory access through Python's buffer protocol for efficient data operations.

buffer: callable  # Buffer property for accessing raw C data

Usage 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 bytes

Buffer Creation from Python Objects

Creates 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)

Memory Transfer

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..."

Advanced Buffer Operations

Zero-Copy Data Sharing

# 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 side

Efficient Array Processing

def 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_array

String Builder Pattern

class 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!"

Binary Data Processing

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")

Performance Considerations

Buffer vs String vs Unpack

# 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 / 4

Memory Alignment

def 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

docs

callbacks-handles.md

core-ffi.md

data-conversion.md

error-handling.md

index.md

memory-management.md

source-generation.md

type-system.md

tile.json