Index: sources/shiboken2/libshiboken/pep384impl.cpp
--- sources/shiboken2/libshiboken/pep384impl.cpp.orig
+++ sources/shiboken2/libshiboken/pep384impl.cpp
@@ -810,6 +810,39 @@ init_PepRuntime()
         PepRuntime_38_flag = 1;
 }
 
+#ifdef Py_LIMITED_API
+static PyObject *emulatePyType_GetDict(PyTypeObject *type)
+{
+    if (_PepRuntimeVersion() < 0x030C00 || type->tp_dict) {
+        auto *res = type->tp_dict;
+        Py_XINCREF(res);
+        return res;
+    }
+    // PYSIDE-2230: Here we are really cheating. We don't know how to
+    //              access an internal dict, and so we simply pretend
+    //              it were an empty dict. This works great for our types.
+    // This was an unexpectedly simple solution :D
+    return PyDict_New();
+}
+#endif
+
+// PyType_GetDict: replacement for <static type>.tp_dict, which is
+// zero for builtin types since 3.12.
+PyObject *PepType_GetDict(PyTypeObject *type)
+{
+#if !defined(Py_LIMITED_API)
+#  if PY_VERSION_HEX >= 0x030C0000
+    return PyType_GetDict(type);
+#  else
+    // pre 3.12 fallback code, mimicking the addref-behavior.
+    Py_XINCREF(type->tp_dict);
+    return type->tp_dict;
+#  endif
+#else
+    return emulatePyType_GetDict(type);
+#endif // Py_LIMITED_API
+}
+
 /*****************************************************************************
  *
  * Module Initialization
