From 310af91576efa024b3586e34b20f690d309769a4 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Tue, 16 Sep 2025 14:14:30 +0100 Subject: [PATCH 1/3] [WIP] Implement PyInterface prototype --- Include/Python.h | 1 + Include/cpython/object.h | 2 + Include/object.h | 5 ++ Include/pyinterface.h | 190 +++++++++++++++++++++++++++++++++++++++ Include/typeslots.h | 4 + Objects/object.c | 75 ++++++++++++++++ Objects/typeslots.inc | 1 + 7 files changed, 278 insertions(+) create mode 100644 Include/pyinterface.h diff --git a/Include/Python.h b/Include/Python.h index 261b4d316bdf858..dd524def535a599 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -80,6 +80,7 @@ __pragma(warning(disable: 4201)) #include "pyatomic.h" #include "pylock.h" #include "critical_section.h" +#include "pyinterface.h" #include "object.h" #include "refcount.h" #include "objimpl.h" diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 4e6f86f29d84730..e3681dbbb5267e0 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -239,6 +239,8 @@ struct _typeobject { * Otherwise, limited to MAX_VERSIONS_PER_CLASS (defined elsewhere). */ uint16_t tp_versions_used; + + getinterfacefunc tp_getinterface; }; #define _Py_ATTR_CACHE_UNUSED (30000) // (see tp_versions_used) diff --git a/Include/object.h b/Include/object.h index 9585f4a1d67a522..a259ce695657497 100644 --- a/Include/object.h +++ b/Include/object.h @@ -491,6 +491,11 @@ PyAPI_FUNC(void) PyObject_ClearWeakRefs(PyObject *); */ PyAPI_FUNC(PyObject *) PyObject_Dir(PyObject *); +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030F0000 +PyAPI_FUNC(int) PyObject_GetInterface(PyObject *obj, const char *intf_name, void *intf); +#endif + + /* Helpers for printing recursive container types */ PyAPI_FUNC(int) Py_ReprEnter(PyObject *); PyAPI_FUNC(void) Py_ReprLeave(PyObject *); diff --git a/Include/pyinterface.h b/Include/pyinterface.h new file mode 100644 index 000000000000000..d374977eed1319a --- /dev/null +++ b/Include/pyinterface.h @@ -0,0 +1,190 @@ +#ifndef Py_PYINTERFACE_H +#define Py_PYINTERFACE_H +#ifdef __cplusplus +extern "C" { +#endif + +/* +The PyInterface_Base struct is the generic type for actual interface data +implementations. The intent is for callers to preallocate the specific struct +and have the PyObject_GetInterface() function fill it in. + +An example of direct use of the interface API (definitions and example without +macro helpers below): + + Py_INTERFACE_VAR(PyInterface_GetAttrWChar, attr_data); + if (PyObject_GetInterface(obj, PyInterface_GetAttrWChar_Name, &attr_data) == 0) { + result = Py_INTERFACE_CALL(attr_data, getattr, L"attribute_name"); + PyInterface_Release(&attr_data); + } else { + char attr_name[128]; + wchar_to_char(attr_name, L"attribute_name"); // hypothetical converter + result = PyObject_GetAttr(obj, attr_name); + } + +Here are the key points that add value: + * the PyInterface_GetAttrWChar_Name string literal is embedded in the calling + module, making the value available when running against earlier releases of + Python (unlike a function export, which would cause a load failure). + * if the name is not available, the PyObject_GetInterface call can fail safely. + Thus, new APIs can be added in later releases, and newer compiled modules can + be fully binary compatible with older releases. + * The attr_data value contains all the information required to eventually + produce the result. It may provide fields for direct access as well as + function pointers that can calculate/copy/return results as needed. + * The interface is resolved by the object's type, but does not have to provide + identical result for every instance. It has independent lifetime from object, + (though this will often just be a new strong reference to the object). + * Static/header libraries can be used to wrap up the faster APIs, so that + extensions can adopt them simply by compiling with the latest release. + An example is shown at the end of this file. + + +Without the Py_INTERFACE_* helper macros, it would look like the below. This is +primarily for the benefit of non-C developers trying to use the API without +macros. + + PyInterface_GetAttrWChar attr_data = { sizeof(PyInterface_GetAttrWChar) }; + if (PyObject_GetInterface(obj, PyInterface_GetAttrWChar_Name, &attr_data) == 0) { + result = (*attr_data.getattr)(&attr_data, L"attribute_name"); + PyInterface_Release(&attr_data); + } ... +*/ + +#define Py_INTERFACE_VAR(t, n) t n = { sizeof(t) } +#define Py_INTERFACE_CALL(data, attr, ...) ((data).attr)(&(data), __VA_ARGS__) + +typedef struct _interfacedata { + Py_ssize_t size; + /* intf is 'struct _interfacedata' but expects the specific struct */ + int (*release)(void *intf); +} PyInterface_Base; + + +typedef int (*getinterfacefunc)(PyObject *o, const char *intf_name, void *intf); + +/* Functions to get interfaces are defined on PyObject and ... TODO */ +int PyInterface_Release(void *intf); + + +/* Some generic/example interface definitions. + +The first (PyInterface_GetAttrWChar) shows a hypothetical new API that may be added +in a later release. Rather than modifying the ABI (making newer extensions +incompatible with earlier releases), it is added as a interface. Runtimes +without the interface will return a runtime error, and the caller uses a +fallback (the example above). +*/ + +#define PyInterface_GetAttrWChar_Name "getattr-wchar" + +typedef struct _getattrwcharinterface { + PyInterface_Base base; + PyObject *(*getattr)(struct _getattrwcharinterface *, const wchar_t *attr); + int (*hasattr)(struct _getattrwcharinterface *, const wchar_t *attr); + // Strong reference to original object, but for private use only. + PyObject *_obj; +} PyInterface_GetAttrWChar; + + +/* This example (PyInterface_AsUTF8) shows an optionally-optimised API, where in some +cases the result of the API is readily available, and can be returned to +pre-allocated memory (in this case, the interface data in the stack of the caller). +The caller can then either use a fast path to access it directly, or the more +generic function (pointer) in the struct that will *always* produce a result, +but may take more computation if the fast result was not present. + + Py_INTERFACE_VAR(PyInterface_AsUTF8, intf); + if (PyObject_GetInterface(o, PyInterface_AsUTF8, &intf) == 0) { + // This check is optional, as the function calls in the interface should + // always succeed. However, developers who want to avoid an additional + // function call/allocation can do the check themselves. + // This check is defined per-interface struct, so users reference the + // docs for the specific interfaces they're using to find the check. + if (intf->s[0]) { + // use intf->s as the result. + call_next_api(intf->s); + } else { + char *s = Py_INTERFACE_CALL(intf, stralloc); + call_next_api(s); + PyMem_Free(s); + } + PyInterface_Release(&intf); + } else { ... } + +*/ + +#define PyInterface_AsUTF8_Name "as-utf8" + +typedef struct _asutf8interface { + PyInterface_Base base; + // Copy of contents if it was readily available. s[0] will be non-zero if set + char s[16]; + // Copy the characters into an existing string + size_t (*strcpy)(struct _asutf8interface *, char *dest, size_t dest_size); + // Allocate new buffer with PyMem_Malloc (use PyMem_Free to release) + char * (*stralloc)(struct _asutf8interface *); + // Optional strong reference to object, if unable to use char array + PyObject *_obj; +} PyInterface_AsUTF8; + +/* Example implementation of PyInterface_AsUTF8.strcpy: + +size_t _PyUnicode_AsUTF8Interface_strcpy(PyInterface_AsUTF8 *intf, char *dest, size_t dest_size) +{ + if (intf->_obj) { + // copy/convert data from _obj... + _internal_copy((PyUnicodeObject *)intf->_obj, dest, dest_size); + return chars_copied; + } else { + return strcpy_s(dest, dest_size, intf->_reserved); + } +} + +*/ + +/* API wrappers. + +These are inline functions (or in a static import library) to embed all the +implementation into the external module. We can change the implementation over +releases, and by using interfaces to add optional handling, the behaviour +remains compatible with earlier releases, and the sources remain compatible. + +Note that additions will usually be at the top of the function, assuming that +newer interfaces are preferred over older ones. + +static inline PyObject *PyObject_GetAttrWChar(PyObject *o, wchar_t *attr) +{ + PyObject *result = NULL; + + // Added in version N+1 + Py_INTERFACE_VAR(PyInterface_GetAttrWChar, intf); + if (PyObject_GetInterface(o, PyInterface_GetAttrWChar_Name, &intf) == 0) { + result = Py_INTERFACE_CALL(intf, getattr, attr); + if (PyInterface_Release(&intf) < 0) { + Py_DecRef(result); + return NULL; + } + return result; + } + PyErr_Clear(); + + // Original implementation in version N + PyObject *attro = PyUnicode_FromWideChar(attr, -1); + if (attro) { + result = PyObject_GetAttr(o, attro); + Py_DecRef(attro); + } + return result; +} + + +This may be defined in its own header or statically linked library, provided the +implementation ends up in the external module, not in the Python library. +*/ + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/Include/typeslots.h b/Include/typeslots.h index a7f3017ec02e92f..7019dada5caa8ce 100644 --- a/Include/typeslots.h +++ b/Include/typeslots.h @@ -94,3 +94,7 @@ /* New in 3.14 */ #define Py_tp_token 83 #endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030F0000 +/* New in 3.15 */ +#define Py_tp_getinterface 84 +#endif diff --git a/Objects/object.c b/Objects/object.c index 1f10c2531fead1b..059430776a2a03d 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2180,6 +2180,81 @@ PyObject_Dir(PyObject *obj) return (obj == NULL) ? _dir_locals() : _dir_object(obj); } + +static int +_PyInterface_GetAttrWChar_Release(PyInterface_GetAttrWChar *intf) +{ + Py_XDECREF(intf->_obj); + return 0; +} + + +static PyObject * +_PyInterface_GetAttrWChar_GetAttr(PyInterface_GetAttrWChar *intf, const wchar_t *attr) +{ + // TODO: Optimise the implementation + PyObject *result = NULL; + PyObject *attro = PyUnicode_FromWideChar(attr, -1); + if (attro) { + result = PyObject_GetAttr(intf->_obj, attro); + Py_DECREF(attro); + } + return result; +} + + +static int +_PyInterface_GetAttrWChar_HasAttr(PyInterface_GetAttrWChar *intf, const wchar_t *attr) +{ + // TODO: Optimise this implementation + int result = -1; + PyObject *attro = PyUnicode_FromWideChar(attr, -1); + if (attro) { + result = PyObject_HasAttrWithError(intf->_obj, attro); + Py_DECREF(attro); + } + return result; +} + + +static int +PyObject_GenericGetInterface(PyObject *obj, const char *intf_name, void *intf) +{ + if (0 == strcmp(intf_name, PyInterface_GetAttrWChar_Name)) { + PyInterface_GetAttrWChar *gawc = (PyInterface_GetAttrWChar *)intf; + if (gawc->base.size != sizeof(PyInterface_GetAttrWChar)) { + PyErr_SetString(PyExc_SystemError, "Invalid size struct passed to PyObject_GetInterface"); + return -1; + } + gawc->base.release = _PyInterface_GetAttrWChar_Release; + gawc->getattr = _PyInterface_GetAttrWChar_GetAttr; + gawc->hasattr = _PyInterface_GetAttrWChar_HasAttr; + gawc->_obj = Py_NewRef(obj); + return 0; + } + PyErr_SetString(PyExc_TypeError, "Interface not supported"); + return -1; +} + + +/* Abstract API for getting an interface. Delegates through the type object, + or uses PyObject_GenericGetInterface if not set. +*/ +int +PyObject_GetInterface(PyObject *obj, const char *intf_name, void *intf) +{ + if (!obj || !intf_name || !intf) { + PyErr_SetString(PyExc_SystemError, "NULL argument passed to PyObject_GetInterface"); + return -1; + } + getinterfacefunc fn = Py_TYPE(obj)->tp_getinterface; + if (fn) { + return fn(obj, intf_name, intf); + } + return PyObject_GenericGetInterface(obj, intf_name, intf); +} + + /* None is a non-NULL undefined value. There is (and should be!) no way to create other objects of this type, diff --git a/Objects/typeslots.inc b/Objects/typeslots.inc index 642160fe0bd8bcc..8e1b41cf2f2128c 100644 --- a/Objects/typeslots.inc +++ b/Objects/typeslots.inc @@ -82,3 +82,4 @@ {offsetof(PyAsyncMethods, am_send), offsetof(PyTypeObject, tp_as_async)}, {-1, offsetof(PyTypeObject, tp_vectorcall)}, {-1, offsetof(PyHeapTypeObject, ht_token)}, +{-1, offsetof(PyTypeObject, tp_getinterface)}, From f53bdc49fa62ef1346da95adccf56c6b450a2e24 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 17 Sep 2025 15:54:14 +0100 Subject: [PATCH 2/3] Switch names to integers, add test --- Include/cpython/object.h | 2 +- Include/object.h | 2 +- Include/pyinterface.h | 58 ++++++++++++++++----------- Lib/test/test_capi/test_interfaces.py | 14 +++++++ Modules/_testcapi/interfaces.c | 42 +++++++++++++++++++ Modules/_testcapi/parts.h | 1 + Modules/_testcapimodule.c | 3 ++ Objects/object.c | 42 +++++++++++++------ PCbuild/_testcapi.vcxproj | 1 + PCbuild/_testcapi.vcxproj.filters | 3 ++ 10 files changed, 131 insertions(+), 37 deletions(-) create mode 100644 Lib/test/test_capi/test_interfaces.py create mode 100644 Modules/_testcapi/interfaces.c diff --git a/Include/cpython/object.h b/Include/cpython/object.h index e3681dbbb5267e0..cdad0812c12d45f 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -240,7 +240,7 @@ struct _typeobject { */ uint16_t tp_versions_used; - getinterfacefunc tp_getinterface; + Py_getinterfacefunc tp_getinterface; }; #define _Py_ATTR_CACHE_UNUSED (30000) // (see tp_versions_used) diff --git a/Include/object.h b/Include/object.h index a259ce695657497..83cb8e80c7fdab6 100644 --- a/Include/object.h +++ b/Include/object.h @@ -492,7 +492,7 @@ PyAPI_FUNC(void) PyObject_ClearWeakRefs(PyObject *); PyAPI_FUNC(PyObject *) PyObject_Dir(PyObject *); #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030F0000 -PyAPI_FUNC(int) PyObject_GetInterface(PyObject *obj, const char *intf_name, void *intf); +PyAPI_FUNC(int) PyObject_GetInterface(PyObject *obj, void *intf); #endif diff --git a/Include/pyinterface.h b/Include/pyinterface.h index d374977eed1319a..c4254417370c96a 100644 --- a/Include/pyinterface.h +++ b/Include/pyinterface.h @@ -13,7 +13,7 @@ An example of direct use of the interface API (definitions and example without macro helpers below): Py_INTERFACE_VAR(PyInterface_GetAttrWChar, attr_data); - if (PyObject_GetInterface(obj, PyInterface_GetAttrWChar_Name, &attr_data) == 0) { + if (PyObject_GetInterface(obj, &attr_data) == 0) { result = Py_INTERFACE_CALL(attr_data, getattr, L"attribute_name"); PyInterface_Release(&attr_data); } else { @@ -23,12 +23,16 @@ macro helpers below): } Here are the key points that add value: - * the PyInterface_GetAttrWChar_Name string literal is embedded in the calling + * the PyInterface_GetAttrWChar_Name constant is embedded in the calling module, making the value available when running against earlier releases of Python (unlike a function export, which would cause a load failure). * if the name is not available, the PyObject_GetInterface call can fail safely. Thus, new APIs can be added in later releases, and newer compiled modules can be fully binary compatible with older releases. + * "names" are arbitrary ints (for fast comparison/switch statements). Core + values have high 32-bits zero - others should use a unique value as their top + 32 bits. The intent is that they are always referred to as a constant, hence, + "names" rather than "index" or similar. * The attr_data value contains all the information required to eventually produce the result. It may provide fields for direct access as well as function pointers that can calculate/copy/return results as needed. @@ -44,27 +48,36 @@ Without the Py_INTERFACE_* helper macros, it would look like the below. This is primarily for the benefit of non-C developers trying to use the API without macros. - PyInterface_GetAttrWChar attr_data = { sizeof(PyInterface_GetAttrWChar) }; - if (PyObject_GetInterface(obj, PyInterface_GetAttrWChar_Name, &attr_data) == 0) { + PyInterface_GetAttrWChar attr_data = { + .size = sizeof(PyInterface_GetAttrWChar), + .name = PyInterface_GetAttrWChar_Name + }; + if (PyObject_GetInterface(obj, &attr_data) == 0) { result = (*attr_data.getattr)(&attr_data, L"attribute_name"); PyInterface_Release(&attr_data); } ... */ -#define Py_INTERFACE_VAR(t, n) t n = { sizeof(t) } +#define Py_INTERFACE_VAR(t, n) t n = { sizeof(t), t ## _Name } #define Py_INTERFACE_CALL(data, attr, ...) ((data).attr)(&(data), __VA_ARGS__) -typedef struct _interfacedata { +typedef struct PyInterface_Base { Py_ssize_t size; - /* intf is 'struct _interfacedata' but expects the specific struct */ - int (*release)(void *intf); + uint64_t name; + /* intf is 'PyInterface_Base' but will be passed the full struct */ + int (*release)(struct PyInterface_Base *intf); + + /* Possibly: void *func_table; ??? + Pro: would allow function tables to be static, so less copying (though + could do this on a case-by-case basis anyway) + Con: additional indirection and more lossy typing */ } PyInterface_Base; -typedef int (*getinterfacefunc)(PyObject *o, const char *intf_name, void *intf); +typedef int (*Py_getinterfacefunc)(PyObject *o, PyInterface_Base *intf); /* Functions to get interfaces are defined on PyObject and ... TODO */ -int PyInterface_Release(void *intf); +PyAPI_FUNC(int) PyInterface_Release(void *intf); /* Some generic/example interface definitions. @@ -76,12 +89,12 @@ without the interface will return a runtime error, and the caller uses a fallback (the example above). */ -#define PyInterface_GetAttrWChar_Name "getattr-wchar" +#define PyInterface_GetAttrWChar_Name 1 -typedef struct _getattrwcharinterface { +typedef struct PyInterface_GetAttrWChar { PyInterface_Base base; - PyObject *(*getattr)(struct _getattrwcharinterface *, const wchar_t *attr); - int (*hasattr)(struct _getattrwcharinterface *, const wchar_t *attr); + PyObject *(*getattr)(struct PyInterface_GetAttrWChar *, const wchar_t *attr); + int (*hasattr)(struct PyInterface_GetAttrWChar *, const wchar_t *attr); // Strong reference to original object, but for private use only. PyObject *_obj; } PyInterface_GetAttrWChar; @@ -95,7 +108,7 @@ generic function (pointer) in the struct that will *always* produce a result, but may take more computation if the fast result was not present. Py_INTERFACE_VAR(PyInterface_AsUTF8, intf); - if (PyObject_GetInterface(o, PyInterface_AsUTF8, &intf) == 0) { + if (PyObject_GetInterface(o, &intf) == 0) { // This check is optional, as the function calls in the interface should // always succeed. However, developers who want to avoid an additional // function call/allocation can do the check themselves. @@ -114,16 +127,16 @@ but may take more computation if the fast result was not present. */ -#define PyInterface_AsUTF8_Name "as-utf8" +#define PyInterface_AsUTF8_Name 2 -typedef struct _asutf8interface { +typedef struct PyInterface_AsUTF8 { PyInterface_Base base; // Copy of contents if it was readily available. s[0] will be non-zero if set char s[16]; // Copy the characters into an existing string - size_t (*strcpy)(struct _asutf8interface *, char *dest, size_t dest_size); + size_t (*strcpy)(struct PyInterface_AsUTF8 *, char *dest, size_t dest_size); // Allocate new buffer with PyMem_Malloc (use PyMem_Free to release) - char * (*stralloc)(struct _asutf8interface *); + char * (*stralloc)(struct PyInterface_AsUTF8 *); // Optional strong reference to object, if unable to use char array PyObject *_obj; } PyInterface_AsUTF8; @@ -152,6 +165,8 @@ remains compatible with earlier releases, and the sources remain compatible. Note that additions will usually be at the top of the function, assuming that newer interfaces are preferred over older ones. +This may be defined in its own header or statically linked library, provided the +implementation ends up in the external module, not in the Python library. static inline PyObject *PyObject_GetAttrWChar(PyObject *o, wchar_t *attr) { @@ -159,7 +174,7 @@ static inline PyObject *PyObject_GetAttrWChar(PyObject *o, wchar_t *attr) // Added in version N+1 Py_INTERFACE_VAR(PyInterface_GetAttrWChar, intf); - if (PyObject_GetInterface(o, PyInterface_GetAttrWChar_Name, &intf) == 0) { + if (PyObject_GetInterface(o, &intf) == 0) { result = Py_INTERFACE_CALL(intf, getattr, attr); if (PyInterface_Release(&intf) < 0) { Py_DecRef(result); @@ -178,9 +193,6 @@ static inline PyObject *PyObject_GetAttrWChar(PyObject *o, wchar_t *attr) return result; } - -This may be defined in its own header or statically linked library, provided the -implementation ends up in the external module, not in the Python library. */ diff --git a/Lib/test/test_capi/test_interfaces.py b/Lib/test/test_capi/test_interfaces.py new file mode 100644 index 000000000000000..14f1cbce13b0b8c --- /dev/null +++ b/Lib/test/test_capi/test_interfaces.py @@ -0,0 +1,14 @@ +import unittest +from test.support import import_helper + + +_testcapi = import_helper.import_module('_testcapi') + + +class InterfacesTest(unittest.TestCase): + def test_interface_getattrwchar(self): + fn = _testcapi.interface_getattrwchar(_testcapi, "interface_getattrwchar") + self.assertIs(fn, _testcapi.interface_getattrwchar) + + with self.assertRaises(AttributeError): + _testcapi.interface_getattrwchar(_testcapi, 'ϼўТλФЙ') diff --git a/Modules/_testcapi/interfaces.c b/Modules/_testcapi/interfaces.c new file mode 100644 index 000000000000000..3b8886b8749e960 --- /dev/null +++ b/Modules/_testcapi/interfaces.c @@ -0,0 +1,42 @@ +#define PYTESTCAPI_NEED_INTERNAL_API + +#include "parts.h" +#include "util.h" + + +static PyObject * +interface_getattrwchar(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj = NULL; + PyObject *attro = NULL; + if (!PyArg_ParseTuple(args, "OO", &obj, &attro)) { + return NULL; + } + wchar_t *attr = PyUnicode_AsWideCharString(attro, NULL); + if (!attr) { + return NULL; + } + + Py_INTERFACE_VAR(PyInterface_GetAttrWChar, attr_data); + if (PyObject_GetInterface(obj, &attr_data) < 0) { + PyMem_Free(attr); + return NULL; + } + PyObject *result = Py_INTERFACE_CALL(attr_data, getattr, attr); + PyInterface_Release(&attr_data); + PyMem_Free(attr); + return result; +} + + +static PyMethodDef test_methods[] = { + {"interface_getattrwchar", interface_getattrwchar, METH_VARARGS}, + {NULL}, +}; + + +int +_PyTestCapi_Init_Interfaces(PyObject *m) +{ + return PyModule_AddFunctions(m, test_methods); +} diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index 32915d04bd3635c..8eabaab0d09b506 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -66,5 +66,6 @@ int _PyTestCapi_Init_Import(PyObject *mod); int _PyTestCapi_Init_Frame(PyObject *mod); int _PyTestCapi_Init_Type(PyObject *mod); int _PyTestCapi_Init_Function(PyObject *mod); +int _PyTestCapi_Init_Interfaces(PyObject *mod); #endif // Py_TESTCAPI_PARTS_H diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index c80a780e22ca348..95fb5490c116eed 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3506,6 +3506,9 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_Function(m) < 0) { return NULL; } + if (_PyTestCapi_Init_Interfaces(m) < 0) { + return NULL; + } PyState_AddModule(m, &_testcapimodule); return m; diff --git a/Objects/object.c b/Objects/object.c index 059430776a2a03d..e92bad567aeca54 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2182,9 +2182,9 @@ PyObject_Dir(PyObject *obj) static int -_PyInterface_GetAttrWChar_Release(PyInterface_GetAttrWChar *intf) +_PyInterface_GetAttrWChar_Release(PyInterface_Base *intf) { - Py_XDECREF(intf->_obj); + Py_XDECREF(((PyInterface_GetAttrWChar*)intf)->_obj); return 0; } @@ -2218,15 +2218,16 @@ _PyInterface_GetAttrWChar_HasAttr(PyInterface_GetAttrWChar *intf, const wchar_t static int -PyObject_GenericGetInterface(PyObject *obj, const char *intf_name, void *intf) +PyObject_GenericGetInterface(PyObject *obj, PyInterface_Base *intf) { - if (0 == strcmp(intf_name, PyInterface_GetAttrWChar_Name)) { - PyInterface_GetAttrWChar *gawc = (PyInterface_GetAttrWChar *)intf; - if (gawc->base.size != sizeof(PyInterface_GetAttrWChar)) { + switch (intf->name) { + case PyInterface_GetAttrWChar_Name: + if (intf->size != sizeof(PyInterface_GetAttrWChar)) { PyErr_SetString(PyExc_SystemError, "Invalid size struct passed to PyObject_GetInterface"); return -1; } - gawc->base.release = _PyInterface_GetAttrWChar_Release; + intf->release = _PyInterface_GetAttrWChar_Release; + PyInterface_GetAttrWChar *gawc = (PyInterface_GetAttrWChar *)intf; gawc->getattr = _PyInterface_GetAttrWChar_GetAttr; gawc->hasattr = _PyInterface_GetAttrWChar_HasAttr; gawc->_obj = Py_NewRef(obj); @@ -2241,17 +2242,34 @@ PyObject_GenericGetInterface(PyObject *obj, const char *intf_name, void *intf) or uses PyObject_GenericGetInterface if not set. */ int -PyObject_GetInterface(PyObject *obj, const char *intf_name, void *intf) +PyObject_GetInterface(PyObject *obj, void *intf) { - if (!obj || !intf_name || !intf) { + if (!obj || !intf) { PyErr_SetString(PyExc_SystemError, "NULL argument passed to PyObject_GetInterface"); return -1; } - getinterfacefunc fn = Py_TYPE(obj)->tp_getinterface; + + Py_getinterfacefunc fn = NULL; + if (PyType_Check(obj)) { + fn = ((PyTypeObject *)obj)->tp_getinterface; + } else { + fn = Py_TYPE(obj)->tp_getinterface; + } if (fn) { - return fn(obj, intf_name, intf); + return fn(obj, (PyInterface_Base *)intf); } - return PyObject_GenericGetInterface(obj, intf_name, intf); + return PyObject_GenericGetInterface(obj, (PyInterface_Base *)intf); +} + + +int +PyInterface_Release(void *intf) +{ + PyInterface_Base *intf_base = (PyInterface_Base *)intf; + if (intf_base && intf_base->release) { + return (*intf_base->release)(intf_base); + } + return 0; } diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj index a355a5fc25707a4..e8204c7024a8906 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -132,6 +132,7 @@ + diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters index 05128d3ac36efcc..ecf32cc1187e74b 100644 --- a/PCbuild/_testcapi.vcxproj.filters +++ b/PCbuild/_testcapi.vcxproj.filters @@ -129,6 +129,9 @@ Source Files + + Source Files + From ebd2b736d64f853c00609ac4d3a83f7117d47e02 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 17 Sep 2025 16:29:47 +0100 Subject: [PATCH 3/3] Fix linter --- Include/pyinterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/pyinterface.h b/Include/pyinterface.h index c4254417370c96a..892e329c09de1e2 100644 --- a/Include/pyinterface.h +++ b/Include/pyinterface.h @@ -4,7 +4,7 @@ extern "C" { #endif -/* +/* The PyInterface_Base struct is the generic type for actual interface data implementations. The intent is for callers to preallocate the specific struct and have the PyObject_GetInterface() function fill it in.