Skip to main content

Modules/_ctypes/_ctypes.c

cpython 3.14 @ ab2d84fe1023/Modules/_ctypes/_ctypes.c

This file is the heart of the ctypes extension module. It defines the complete type hierarchy that lets Python code describe and call native C functions without writing any C. The hierarchy roots at _CData, and every concrete ctypes type (Structure, Union, Array, SimpleType, PointerType, FunctionType) descends from it. On top of the type hierarchy sits the marshalling engine: a table of getfunc/setfunc pairs that convert between Python objects and raw C memory for every primitive C type. At the lowest level, marshalled arguments are handed off to libffi via ffi_call, which handles the platform ABI details.

Map

RegionLines (approx.)What it does
Module init and type registration1-150Initialises the _ctypes module, registers all type objects with the interpreter.
_CData base type150-400PyCData_Type, PyCData_NewInternal, _CData_set / _CData_get.
SimpleType metaclass400-700Creates scalar ctypes types (c_int, c_double, etc.) at class-creation time; installs getfunc/setfunc.
PointerType700-1000Type metaclass for pointer types; _Pointer_set_contents, dereference via __getitem__.
ArrayType1000-1300Fixed-length array metaclass; bounds-checked __getitem__ and __setitem__.
StructureType / UnionType1300-2000Metaclasses that parse _fields_, compute layout, and build CField descriptors.
CField descriptor2000-2500CField_get / CField_set read and write struct members through bit-field-aware masks.
FunctionType2500-3200Wraps a function pointer; builds the ffi_cif call-info struct; drives ffi_call.
getfunc / setfunc table3200-4000One entry per C primitive; covers signed/unsigned integers of all widths, float, double, char *, void *, PyObject *.
Miscellaneous helpers4000-5000CastFunc, PointerObj, byref, buffer-protocol helpers, _unpickle.

Reading

The _CData base and memory ownership

Every ctypes instance owns (or borrows) a block of raw C memory tracked by the CDataObject struct:

// Modules/_ctypes/_ctypes.c:172
typedef struct {
PyObject_HEAD
char *b_ptr; /* pointer to memory block */
int b_needsfree; /* b_ptr must be freed */
CDataObject *b_base;/* base object that owns b_ptr, or NULL */
Py_ssize_t b_size; /* size of memory block in bytes */
Py_ssize_t b_offset;/* offset of this object into b_base */
Py_ssize_t b_index; /* index of this object into b_base */
PyObject *b_objects;/* references we need to keep alive */
} CDataObject;

When b_base is non-NULL the instance is a view into another object's buffer and b_needsfree is 0. When b_base is NULL the instance allocated its own block and is responsible for freeing it. This dual-ownership model is what lets Structure fields hand back references into the parent's memory without copying.

getfunc / setfunc dispatch

For each primitive C type, SimpleType_new looks up an entry in the static fielddesc_list table and stores the pair on the type object:

// Modules/_ctypes/_ctypes.c:3241
static struct fielddesc *
_ctypes_get_fielddesc(const char *proto)
{
struct fielddesc *table = fielddesc_list;
for (; table->code; ++table)
if (table->code == proto[0])
return table;
return NULL;
}

At instance __set__ time, _CData_set calls stginfo->setfunc(ptr, value, size) directly, no Python attribute lookup involved. The symmetry between getfunc (memory to Python object) and setfunc (Python object to memory) is the key abstraction that makes the entire marshalling layer extensible: adding a new primitive type means adding one row to fielddesc_list.

ffi_call integration in FunctionType

When a FunctionType instance is called, _CallProc in _ctypes.c marshals each argument through its registered setfunc, builds an array of void * pointers, and delegates to libffi:

// Modules/_ctypes/_ctypes.c:2874
result = ffi_call(&self->cif,
(void (*)(void))self->restype,
resmem,
avalues);
if (result != FFI_OK) {
PyErr_SetString(PyExc_RuntimeError,
"ffi_call failed");
goto cleanup;
}

The ffi_cif is built once at type-creation time by FunctionType_new, so repeated calls pay no setup cost. After ffi_call returns, the return value is converted back to a Python object via the restype's getfunc.

gopy mirror

Not yet ported to gopy. ctypes requires deep integration with platform calling conventions (via libffi) and raw memory management that has no direct Go equivalent. A future port would need to decide between wrapping libffi via cgo, using a pure-Go foreign-function mechanism, or restricting ctypes to a subset that gopy can express natively.

CPython 3.14 changes

  • _ctypes gained a vectorcall fast path for FunctionType.__call__ to reduce per-call overhead on the marshalling hot path.
  • StructureType layout now respects PEP 749 annotations (lazy evaluation), so _fields_ entries that reference forward-declared ctypes types resolve correctly.
  • Several getfunc/setfunc entries for wide-character types were updated to handle the Windows / POSIX wchar_t size difference more consistently.