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.