diff --git a/include/boost/python/module.hpp b/include/boost/python/module.hpp index f6f9fa871..4ec34868b 100644 --- a/include/boost/python/module.hpp +++ b/include/boost/python/module.hpp @@ -11,6 +11,7 @@ # define BOOST_PYTHON_MODULE BOOST_PYTHON_MODULE_INIT # if PY_VERSION_HEX >= 0x03050000 # define BOOST_PYTHON_MODULE_MULTI_PHASE BOOST_PYTHON_MODULE_MULTI_PHASE_INIT +# define BOOST_PYTHON_MODULE_WITH_STATE BOOST_PYTHON_MODULE_WITH_STATE_INIT # endif #endif // MODULE_DWA20011221_HPP diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index 0d826be65..abf1ce252 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -9,6 +9,11 @@ # include # include +# if PY_VERSION_HEX >= 0x03050000 +# include +# include +# endif + # ifndef BOOST_PYTHON_MODULE_INIT namespace boost { namespace python { @@ -51,6 +56,8 @@ BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)(), bool gil_not_us BOOST_PYTHON_DECL int exec_module(PyObject*, void(*)()); +BOOST_PYTHON_DECL int exec_module_with_state(PyObject*, void(*)(void*)); + # endif // PY_VERSION_HEX >= 0x03050000 # else // PY_VERSION_HEX >= 0x03000000 @@ -160,6 +167,44 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); return PyModuleDef_Init(&moduledef); \ } \ void BOOST_PP_CAT(init_module_, name)() +# define _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType, ...) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ + { \ + return boost::python::detail::exec_module_with_state( \ + mod, reinterpret_cast(BOOST_PP_CAT(init_module_, name)) ); \ + } \ + extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ + { \ + BOOST_STATIC_ASSERT_MSG(boost::is_pod::value, \ + "Module State MUST be a plain POD structure!"); \ + static PyModuleDef_Base initial_m_base = { \ + PyObject_HEAD_INIT(NULL) \ + 0, /* m_init */ \ + 0, /* m_index */ \ + 0 /* m_copy */ }; \ + static PyMethodDef initial_methods[] = { { 0, 0, 0, 0 } }; \ + \ + static PyModuleDef_Slot slots[] = { \ + {Py_mod_exec, reinterpret_cast(reinterpret_cast(BOOST_PP_CAT(exec_module_, name)))}, \ + {Py_mod_gil, boost::python::detail::gil_not_used_option(__VA_ARGS__) ? Py_MOD_GIL_NOT_USED : Py_MOD_GIL_USED}, \ + {0, NULL} \ + }; \ + \ + static struct PyModuleDef moduledef = { \ + initial_m_base, \ + BOOST_PP_STRINGIZE(name), \ + 0, /* m_doc */ \ + sizeof(StateType), /* m_size */ \ + initial_methods, \ + slots, /* m_slots */ \ + 0, /* m_traverse */ \ + 0, /* m_clear */ \ + 0, /* m_free */ \ + }; \ + \ + return PyModuleDef_Init(&moduledef); \ + } \ + void BOOST_PP_CAT(init_module_, name)(StateType* state) # else // ! HAS_CXX11 && Python 3.13+ # define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ @@ -196,6 +241,43 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); return PyModuleDef_Init(&moduledef); \ } \ void BOOST_PP_CAT(init_module_, name)() +# define _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ + { \ + return boost::python::detail::exec_module_with_state( \ + mod, reinterpret_cast(BOOST_PP_CAT(init_module_, name)) ); \ + } \ + extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ + { \ + BOOST_STATIC_ASSERT_MSG(boost::is_pod::value, \ + "Module State MUST be a plain POD structure!"); \ + static PyModuleDef_Base initial_m_base = { \ + PyObject_HEAD_INIT(NULL) \ + 0, /* m_init */ \ + 0, /* m_index */ \ + 0 /* m_copy */ }; \ + static PyMethodDef initial_methods[] = { { 0, 0, 0, 0 } }; \ + \ + static PyModuleDef_Slot slots[] = { \ + {Py_mod_exec, reinterpret_cast(reinterpret_cast(BOOST_PP_CAT(exec_module_, name)))}, \ + {0, NULL} \ + }; \ + \ + static struct PyModuleDef moduledef = { \ + initial_m_base, \ + BOOST_PP_STRINGIZE(name), \ + 0, /* m_doc */ \ + sizeof(StateType), /* m_size */ \ + initial_methods, \ + slots, /* m_slots */ \ + 0, /* m_traverse */ \ + 0, /* m_clear */ \ + 0, /* m_free */ \ + }; \ + \ + return PyModuleDef_Init(&moduledef); \ + } \ + void BOOST_PP_CAT(init_module_, name)(StateType* state) # endif // HAS_CXX11 && Python 3.13+ # endif // PY_VERSION_HEX >= 0x03050000 @@ -227,10 +309,16 @@ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name) # define BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name, ...) \ void BOOST_PP_CAT(init_module_,name)(); \ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name, __VA_ARGS__) +# define BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType, ...) \ + void BOOST_PP_CAT(init_module_,name)(StateType* state); \ +extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType, __VA_ARGS__) # else # define BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ void BOOST_PP_CAT(init_module_,name)(); \ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) +# define BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType) \ + void BOOST_PP_CAT(init_module_,name)(StateType* state); \ +extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType) # endif // HAS_CXX11 && Python 3.13+ # endif // PY_VERSION_HEX >= 0x03050000 diff --git a/src/module.cpp b/src/module.cpp index 22c4c5868..200b0bb78 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -26,6 +26,34 @@ namespace return m; } +# if PY_VERSION_HEX >= 0x03050000 + class init_function_with_state { + public: + init_function_with_state(void(*init_function)(void*), void* state) + : init_function_(init_function), state_(state) {} + + void operator()() const { init_function_(state_); } + private: + void(*const init_function_)(void*); + void* const state_; + }; + + PyObject* init_module_in_scope_with_state(PyObject* m, void(*init_function)(void*)) + { + if (m != 0) + { + // Create the current module scope + object m_obj(((borrowed_reference_t*)m)); + scope current_module(m_obj); + + void* state = PyModule_GetState(m); + + if (handle_exception(init_function_with_state(init_function, state))) return NULL; + } + + return m; + } +# endif } BOOST_PYTHON_DECL void scope_setattr_doc(char const* name, object const& x, char const* doc) @@ -62,6 +90,14 @@ BOOST_PYTHON_DECL int exec_module(PyObject* mod, void(*init_function)()) return retval ? 0 : -1; } +BOOST_PYTHON_DECL int exec_module_with_state(PyObject* mod, void(*init_function)(void*)) +{ + PyObject* retval = init_module_in_scope_with_state( + mod, + init_function); + return retval ? 0 : -1; +} + # endif #else diff --git a/test/fabscript b/test/fabscript index f418ed3ef..c6d834aa0 100644 --- a/test/fabscript +++ b/test/fabscript @@ -180,5 +180,7 @@ tests.append(extension_test("module_multi_phase", condition=python_version_major > 3 or (python_version_major == 3 and python_version_minor >= 5))) tests.append(extension_test("module_multi_phase_nogil", condition=python_version_major > 3 or (python_version_major == 3 and python_version_minor >= 5))) +tests.append(extension_test("module_multi_phase_state", + condition=python_version_major > 3 or (python_version_major == 3 and python_version_minor >= 5))) default = report('report', tests, fail_on_failures=True) diff --git a/test/module_multi_phase_state.cpp b/test/module_multi_phase_state.cpp new file mode 100644 index 000000000..0e497fc9c --- /dev/null +++ b/test/module_multi_phase_state.cpp @@ -0,0 +1,30 @@ +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +using namespace boost::python; + +struct TestState +{ + int x; +}; + +int get_state_x() { + TestState* state = reinterpret_cast(PyModule_GetState(import("module_multi_phase_state_ext").ptr())); + if (state != nullptr) { + return state->x; + } + return -1; +} + +BOOST_PYTHON_MODULE_WITH_STATE(module_multi_phase_state_ext, TestState) +{ + state->x = 42; + def("x", get_state_x); +} + +#include "module_tail.cpp" diff --git a/test/module_multi_phase_state.py b/test/module_multi_phase_state.py new file mode 100644 index 000000000..1df4b9623 --- /dev/null +++ b/test/module_multi_phase_state.py @@ -0,0 +1,23 @@ +# Distributed under the Boost +# Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +""" +>>> import module_multi_phase_state_ext +>>> module_multi_phase_state_ext.x() +42 +""" + +def run(args = None): + import sys + import doctest + + if args is not None: + sys.argv = args + return doctest.testmod(sys.modules.get(__name__)) + +if __name__ == '__main__': + print("running...") + import sys + status = run()[0] + if (status == 0): print("Done.") + sys.exit(status)