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
| Lines | Symbol | Role |
|---|---|---|
| 1-100 | StgDict | Per-type storage info: size, alignment, shape, field offsets |
| 101-240 | Structure._fields_ | Compute field layout; populate StgDict |
| 241-380 | Union metaclass | Overlapping fields; size = max field size |
| 381-500 | Array type creation | c_int * 4 creates a 4-element array type |
| 501-700 | CFUNCTYPE / callbacks | C 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.