|
| 1 | +#ifndef Py_PYINTERFACE_H |
| 2 | +#define Py_PYINTERFACE_H |
| 3 | +#ifdef __cplusplus |
| 4 | +extern "C" { |
| 5 | +#endif |
| 6 | + |
| 7 | +/* |
| 8 | +The PyInterface_Base struct is the generic type for actual interface data |
| 9 | +implementations. The intent is for callers to preallocate the specific struct |
| 10 | +and have the PyObject_GetInterface() function fill it in. |
| 11 | +
|
| 12 | +An example of direct use of the interface API (definitions and example without |
| 13 | +macro helpers below): |
| 14 | +
|
| 15 | + Py_INTERFACE_VAR(PyInterface_GetAttrWChar, attr_data); |
| 16 | + if (PyObject_GetInterface(obj, PyInterface_GetAttrWChar_Name, &attr_data) == 0) { |
| 17 | + result = Py_INTERFACE_CALL(attr_data, getattr, L"attribute_name"); |
| 18 | + PyInterface_Release(&attr_data); |
| 19 | + } else { |
| 20 | + char attr_name[128]; |
| 21 | + wchar_to_char(attr_name, L"attribute_name"); // hypothetical converter |
| 22 | + result = PyObject_GetAttr(obj, attr_name); |
| 23 | + } |
| 24 | +
|
| 25 | +Here are the key points that add value: |
| 26 | + * the PyInterface_GetAttrWChar_Name string literal is embedded in the calling |
| 27 | + module, making the value available when running against earlier releases of |
| 28 | + Python (unlike a function export, which would cause a load failure). |
| 29 | + * if the name is not available, the PyObject_GetInterface call can fail safely. |
| 30 | + Thus, new APIs can be added in later releases, and newer compiled modules can |
| 31 | + be fully binary compatible with older releases. |
| 32 | + * The attr_data value contains all the information required to eventually |
| 33 | + produce the result. It may provide fields for direct access as well as |
| 34 | + function pointers that can calculate/copy/return results as needed. |
| 35 | + * The interface is resolved by the object's type, but does not have to provide |
| 36 | + identical result for every instance. It has independent lifetime from object, |
| 37 | + (though this will often just be a new strong reference to the object). |
| 38 | + * Static/header libraries can be used to wrap up the faster APIs, so that |
| 39 | + extensions can adopt them simply by compiling with the latest release. |
| 40 | + An example is shown at the end of this file. |
| 41 | +
|
| 42 | +
|
| 43 | +Without the Py_INTERFACE_* helper macros, it would look like the below. This is |
| 44 | +primarily for the benefit of non-C developers trying to use the API without |
| 45 | +macros. |
| 46 | +
|
| 47 | + PyInterface_GetAttrWChar attr_data = { sizeof(PyInterface_GetAttrWChar) }; |
| 48 | + if (PyObject_GetInterface(obj, PyInterface_GetAttrWChar_Name, &attr_data) == 0) { |
| 49 | + result = (*attr_data.getattr)(&attr_data, L"attribute_name"); |
| 50 | + PyInterface_Release(&attr_data); |
| 51 | + } ... |
| 52 | +*/ |
| 53 | + |
| 54 | +#define Py_INTERFACE_VAR(t, n) t n = { sizeof(t) } |
| 55 | +#define Py_INTERFACE_CALL(data, attr, ...) ((data).attr)(&(data), __VA_ARGS__) |
| 56 | + |
| 57 | +typedef struct _interfacedata { |
| 58 | + Py_ssize_t size; |
| 59 | + /* intf is 'struct _interfacedata' but expects the specific struct */ |
| 60 | + int (*release)(void *intf); |
| 61 | +} PyInterface_Base; |
| 62 | + |
| 63 | + |
| 64 | +typedef int (*getinterfacefunc)(PyObject *o, const char *intf_name, void *intf); |
| 65 | + |
| 66 | +/* Functions to get interfaces are defined on PyObject and ... TODO */ |
| 67 | +int PyInterface_Release(void *intf); |
| 68 | + |
| 69 | + |
| 70 | +/* Some generic/example interface definitions. |
| 71 | +
|
| 72 | +The first (PyInterface_GetAttrWChar) shows a hypothetical new API that may be added |
| 73 | +in a later release. Rather than modifying the ABI (making newer extensions |
| 74 | +incompatible with earlier releases), it is added as a interface. Runtimes |
| 75 | +without the interface will return a runtime error, and the caller uses a |
| 76 | +fallback (the example above). |
| 77 | +*/ |
| 78 | + |
| 79 | +#define PyInterface_GetAttrWChar_Name "getattr-wchar" |
| 80 | + |
| 81 | +typedef struct _getattrwcharinterface { |
| 82 | + PyInterface_Base base; |
| 83 | + PyObject *(*getattr)(struct _getattrwcharinterface *, const wchar_t *attr); |
| 84 | + int (*hasattr)(struct _getattrwcharinterface *, const wchar_t *attr); |
| 85 | + // Strong reference to original object, but for private use only. |
| 86 | + PyObject *_obj; |
| 87 | +} PyInterface_GetAttrWChar; |
| 88 | + |
| 89 | + |
| 90 | +/* This example (PyInterface_AsUTF8) shows an optionally-optimised API, where in some |
| 91 | +cases the result of the API is readily available, and can be returned to |
| 92 | +pre-allocated memory (in this case, the interface data in the stack of the caller). |
| 93 | +The caller can then either use a fast path to access it directly, or the more |
| 94 | +generic function (pointer) in the struct that will *always* produce a result, |
| 95 | +but may take more computation if the fast result was not present. |
| 96 | +
|
| 97 | + Py_INTERFACE_VAR(PyInterface_AsUTF8, intf); |
| 98 | + if (PyObject_GetInterface(o, PyInterface_AsUTF8, &intf) == 0) { |
| 99 | + // This check is optional, as the function calls in the interface should |
| 100 | + // always succeed. However, developers who want to avoid an additional |
| 101 | + // function call/allocation can do the check themselves. |
| 102 | + // This check is defined per-interface struct, so users reference the |
| 103 | + // docs for the specific interfaces they're using to find the check. |
| 104 | + if (intf->s[0]) { |
| 105 | + // use intf->s as the result. |
| 106 | + call_next_api(intf->s); |
| 107 | + } else { |
| 108 | + char *s = Py_INTERFACE_CALL(intf, stralloc); |
| 109 | + call_next_api(s); |
| 110 | + PyMem_Free(s); |
| 111 | + } |
| 112 | + PyInterface_Release(&intf); |
| 113 | + } else { ... } |
| 114 | +
|
| 115 | +*/ |
| 116 | + |
| 117 | +#define PyInterface_AsUTF8_Name "as-utf8" |
| 118 | + |
| 119 | +typedef struct _asutf8interface { |
| 120 | + PyInterface_Base base; |
| 121 | + // Copy of contents if it was readily available. s[0] will be non-zero if set |
| 122 | + char s[16]; |
| 123 | + // Copy the characters into an existing string |
| 124 | + size_t (*strcpy)(struct _asutf8interface *, char *dest, size_t dest_size); |
| 125 | + // Allocate new buffer with PyMem_Malloc (use PyMem_Free to release) |
| 126 | + char * (*stralloc)(struct _asutf8interface *); |
| 127 | + // Optional strong reference to object, if unable to use char array |
| 128 | + PyObject *_obj; |
| 129 | +} PyInterface_AsUTF8; |
| 130 | + |
| 131 | +/* Example implementation of PyInterface_AsUTF8.strcpy: |
| 132 | +
|
| 133 | +size_t _PyUnicode_AsUTF8Interface_strcpy(PyInterface_AsUTF8 *intf, char *dest, size_t dest_size) |
| 134 | +{ |
| 135 | + if (intf->_obj) { |
| 136 | + // copy/convert data from _obj... |
| 137 | + _internal_copy((PyUnicodeObject *)intf->_obj, dest, dest_size); |
| 138 | + return chars_copied; |
| 139 | + } else { |
| 140 | + return strcpy_s(dest, dest_size, intf->_reserved); |
| 141 | + } |
| 142 | +} |
| 143 | +
|
| 144 | +*/ |
| 145 | + |
| 146 | +/* API wrappers. |
| 147 | +
|
| 148 | +These are inline functions (or in a static import library) to embed all the |
| 149 | +implementation into the external module. We can change the implementation over |
| 150 | +releases, and by using interfaces to add optional handling, the behaviour |
| 151 | +remains compatible with earlier releases, and the sources remain compatible. |
| 152 | +
|
| 153 | +Note that additions will usually be at the top of the function, assuming that |
| 154 | +newer interfaces are preferred over older ones. |
| 155 | +
|
| 156 | +static inline PyObject *PyObject_GetAttrWChar(PyObject *o, wchar_t *attr) |
| 157 | +{ |
| 158 | + PyObject *result = NULL; |
| 159 | +
|
| 160 | + // Added in version N+1 |
| 161 | + Py_INTERFACE_VAR(PyInterface_GetAttrWChar, intf); |
| 162 | + if (PyObject_GetInterface(o, PyInterface_GetAttrWChar_Name, &intf) == 0) { |
| 163 | + result = Py_INTERFACE_CALL(intf, getattr, attr); |
| 164 | + if (PyInterface_Release(&intf) < 0) { |
| 165 | + Py_DecRef(result); |
| 166 | + return NULL; |
| 167 | + } |
| 168 | + return result; |
| 169 | + } |
| 170 | + PyErr_Clear(); |
| 171 | +
|
| 172 | + // Original implementation in version N |
| 173 | + PyObject *attro = PyUnicode_FromWideChar(attr, -1); |
| 174 | + if (attro) { |
| 175 | + result = PyObject_GetAttr(o, attro); |
| 176 | + Py_DecRef(attro); |
| 177 | + } |
| 178 | + return result; |
| 179 | +} |
| 180 | +
|
| 181 | +
|
| 182 | +This may be defined in its own header or statically linked library, provided the |
| 183 | +implementation ends up in the external module, not in the Python library. |
| 184 | +*/ |
| 185 | + |
| 186 | + |
| 187 | +#ifdef __cplusplus |
| 188 | +} |
| 189 | +#endif |
| 190 | +#endif |
0 commit comments