Python’s ctypes module is a foreign function interface (FFI) that loads shared libraries and calls C functions without writing a C extension. import ctypes. CDLL: lib = ctypes.CDLL("libm.so.6") (Linux), ctypes.CDLL("libSystem.dylib") (macOS); ctypes.WinDLL("kernel32.dll") (Windows). Type mapping: ctypes.c_int, ctypes.c_double, ctypes.c_float, ctypes.c_char_p (null-terminated bytes), ctypes.c_void_p, ctypes.c_bool, ctypes.c_size_t. argtypes / restype: lib.sqrt.argtypes = [ctypes.c_double]; lib.sqrt.restype = ctypes.c_double — essential for correctness. Structure: class Point(ctypes.Structure): _fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int)]. Pointer: ctypes.POINTER(ctypes.c_int) — pointer type; ctypes.byref(obj) — pass like &obj; ctypes.pointer(obj) — full pointer object. Buffers: buf = ctypes.create_string_buffer(128) — mutable c_char array; buf.value = b"hello". cast: ctypes.cast(ptr, ctypes.POINTER(ctypes.c_uint8)) — reinterpret pointer. sizeof: ctypes.sizeof(ctypes.c_double) → 8. string_at: ctypes.string_at(addr) → bytes until null. memmove: ctypes.memmove(dst, src, n) — copies n bytes. ctypes.cdll.LoadLibrary(path) — alternative loader. Claude Code generates libc wrappers, high-performance buffer manipulators, platform API callers, and binary-format readers.
CLAUDE.md for ctypes
## ctypes Stack
- Stdlib: import ctypes
- Load: lib = ctypes.CDLL(ctypes.util.find_library("m")) # portable
- Types: lib.fn.argtypes = [ctypes.c_double]; lib.fn.restype = ctypes.c_double
- Struct: class S(ctypes.Structure): _fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int)]
- Buffer: buf = ctypes.create_string_buffer(256); ctypes.memmove(buf, src, n)
- Ptr: ctypes.byref(obj) # pass &obj to C function
ctypes C Bindings Pipeline
# app/ctypesutil.py — libc helpers, struct marshall, buffer ops, type tools
from __future__ import annotations
import ctypes
import ctypes.util
import struct
import sys
from dataclasses import dataclass
from typing import Any, Type
# ─────────────────────────────────────────────────────────────────────────────
# 1. Library loading helpers
# ─────────────────────────────────────────────────────────────────────────────
def load_library(name: str) -> ctypes.CDLL:
"""
Load a shared library by short name (e.g. "m", "c", "z").
Uses ctypes.util.find_library for portable path resolution.
Example:
libm = load_library("m")
libm.sqrt.argtypes = [ctypes.c_double]
libm.sqrt.restype = ctypes.c_double
print(libm.sqrt(2.0)) # 1.4142...
"""
path = ctypes.util.find_library(name)
if not path:
raise OSError(f"Library not found: {name}")
return ctypes.CDLL(path)
def load_libc() -> ctypes.CDLL:
"""
Load the C standard library portably.
Example:
libc = load_libc()
libc.puts(b"hello from C")
"""
if sys.platform == "win32":
return ctypes.CDLL("msvcrt.dll")
path = ctypes.util.find_library("c")
return ctypes.CDLL(path)
# ─────────────────────────────────────────────────────────────────────────────
# 2. C type utilities
# ─────────────────────────────────────────────────────────────────────────────
_PY_TO_CTYPE: dict[type, type] = {
int: ctypes.c_long,
float: ctypes.c_double,
bool: ctypes.c_bool,
bytes: ctypes.c_char_p,
str: ctypes.c_wchar_p,
}
def py_to_ctype(py_type: type) -> type:
"""
Map a Python type to a ctypes type.
Example:
py_to_ctype(float) # ctypes.c_double
py_to_ctype(int) # ctypes.c_long
"""
ct = _PY_TO_CTYPE.get(py_type)
if ct is None:
raise TypeError(f"No ctypes mapping for {py_type}")
return ct
def declare_function(
lib: ctypes.CDLL,
name: str,
restype: Any,
*argtypes: Any,
) -> Any:
"""
Declare a C function's types and return the callable.
Example:
sqrt_fn = declare_function(libm, "sqrt", ctypes.c_double, ctypes.c_double)
print(sqrt_fn(2.0))
"""
fn = getattr(lib, name)
fn.restype = restype
fn.argtypes = list(argtypes)
return fn
def sizeof_all(*ctypes_types) -> dict[str, int]:
"""
Return {type_name: sizeof} for a list of ctypes types.
Example:
sizes = sizeof_all(ctypes.c_int, ctypes.c_long, ctypes.c_double)
print(sizes)
"""
return {t.__name__: ctypes.sizeof(t) for t in ctypes_types}
# ─────────────────────────────────────────────────────────────────────────────
# 3. Structure and array helpers
# ─────────────────────────────────────────────────────────────────────────────
def make_struct(name: str, fields: list[tuple[str, Any]]) -> type:
"""
Dynamically create a ctypes.Structure subclass.
Example:
Point = make_struct("Point", [("x", ctypes.c_int), ("y", ctypes.c_int)])
p = Point(x=10, y=20)
print(p.x, p.y)
"""
return type(name, (ctypes.Structure,), {"_fields_": fields})
def struct_to_dict(obj: ctypes.Structure) -> dict[str, Any]:
"""
Convert a ctypes.Structure instance to a plain Python dict.
Example:
Point = make_struct("Point", [("x", ctypes.c_int), ("y", ctypes.c_int)])
p = Point(x=10, y=20)
print(struct_to_dict(p)) # {"x": 10, "y": 20}
"""
return {name: getattr(obj, name) for name, _ in obj._fields_}
def struct_to_bytes(obj: ctypes.Structure) -> bytes:
"""
Serialize a ctypes.Structure to a bytes representation.
Example:
raw = struct_to_bytes(point_instance)
"""
return bytes(obj)
def struct_from_bytes(struct_type: type, data: bytes):
"""
Deserialize bytes into a ctypes.Structure instance.
Example:
point = struct_from_bytes(Point, raw_bytes)
"""
return struct_type.from_buffer_copy(data)
def make_array(ctype: Any, values: list) -> ctypes.Array:
"""
Create a ctypes array from a Python list.
Example:
arr = make_array(ctypes.c_int, [1, 2, 3, 4, 5])
print(arr[2]) # 3
"""
ArrayType = ctype * len(values)
return ArrayType(*values)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Buffer helpers
# ─────────────────────────────────────────────────────────────────────────────
def alloc_buffer(size: int, init: bytes | None = None) -> ctypes.Array:
"""
Allocate a mutable byte buffer optionally pre-filled with init.
Example:
buf = alloc_buffer(256, b"hello\\x00")
print(buf.value) # b'hello'
"""
buf = ctypes.create_string_buffer(size)
if init:
ctypes.memmove(buf, init, min(len(init), size))
return buf
def buffer_to_bytes(buf: ctypes.Array) -> bytes:
"""Read the raw contents of a ctypes buffer (including null bytes)."""
return bytes(buf)
def bytes_to_ptr(data: bytes) -> ctypes.c_char_p:
"""Wrap a bytes object as a c_char_p (const char*)."""
return ctypes.c_char_p(data)
def copy_bytes_into_buffer(buf: ctypes.Array, data: bytes, offset: int = 0) -> None:
"""
Copy bytes data into a ctypes buffer starting at offset.
Example:
buf = alloc_buffer(256)
copy_bytes_into_buffer(buf, b"hello", offset=4)
"""
ctypes.memmove(ctypes.addressof(buf) + offset, data, len(data))
# ─────────────────────────────────────────────────────────────────────────────
# 5. libc wrappers
# ─────────────────────────────────────────────────────────────────────────────
def libc_memset(buf: ctypes.Array, value: int, count: int) -> None:
"""
Fill `count` bytes of `buf` with `value` using libc memset.
Example:
buf = alloc_buffer(64)
libc_memset(buf, 0xFF, 64)
"""
ctypes.memset(buf, value, count)
def libc_strlen(data: bytes) -> int:
"""
Return the length of a null-terminated C string in bytes.
Example:
libc_strlen(b"hello\\x00extra") # 5
"""
libc = load_libc()
libc.strlen.restype = ctypes.c_size_t
libc.strlen.argtypes = [ctypes.c_char_p]
return libc.strlen(data)
def system_pagesize() -> int:
"""
Return the OS memory page size in bytes.
Example:
print(system_pagesize()) # typically 4096
"""
if sys.platform == "win32":
si = ctypes.create_string_buffer(64)
ctypes.windll.kernel32.GetSystemInfo(si) # type: ignore[attr-defined]
return struct.unpack_from("I", bytes(si), 8)[0] # dwPageSize at offset 8
libc = load_libc()
libc.getpagesize.restype = ctypes.c_int
return libc.getpagesize()
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== ctypes demo ===")
# ── sizeof ────────────────────────────────────────────────────────────────
print("\n--- sizeof_all ---")
sizes = sizeof_all(ctypes.c_char, ctypes.c_short, ctypes.c_int,
ctypes.c_long, ctypes.c_double, ctypes.c_void_p)
for name, n in sizes.items():
print(f" sizeof({name}) = {n}")
# ── math library via ctypes ───────────────────────────────────────────────
print("\n--- load_library('m') ---")
try:
libm = load_library("m")
sqrt_fn = declare_function(libm, "sqrt", ctypes.c_double, ctypes.c_double)
pow_fn = declare_function(libm, "pow", ctypes.c_double, ctypes.c_double, ctypes.c_double)
print(f" sqrt(2.0) = {sqrt_fn(2.0):.6f}")
print(f" pow(2.0, 10) = {pow_fn(2.0, 10):.0f}")
except OSError as e:
print(f" libm not found: {e}")
# ── Structure ─────────────────────────────────────────────────────────────
print("\n--- make_struct ---")
Point = make_struct("Point", [("x", ctypes.c_int), ("y", ctypes.c_int)])
p = Point(x=10, y=-5)
print(f" Point(x={p.x}, y={p.y})")
print(f" struct_to_dict: {struct_to_dict(p)}")
raw = struct_to_bytes(p)
print(f" raw bytes ({len(raw)}): {raw.hex()}")
p2 = struct_from_bytes(Point, raw)
print(f" round-tripped: x={p2.x}, y={p2.y}")
# ── buffer ops ────────────────────────────────────────────────────────────
print("\n--- buffer ops ---")
buf = alloc_buffer(32, b"hello, ctypes!")
print(f" buf.value: {buf.value!r}")
libc_memset(buf, 0x41, 5) # overwrite first 5 bytes with 'A'
print(f" after memset('A', 5): {bytes(buf)[:10]!r}")
# ── array ─────────────────────────────────────────────────────────────────
print("\n--- make_array ---")
arr = make_array(ctypes.c_int, [10, 20, 30, 40, 50])
print(f" arr[0]={arr[0]} arr[4]={arr[4]} len={len(arr)}")
print(f" sum: {sum(arr)}")
# ── pagesize ──────────────────────────────────────────────────────────────
print("\n--- system_pagesize ---")
try:
print(f" page size: {system_pagesize()} bytes")
except Exception as e:
print(f" error: {e}")
print("\n=== done ===")
For the cffi alternative — cffi (PyPI) provides an alternative FFI with a C-declaration parser (ffi.cdef()), inline C compilation via ffi.verify(), and an API mode that generates a compiled C extension for maximum performance; it is the FFI used internally by PyPy — use cffi when you need to define complex C APIs by pasting header declarations, need API-mode performance comparable to a handwritten C extension, or are targeting PyPy compatibility; use ctypes for interactive prototyping, small-scale library wrapping, and when zero dependencies matter. For the struct alternative — struct.pack() / struct.unpack() work on Python bytes objects and are ideal for parsing binary file formats and network protocols without going through C function calls; ctypes.Structure maps a Python class directly onto C memory layout — use struct for pure Python binary parsing where you receive bytes from a file or socket; use ctypes.Structure when you need to pass a pointer to a C struct into a C library function, or when the binary layout must match a specific C ABI. The Claude Skills 360 bundle includes ctypes skill sets covering load_library()/load_libc() portable loaders, py_to_ctype()/declare_function()/sizeof_all() type utilities, make_struct()/struct_to_dict()/struct_to_bytes()/struct_from_bytes() struct helpers, make_array()/alloc_buffer()/copy_bytes_into_buffer()/buffer_to_bytes() buffer tools, and libc_memset()/libc_strlen()/system_pagesize() libc wrappers. Start with the free tier to try C FFI patterns and ctypes pipeline code generation.