Skip to main content

Modules/_ctypes/ (part 2)

Source:

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

This annotation covers composite types and callbacks. See modules_ctypes_detail for simple types (c_int, c_char_p), cdll.LoadLibrary, function prototypes, and argument marshalling.

Map​

LinesSymbolRole
1-100StgDictPer-type storage info: size, alignment, shape, field offsets
101-240Structure._fields_Compute field layout; populate StgDict
241-380Union metaclassOverlapping fields; size = max field size
381-500Array type creationc_int * 4 creates a 4-element array type
501-700CFUNCTYPE / callbacksC callable that invokes a Python function

Reading​

StgDict​

// CPython: Modules/_ctypes/stgdict.c:40 StgDict layout
/* Every ctypes type has an associated StgDict (storage dict).
Key fields:
size — sizeof the type in bytes
align — alignment requirement
length — number of elements (for arrays)
proto — element type (for arrays/pointers)
setfunc — convert Python -> C value
getfunc — convert C value -> Python
fields — list of CField objects (for Structure/Union) */

StgDict is stored as a C extension on the type's tp_dict. It is computed once when _fields_ is set and reused for all instances.

Structure._fields_​

// CPython: Modules/_ctypes/stgdict.c:420 PyCStructUnionType_update_stgdict
int
PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct)
{
Py_ssize_t offset = 0, size = 0, align = 1;
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(fields); i++) {
PyObject *pair = PyList_GET_ITEM(fields, i);
const char *fname = PyUnicode_AsUTF8(PyTuple_GET_ITEM(pair, 0));
PyObject *ftype = PyTuple_GET_ITEM(pair, 1);
StgDictObject *fdict = PyType_stgdict(ftype);
/* Align offset to fdict->align */
offset = (offset + fdict->align - 1) & ~(fdict->align - 1);
/* Create CField descriptor at this offset */
PyMemberDef def = {fname, T_OBJECT, offset, 0};
offset += fdict->size;
}
stgdict->size = offset;
stgdict->align = align;
}

Setting MyStruct._fields_ = [('x', c_int), ('y', c_float)] triggers PyCStructUnionType_update_stgdict which computes C-compatible field offsets including alignment padding.

CFUNCTYPE​

// CPython: Modules/_ctypes/callproc.c:1140 CThunkObject_new
/* A CThunkObject wraps a Python callable as a C function pointer.
When the C code calls the function pointer:
1. A thunk trampoline (generated by libffi) calls _CallPythonObject
2. _CallPythonObject acquires the GIL
3. Converts C arguments to Python objects
4. Calls the Python function
5. Converts the return value back to C
6. Releases the GIL */

CFUNCTYPE(c_int, c_int)(my_func) creates a C-callable function pointer. The libffi closure is allocated in executable memory. This is how qsort can accept a Python comparison function.

_CallPythonObject​

// CPython: Modules/_ctypes/callproc.c:1040 _CallPythonObject
static void
_CallPythonObject(void *mem, ffi_type *restype, SETFUNC setfunc,
PyObject *callable, PyObject *converters,
int flags, void **pArgs)
{
PyGILState_STATE state = PyGILState_Ensure();
/* Convert C args to Python objects using converters */
PyObject *arglist = PyTuple_New(nArgs);
...
PyObject *result = PyObject_Call(callable, arglist, NULL);
/* Write return value back to mem via setfunc */
if (setfunc) setfunc(mem, result, 0);
PyGILState_Release(state);
}

The GIL is acquired before calling into Python. This makes ctypes callbacks thread-safe: a C library can call the callback from any thread, and the GIL ensures Python objects are safe to access.

gopy notes​

ctypes support in gopy is limited. StgDict is represented by objects.StgDict in objects/ctypes.go. Structure._fields_ layout uses Go unsafe.Sizeof and unsafe.Alignof for the field alignment calculation. CFUNCTYPE callbacks are not yet implemented as they require CGo trampolines.