| /* Use this file as a template to start implementing a module that | |
| also declares object types. All occurrences of 'Xxo' should be changed | |
| to something reasonable for your objects. After that, all other | |
| occurrences of 'xx' should be changed to something reasonable for your | |
| module. If your module is named foo your source file should be named | |
| foo.c or foomodule.c. | |
| You will probably want to delete all references to 'x_attr' and add | |
| your own types of attributes instead. Maybe you want to name your | |
| local variables other than 'self'. If your object type is needed in | |
| other files, you'll have to create a file "foobarobject.h"; see | |
| floatobject.h for an example. | |
| This module uses Limited API 3.15. | |
| See ``xxlimited_3_13.c`` if you want to support older CPython versions. | |
| This module roughly corresponds to the following. | |
| (All underscore-prefixed attributes are not accessible from Python.) | |
| :: | |
| class Xxo: | |
| """A class that explicitly stores attributes in an internal dict | |
| (to simulate custom attribute handling). | |
| """ | |
| def __init__(self): | |
| # In the C class, "_x_attr" is not accessible from Python code | |
| self._x_attr = {} | |
| self._x_exports = 0 | |
| def __getattr__(self, name): | |
| return self._x_attr[name] | |
| def __setattr__(self, name, value): | |
| if name == "reserved": | |
| raise AttributeError("cannot set 'reserved'") | |
| self._x_attr[name] = value | |
| def __delattr__(self, name): | |
| del self._x_attr[name] | |
| @property | |
| def x_exports(self): | |
| """Return the number of times an internal buffer is exported.""" | |
| # Each Xxo instance has a 10-byte buffer that can be | |
| # accessed via the buffer interface (e.g. `memoryview`). | |
| return self._x_exports | |
| def demo(o, /): | |
| if isinstance(o, str): | |
| return o | |
| elif isinstance(o, Xxo): | |
| return o | |
| else: | |
| raise Error('argument must be str or Xxo') | |
| class Error(Exception): | |
| """Exception raised by the xxlimited module""" | |
| def foo(i: int, j: int, /): | |
| """Return the sum of i and j.""" | |
| # Unlike this pseudocode, the C function will *only* work with | |
| # integers and perform C long int arithmetic | |
| return i + j | |
| def new(): | |
| return Xxo() | |
| def Str(str): | |
| # A trivial subclass of a built-in type | |
| pass | |
| */ | |
| // Target both flavors of the Stable ABI. | |
| // Both are set to version 3.15, which adds PyModExport | |
| // (When using a build tool, check if it has an option to set these | |
| // so they do not need to be defined in the source.) | |
| #define Py_LIMITED_API 0x030f0000 // abi3 (GIL-enabled builds) | |
| #define Py_TARGET_ABI3T 0x030f0000 // abi3t (free-threaded builds) | |
| #include "Python.h" | |
| #include <string.h> | |
| #define BUFSIZE 10 | |
| // Module state | |
| typedef struct { | |
| PyTypeObject *Xxo_Type; // Xxo class | |
| PyObject *Error_Type; // Error class | |
| } xx_state; | |
| /* Xxo objects. | |
| * | |
| * A non-trivial extension type, intentionally showing a number of features | |
| * that aren't easy to implement in the Limited API. | |
| */ | |
| // Forward declaration | |
| static PyType_Spec Xxo_Type_spec; | |
| // Get the module state (xx_state*) from a given type object 'type', which | |
| // must be a subclass of Xxo (the type we're defining). | |
| // This is complicated by the fact that the Xxo type is dynamically allocated, | |
| // and there may be several such types in a given Python process -- for | |
| // example, in different subinterpreters, or through loading this | |
| // extension module several times. | |
| // So, we don't have a "global" pointer to the type, or to the module, etc.; | |
| // instead we search based on `Xxo_Type_spec` (which is static, immutable, | |
| // and process-global). | |
| // | |
| // When possible, it's better to avoid `PyType_GetBaseByToken` -- for an | |
| // example, see the `demo` method (Xxo_demo C function), which uses a | |
| // "defining class". But, in many cases it's the best solution. | |
| static xx_state * | |
| Xxo_state_from_type(PyTypeObject *type) | |
| { | |
| PyTypeObject *base; | |
| // Search all superclasses of 'type' for one that was defined using | |
| // "Xxo_Type_spec". That must be our 'Xxo' class. | |
| if (PyType_GetBaseByToken(type, &Xxo_Type_spec, &base) < 0) { | |
| return NULL; | |
| } | |
| if (base == NULL) { | |
| PyErr_SetString(PyExc_TypeError, "need Xxo subclass"); | |
| return NULL; | |
| } | |
| // From this type, get the associated module. That must be the | |
| // relevant `xxlimited` module. | |
| xx_state *state = PyType_GetModuleState(base); | |
| Py_DECREF(base); | |
| return state; | |
| } | |
| // Structure for data needed by the XxoObject type. | |
| // Since the object may be shared across threads, access to the fields | |
| // usually needs to be synchronized (using Py_BEGIN_CRITICAL_SECTION). | |
| typedef struct { | |
| PyObject *x_attr; /* Attributes dictionary. | |
| * May be NULL, which acts as an | |
| * empty dict. | |
| */ | |
| Py_ssize_t x_exports; /* how many buffers are exported */ | |
| char x_buffer[BUFSIZE]; /* buffer for Py_buffer (for simplicity, | |
| * this is constant, so does not need | |
| * synchronization) | |
| */ | |
| } XxoObject_Data; | |
| // Get the `XxoObject_Data` structure for a given instance of our type. | |
| static XxoObject_Data * | |
| Xxo_get_data(PyObject *self) | |
| { | |
| xx_state *state = Xxo_state_from_type(Py_TYPE(self)); | |
| if (!state) { | |
| return NULL; | |
| } | |
| XxoObject_Data *data = PyObject_GetTypeData(self, state->Xxo_Type); | |
| return data; | |
| } | |
| // A variant of Xxo_get_data to be used in the tp_traverse handler. | |
| // This function cannot have side effects (including reference count | |
| // manipulation, creating objects, and raising exceptions), and must not | |
| // call API functions that might have side effects. | |
| // See: https://docs.python.org/3.15/c-api/gcsupport.html#traversal | |
| static XxoObject_Data * | |
| Xxo_get_data_DuringGC(PyObject *self) | |
| { | |
| PyTypeObject *base; | |
| PyType_GetBaseByToken_DuringGC(Py_TYPE(self), &Xxo_Type_spec, &base); | |
| if (base == NULL) { | |
| return NULL; | |
| } | |
| xx_state *state = PyType_GetModuleState_DuringGC(base); | |
| if (state == NULL) { | |
| return NULL; | |
| } | |
| XxoObject_Data *data = PyObject_GetTypeData_DuringGC(self, state->Xxo_Type); | |
| return data; | |
| } | |
| // Xxo initialization | |
| // This is the implementation of Xxo.__new__ | |
| static PyObject * | |
| Xxo_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) | |
| { | |
| // Validate that we did not get any arguments. | |
| if ((args != NULL && PyObject_Length(args)) | |
| || (kwargs != NULL && PyObject_Length(kwargs))) | |
| { | |
| PyErr_SetString(PyExc_TypeError, "Xxo.__new__() takes no arguments"); | |
| return NULL; | |
| } | |
| // Create an instance of *type* (which may be a subclass) | |
| allocfunc alloc = PyType_GetSlot(type, Py_tp_alloc); | |
| PyObject *self = alloc(type, 0); | |
| if (self == NULL) { | |
| return NULL; | |
| } | |
| // Initialize the C members on the instance. | |
| // This is only included for the sake of example. The default alloc | |
| // function zeroes instance memory; we don't need to do it again. | |
| // Note that we during initialization (and finalization), we hold the only | |
| // reference to the object, so we don't need to synchronize with | |
| // other threads. | |
| XxoObject_Data *xxo_data = Xxo_get_data(self); | |
| if (xxo_data == NULL) { | |
| Py_DECREF(self); | |
| return NULL; | |
| } | |
| xxo_data->x_attr = NULL; | |
| memset(xxo_data->x_buffer, 0, BUFSIZE); | |
| xxo_data->x_exports = 0; | |
| return self; | |
| } | |
| /* Xxo finalization. | |
| * | |
| * Types that store references to other PyObjects generally need to implement | |
| * the GC slots: traverse, clear, dealloc, and (optionally) finalize. | |
| */ | |
| // traverse: Visit all references from an object, including its type | |
| static int | |
| Xxo_traverse(PyObject *self, visitproc visit, void *arg) | |
| { | |
| // Visit the type | |
| Py_VISIT(Py_TYPE(self)); | |
| // Visit the attribute dict | |
| XxoObject_Data *data = Xxo_get_data_DuringGC(self); | |
| if (data == NULL) { | |
| return 0; | |
| } | |
| Py_VISIT(data->x_attr); | |
| return 0; | |
| } | |
| // clear: drop references in order to break all reference cycles | |
| static int | |
| Xxo_clear(PyObject *self) | |
| { | |
| XxoObject_Data *data = Xxo_get_data(self); | |
| if (data == NULL) { | |
| return 0; | |
| } | |
| Py_CLEAR(data->x_attr); | |
| return 0; | |
| } | |
| // finalize: like clear, but should leave the object in a consistent state. | |
| // Equivalent to `__del__` in Python. | |
| static void | |
| Xxo_finalize(PyObject *self) | |
| { | |
| XxoObject_Data *data = Xxo_get_data(self); | |
| if (data == NULL) { | |
| return; | |
| } | |
| Py_CLEAR(data->x_attr); | |
| } | |
| // dealloc: drop all remaining references and free memory | |
| static void | |
| Xxo_dealloc(PyObject *self) | |
| { | |
| // This function must preserve currently raised exception, if any. | |
| PyObject *exc = PyErr_GetRaisedException(); | |
| PyObject_GC_UnTrack(self); | |
| Xxo_finalize(self); | |
| PyTypeObject *tp = Py_TYPE(self); | |
| freefunc free = PyType_GetSlot(tp, Py_tp_free); | |
| free(self); | |
| Py_DECREF(tp); | |
| if (PyErr_Occurred()) { | |
| PyErr_WriteUnraisable(NULL); | |
| } | |
| PyErr_SetRaisedException(exc); | |
| } | |
| /* Xxo attribute handling */ | |
| // Get an attribute. | |
| static PyObject * | |
| Xxo_getattro(PyObject *self, PyObject *name) | |
| { | |
| XxoObject_Data *data = Xxo_get_data(self); | |
| if (data == NULL) { | |
| return 0; | |
| } | |
| PyObject *x_attr; | |
| Py_BEGIN_CRITICAL_SECTION(self); | |
| x_attr = data->x_attr; | |
| Py_END_CRITICAL_SECTION(); | |
| if (x_attr != NULL) { | |
| PyObject *v = PyDict_GetItemWithError(x_attr, name); | |
| if (v != NULL) { | |
| return Py_NewRef(v); | |
| } | |
| else if (PyErr_Occurred()) { | |
| return NULL; | |
| } | |
| } | |
| // Fall back to generic implementation (this handles special attributes, | |
| // raising AttributeError, etc.) | |
| return PyObject_GenericGetAttr(self, name); | |
| } | |
| // Set or delete an attribute. | |
| static int | |
| Xxo_setattro(PyObject *self, PyObject *name, PyObject *v) | |
| { | |
| // filter a specific attribute name | |
| if (PyUnicode_Check(name) && PyUnicode_EqualToUTF8(name, "reserved")) { | |
| PyErr_Format(PyExc_AttributeError, "cannot set %R", name); | |
| return -1; | |
| } | |
| XxoObject_Data *data = Xxo_get_data(self); | |
| if (data == NULL) { | |
| return -1; | |
| } | |
| // If the attribute dict is not created yet, make one. | |
| // This needs to be protected by a critical section to avoid another thread | |
| // creating a duplicate dict. | |
| PyObject *x_attr; | |
| Py_BEGIN_CRITICAL_SECTION(self); | |
| x_attr = data->x_attr; | |
| if (x_attr == NULL) { | |
| // prepare the attribute dict | |
| data->x_attr = x_attr = PyDict_New(); | |
| } | |
| Py_END_CRITICAL_SECTION(); | |
| if (x_attr == NULL) { | |
| return -1; | |
| } | |
| if (v == NULL) { | |
| // delete an attribute | |
| int rv = PyDict_DelItem(x_attr, name); | |
| if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) { | |
| PyErr_SetString(PyExc_AttributeError, | |
| "delete non-existing Xxo attribute"); | |
| return -1; | |
| } | |
| return rv; | |
| } | |
| else { | |
| // set an attribute | |
| return PyDict_SetItem(x_attr, name, v); | |
| } | |
| } | |
| /* Xxo methods: C functions plus a PyMethodDef array that lists them and | |
| * specifies metadata. | |
| */ | |
| static PyObject * | |
| Xxo_demo(PyObject *self, PyTypeObject *defining_class, | |
| PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) | |
| { | |
| if (kwnames != NULL && PyObject_Length(kwnames)) { | |
| PyErr_SetString(PyExc_TypeError, "demo() takes no keyword arguments"); | |
| return NULL; | |
| } | |
| if (nargs != 1) { | |
| PyErr_SetString(PyExc_TypeError, "demo() takes exactly 1 argument"); | |
| return NULL; | |
| } | |
| PyObject *o = args[0]; | |
| /* Test if the argument is "str" */ | |
| if (PyUnicode_Check(o)) { | |
| return Py_NewRef(o); | |
| } | |
| /* test if the argument is of the Xxo class */ | |
| if (PyObject_TypeCheck(o, defining_class)) { | |
| return Py_NewRef(o); | |
| } | |
| return Py_NewRef(Py_None); | |
| } | |
| static PyMethodDef Xxo_methods[] = { | |
| {"demo", _PyCFunction_CAST(Xxo_demo), | |
| METH_METHOD | METH_FASTCALL | METH_KEYWORDS, PyDoc_STR("demo(o) -> o")}, | |
| {NULL, NULL} /* sentinel */ | |
| }; | |
| /* Xxo buffer interface: C functions later referenced from PyType_Slot array. | |
| * Other interfaces (e.g. for sequence-like or number-like types) are defined | |
| * similarly. | |
| */ | |
| static int | |
| Xxo_getbuffer(PyObject *self, Py_buffer *view, int flags) | |
| { | |
| XxoObject_Data *data = Xxo_get_data(self); | |
| if (data == NULL) { | |
| return -1; | |
| } | |
| int res = PyBuffer_FillInfo(view, self, | |
| (void *)data->x_buffer, BUFSIZE, | |
| 0, flags); | |
| if (res == 0) { | |
| Py_BEGIN_CRITICAL_SECTION(self); | |
| data->x_exports++; | |
| Py_END_CRITICAL_SECTION(); | |
| } | |
| return res; | |
| } | |
| static void | |
| Xxo_releasebuffer(PyObject *self, Py_buffer *Py_UNUSED(view)) | |
| { | |
| XxoObject_Data *data = Xxo_get_data(self); | |
| if (data == NULL) { | |
| return; | |
| } | |
| Py_BEGIN_CRITICAL_SECTION(self); | |
| data->x_exports--; | |
| Py_END_CRITICAL_SECTION(); | |
| } | |
| static PyObject * | |
| Xxo_get_x_exports(PyObject *self, void *Py_UNUSED(closure)) | |
| { | |
| XxoObject_Data *data = Xxo_get_data(self); | |
| if (data == NULL) { | |
| return NULL; | |
| } | |
| Py_ssize_t result; | |
| Py_BEGIN_CRITICAL_SECTION(self); | |
| result = data->x_exports; | |
| Py_END_CRITICAL_SECTION(); | |
| return PyLong_FromSsize_t(result); | |
| } | |
| /* Xxo type definition */ | |
| PyDoc_STRVAR(Xxo_doc, | |
| "A class that explicitly stores attributes in an internal dict"); | |
| static PyGetSetDef Xxo_getsetlist[] = { | |
| {"x_exports", Xxo_get_x_exports, NULL, NULL}, | |
| {NULL}, | |
| }; | |
| static PyType_Slot Xxo_Type_slots[] = { | |
| {Py_tp_doc, (char *)Xxo_doc}, | |
| {Py_tp_new, Xxo_new}, | |
| {Py_tp_traverse, Xxo_traverse}, | |
| {Py_tp_clear, Xxo_clear}, | |
| {Py_tp_finalize, Xxo_finalize}, | |
| {Py_tp_dealloc, Xxo_dealloc}, | |
| {Py_tp_getattro, Xxo_getattro}, | |
| {Py_tp_setattro, Xxo_setattro}, | |
| {Py_tp_methods, Xxo_methods}, | |
| {Py_bf_getbuffer, Xxo_getbuffer}, | |
| {Py_bf_releasebuffer, Xxo_releasebuffer}, | |
| {Py_tp_getset, Xxo_getsetlist}, | |
| {Py_tp_token, Py_TP_USE_SPEC}, | |
| {0, 0}, /* sentinel */ | |
| }; | |
| static PyType_Spec Xxo_Type_spec = { | |
| .name = "xxlimited.Xxo", | |
| .basicsize = -(Py_ssize_t)sizeof(XxoObject_Data), | |
| .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, | |
| .slots = Xxo_Type_slots, | |
| }; | |
| /* Str type definition*/ | |
| static PyType_Slot Str_Type_slots[] = { | |
| // slots array intentionally kept empty | |
| {0, 0}, /* sentinel */ | |
| }; | |
| static PyType_Spec Str_Type_spec = { | |
| .name = "xxlimited.Str", | |
| .basicsize = 0, | |
| .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, | |
| .slots = Str_Type_slots, | |
| }; | |
| /* Function of two integers returning integer (with C "long int" arithmetic) */ | |
| PyDoc_STRVAR(xx_foo_doc, | |
| "foo(i,j)\n\ | |
| \n\ | |
| Return the sum of i and j."); | |
| static PyObject * | |
| xx_foo(PyObject *module, PyObject *args) | |
| { | |
| long i, j; | |
| long res; | |
| if (!PyArg_ParseTuple(args, "ll:foo", &i, &j)) | |
| return NULL; | |
| res = i+j; /* XXX Do something here */ | |
| return PyLong_FromLong(res); | |
| } | |
| /* Function of no arguments returning new Xxo object. | |
| * Note that a function exposed to Python with METH_NOARGS requires an unused | |
| * second argument, so we cannot use newXxoObject directly. | |
| */ | |
| static PyObject * | |
| xx_new(PyObject *module, PyObject *Py_UNUSED(unused)) | |
| { | |
| xx_state *state = PyModule_GetState(module); | |
| return Xxo_new(state->Xxo_Type, NULL, NULL); | |
| } | |
| /* List of functions defined in the module */ | |
| static PyMethodDef xx_methods[] = { | |
| {"foo", xx_foo, METH_VARARGS, | |
| xx_foo_doc}, | |
| {"new", xx_new, METH_NOARGS, | |
| PyDoc_STR("new() -> new Xx object")}, | |
| {NULL, NULL} /* sentinel */ | |
| }; | |
| /* The module itself */ | |
| PyDoc_STRVAR(module_doc, | |
| "This is a template module just for instruction."); | |
| static int | |
| xx_modexec(PyObject *m) | |
| { | |
| xx_state *state = PyModule_GetState(m); | |
| state->Error_Type = PyErr_NewException("xxlimited.Error", NULL, NULL); | |
| if (state->Error_Type == NULL) { | |
| return -1; | |
| } | |
| if (PyModule_AddType(m, (PyTypeObject*)state->Error_Type) < 0) { | |
| return -1; | |
| } | |
| state->Xxo_Type = (PyTypeObject*)PyType_FromModuleAndSpec( | |
| m, &Xxo_Type_spec, NULL); | |
| if (state->Xxo_Type == NULL) { | |
| return -1; | |
| } | |
| if (PyModule_AddType(m, state->Xxo_Type) < 0) { | |
| return -1; | |
| } | |
| // Add the Str type. It is not needed from C code, so it is only | |
| // added to the module dict. | |
| // It does not inherit from "object" (PyObject_Type), but from "str" | |
| // (PyUnincode_Type). | |
| PyTypeObject *Str_Type = (PyTypeObject*)PyType_FromModuleAndSpec( | |
| m, &Str_Type_spec, (PyObject *)&PyUnicode_Type); | |
| if (Str_Type == NULL) { | |
| return -1; | |
| } | |
| if (PyModule_AddType(m, Str_Type) < 0) { | |
| Py_DECREF(Str_Type); | |
| return -1; | |
| } | |
| Py_DECREF(Str_Type); | |
| return 0; | |
| } | |
| // Module finalization: modules that hold references in their module state | |
| // need to implement the fullowing GC hooks. They're similar to the ones for | |
| // types (see "Xxo finalization"). | |
| static int | |
| xx_traverse(PyObject *module, visitproc visit, void *arg) | |
| { | |
| xx_state *state = PyModule_GetState_DuringGC(module); | |
| if (state == NULL) { | |
| return 0; | |
| } | |
| Py_VISIT(state->Xxo_Type); | |
| Py_VISIT(state->Error_Type); | |
| return 0; | |
| } | |
| static int | |
| xx_clear(PyObject *module) | |
| { | |
| xx_state *state = PyModule_GetState(module); | |
| if (state == NULL) { | |
| return 0; | |
| } | |
| Py_CLEAR(state->Xxo_Type); | |
| Py_CLEAR(state->Error_Type); | |
| return 0; | |
| } | |
| static void | |
| xx_free(void *module) | |
| { | |
| // allow xx_modexec to omit calling xx_clear on error | |
| (void)xx_clear((PyObject *)module); | |
| xx_state *state = PyModule_GetState(module); | |
| if (state == NULL) { | |
| return; | |
| } | |
| } | |
| // Information that CPython uses to prevent loading incompatible extenstions | |
| PyABIInfo_VAR(abi_info); | |
| static PySlot xx_slots[] = { | |
| /* Basic metadata */ | |
| PySlot_STATIC_DATA(Py_mod_name, "xxlimited"), | |
| PySlot_STATIC_DATA(Py_mod_doc, (void*)module_doc), | |
| PySlot_DATA(Py_mod_abi, &abi_info), | |
| /* The method table */ | |
| PySlot_STATIC_DATA(Py_mod_methods, xx_methods), | |
| /* exec function to initialize the module (called as part of import | |
| * after the object was added to sys.modules) | |
| */ | |
| PySlot_FUNC(Py_mod_exec, xx_modexec), | |
| /* Module state and associated functions */ | |
| PySlot_SIZE(Py_mod_state_size, sizeof(xx_state)), | |
| PySlot_FUNC(Py_mod_state_traverse, xx_traverse), | |
| PySlot_FUNC(Py_mod_state_clear, xx_clear), | |
| PySlot_FUNC(Py_mod_state_free, xx_free), | |
| /* Signal that this module supports being loaded in multiple interpreters | |
| * with separate GILs (global interpreter locks). | |
| * See "Isolating Extension Modules" on how to prepare a module for this: | |
| * https://docs.python.org/3/howto/isolating-extensions.html | |
| */ | |
| PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED), | |
| /* Signal that this module does not rely on the GIL for its own needs. | |
| * Without this slot, free-threaded builds of CPython will enable | |
| * the GIL when this module is loaded. | |
| */ | |
| PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED), | |
| PySlot_END | |
| }; | |
| /* Export function for the module. *Must* be called PyModExport_xx; usually | |
| * it is the only non-`static` object in a module definition. | |
| */ | |
| PyMODEXPORT_FUNC | |
| PyModExport_xxlimited(void) | |
| { | |
| return xx_slots; | |
| } |