Foreign Function Interface for Python calling C code.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Error management, system integration utilities, and platform-specific functionality including errno handling and Windows Unicode support.
Access and manipulation of C library error codes through the errno mechanism.
errno: property # Property for C errno access
def _get_errno(self):
"""Get current errno value"""
def _set_errno(self, errno):
"""Set errno value"""Usage Examples:
import os
# Access C errno
ffi = FFI()
ffi.cdef("FILE* fopen(const char* filename, const char* mode);")
libc = ffi.dlopen(None)
# Try to open non-existent file
result = libc.fopen(b"/nonexistent/file.txt", b"r")
if result == ffi.NULL:
error_code = ffi.errno
print(f"C errno: {error_code}")
print(f"Python equivalent: {os.strerror(error_code)}")
# Set errno manually
ffi.errno = 42
print(f"Set errno to: {ffi.errno}")Windows-specific error code retrieval and formatting.
def getwinerror(self, code=-1):
"""
Get Windows error information.
Parameters:
- code (int): Error code (-1 for GetLastError())
Returns:
tuple: (error_code, error_message) on Windows, OSError on other platforms
"""Usage Example:
# Windows-specific error handling
if sys.platform == "win32":
try:
# Some Windows API call that might fail
ffi.cdef("void* CreateFileA(const char* name, int access, int share, void* security, int creation, int flags, void* template);")
kernel32 = ffi.dlopen("kernel32.dll")
handle = kernel32.CreateFileA(b"invalid\\path\\file.txt", 0x80000000, 0, ffi.NULL, 3, 0, ffi.NULL)
if handle == ffi.cast("void*", -1): # INVALID_HANDLE_VALUE
error_code, error_msg = ffi.getwinerror()
print(f"Windows Error {error_code}: {error_msg}")
except OSError:
print("Not on Windows platform")Includes type definitions from another FFI instance for modular design.
def include(self, ffi_to_include):
"""
Include types from another FFI instance.
Parameters:
- ffi_to_include: Another FFI instance to include types from
Returns:
None
Note: Only includes types, not functions or variables
"""Usage Example:
# Base types FFI
base_ffi = FFI()
base_ffi.cdef("""
typedef struct {
int x, y;
} point_t;
typedef struct {
point_t top_left;
point_t bottom_right;
} rect_t;
""")
# Graphics FFI that uses base types
graphics_ffi = FFI()
graphics_ffi.include(base_ffi) # Include point_t and rect_t
graphics_ffi.cdef("""
// Can now use point_t and rect_t
void draw_rect(rect_t rect);
point_t get_center(rect_t rect);
""")Configures Windows-specific Unicode type definitions and macros.
def set_unicode(self, enabled_flag):
"""
Configure Windows Unicode support.
Parameters:
- enabled_flag (bool): Enable Unicode types and macros
Returns:
None
Note: Can only be called once per FFI instance
"""Usage Example:
# Configure for Unicode Windows API
ffi = FFI()
ffi.set_unicode(True) # Enables UNICODE and _UNICODE macros
# Now TCHAR maps to wchar_t, LPTSTR to wchar_t*, etc.
ffi.cdef("""
int MessageBoxW(void* hWnd, const TCHAR* text, const TCHAR* caption, unsigned int type);
""")
# For ANSI API
ansi_ffi = FFI()
ansi_ffi.set_unicode(False) # TCHAR maps to char
ansi_ffi.cdef("""
int MessageBoxA(void* hWnd, const TCHAR* text, const TCHAR* caption, unsigned int type);
""")Executes functions exactly once per tag, useful for expensive initialization operations.
def init_once(self, func, tag):
"""
Execute function once per tag.
Parameters:
- func: Function to execute
- tag: Unique identifier for this initialization
Returns:
Result of func() on first call, cached result on subsequent calls
"""Usage Example:
# Expensive initialization
def initialize_crypto():
print("Initializing cryptographic library...")
# Expensive setup code here
return {"initialized": True, "algorithms": ["AES", "RSA"]}
ffi = FFI()
# Called multiple times, but initialization happens only once
result1 = ffi.init_once(initialize_crypto, "crypto_init")
result2 = ffi.init_once(initialize_crypto, "crypto_init") # Uses cached result
print(result1 is result2) # True - same object returnedAdvanced features for embedding Python in C applications.
def embedding_api(self, csource, packed=False, pack=None):
"""
Define API for embedding Python in C.
Parameters:
- csource (str): C declarations for embedding API
- packed (bool): Pack structures
- pack (int): Packing alignment
Returns:
None
"""
def embedding_init_code(self, pysource):
"""
Set Python initialization code for embedding.
Parameters:
- pysource (str): Python code to execute on embedding initialization
Returns:
None
"""
def def_extern(self, *args, **kwds):
"""
Define external function for API mode (embedding).
Note: Only available on API-mode FFI objects
"""Usage Example:
# Embedding setup
embed_ffi = FFI()
# Define embedding API
embed_ffi.embedding_api("""
int process_data(int* input, int count);
char* get_status();
""")
# Set initialization code
embed_ffi.embedding_init_code("""
def process_data(input_ptr, count):
# Convert C array to Python list
data = ffi.unpack(ffi.cast("int*", input_ptr), count)
# Process in Python
result = sum(x * 2 for x in data)
return result
def get_status():
return ffi.new("char[]", b"Ready")
# Register functions
ffi.def_extern(process_data)
ffi.def_extern(get_status)
""")class FFIError(Exception):
"""Base exception for CFFI errors"""
class CDefError(Exception):
"""C declaration parsing errors"""
class VerificationError(Exception):
"""Code verification and compilation errors"""
class VerificationMissing(Exception):
"""Incomplete structure definition errors"""
class PkgConfigError(Exception):
"""Package configuration errors"""Usage Examples:
try:
ffi = FFI()
ffi.cdef("invalid C syntax here")
except CDefError as e:
print(f"C definition error: {e}")
try:
ffi.cdef("struct incomplete;")
incomplete = ffi.new("struct incomplete *") # Error: incomplete type
except VerificationMissing as e:
print(f"Incomplete structure: {e}")
try:
ffi.verify("int func() { syntax error }")
except VerificationError as e:
print(f"Compilation failed: {e}")class CFfiErrorContext:
def __init__(self, ffi, operation_name):
self.ffi = ffi
self.operation_name = operation_name
self.saved_errno = None
def __enter__(self):
# Save current errno
self.saved_errno = self.ffi.errno
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
return
# Add context to exceptions
if isinstance(exc_val, (OSError, FFIError)):
current_errno = self.ffi.errno
if current_errno != self.saved_errno:
exc_val.args = exc_val.args + (f"errno changed to {current_errno} during {self.operation_name}",)
# Restore errno
self.ffi.errno = self.saved_errno
# Usage
with CFfiErrorContext(ffi, "file operations"):
# File operations that might change errno
passdef safe_library_call(ffi, lib, func_name, *args, check_errno=True, check_return=None):
"""Safely call C library function with error checking"""
# Save errno
old_errno = ffi.errno if check_errno else None
if check_errno:
ffi.errno = 0
try:
# Get function
func = getattr(lib, func_name)
# Call function
result = func(*args)
# Check return value
if check_return and not check_return(result):
raise RuntimeError(f"{func_name} returned error value: {result}")
# Check errno
if check_errno and ffi.errno != 0:
error_msg = os.strerror(ffi.errno)
raise OSError(ffi.errno, f"{func_name} failed: {error_msg}")
return result
finally:
# Restore errno
if check_errno and old_errno is not None:
ffi.errno = old_errno
# Usage
ffi.cdef("FILE* fopen(const char* name, const char* mode);")
libc = ffi.dlopen(None)
try:
file_ptr = safe_library_call(
ffi, libc, "fopen",
b"test.txt", b"r",
check_return=lambda x: x != ffi.NULL
)
print("File opened successfully")
except (OSError, RuntimeError) as e:
print(f"Failed to open file: {e}")def get_system_error(ffi):
"""Get system error in a cross-platform way"""
if sys.platform == "win32":
try:
error_code, error_msg = ffi.getwinerror()
return f"Windows Error {error_code}: {error_msg}"
except OSError:
pass
# Fall back to errno
errno_val = ffi.errno
if errno_val != 0:
return f"System Error {errno_val}: {os.strerror(errno_val)}"
return "No system error"
# Usage in error handling
try:
# Some system operation
pass
except Exception as e:
system_error = get_system_error(ffi)
print(f"Operation failed: {e}")
print(f"System error: {system_error}")import logging
class CFfiLogger:
def __init__(self, ffi, logger_name="cffi"):
self.ffi = ffi
self.logger = logging.getLogger(logger_name)
def log_system_state(self, level=logging.DEBUG):
"""Log current system error state"""
errno_val = self.ffi.errno
self.logger.log(level, f"Current errno: {errno_val}")
if sys.platform == "win32":
try:
win_error = self.ffi.getwinerror()
self.logger.log(level, f"Windows error: {win_error}")
except OSError:
pass
def wrap_call(self, func, *args, **kwargs):
"""Wrap function call with logging"""
self.logger.debug(f"Calling {func.__name__} with args: {args}")
self.log_system_state()
try:
result = func(*args, **kwargs)
self.logger.debug(f"{func.__name__} returned: {result}")
return result
except Exception as e:
self.logger.error(f"{func.__name__} failed: {e}")
self.log_system_state(logging.ERROR)
raise
# Usage
logger = CFfiLogger(ffi)
logger.wrap_call(libc.fopen, b"test.txt", b"r")Install with Tessl CLI
npx tessl i tessl/pypi-cffi