From b2767887a9a803406abe7174cc005a0509789cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 28 Apr 2022 21:38:27 +0200 Subject: [PATCH] Many update to have a fully isolated plugin_python. --- CMakeLists.txt | 8 +- src/proxy/CMakeLists.txt | 2 +- src/proxy/plugin_python_en.ts | 24 +++--- src/proxy/proxypython.cpp | 66 +++++++++++--- src/runner-pybind11/CMakeLists.txt | 23 ++++- src/runner-pybind11/pybind11_qt/pybind11_qt.h | 19 ++++- .../pybind11_qt/pybind11_qt_enums.h | 85 +++++++++++++++++++ src/runner-pybind11/pythonrunner.cpp | 35 +++----- src/runner-pybind11/pythonrunner.h | 17 +++- src/runner-pybind11/wrappers/pyfiletree.cpp | 2 - src/runner-pybind11/wrappers/pyplugins.cpp | 5 ++ src/runner-pybind11/wrappers/pyplugins.h | 5 ++ src/runner-pybind11/wrappers/wrappers.cpp | 15 ++-- src/runner-pybind11/wrappers/wrappers.h | 3 + 14 files changed, 245 insertions(+), 64 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ce4049..27c9638 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,17 +6,19 @@ else() include(${CMAKE_CURRENT_LIST_DIR}/../cmake_common/mo2.cmake) endif() +set(PYTHON_BUILD_PATH ${PYTHON_ROOT}/PCBuild/amd64) +set(PYVERSION 310) + set(PYBIND11_FINDPYTHON true) -set(PYTHON_EXECUTABLE ${PYTHON_ROOT}/PCBuild/amd64/python.exe) +set(PYTHON_EXECUTABLE ${PYTHON_BUILD_PATH}/python.exe) set(PYTHON_INCLUDE_DIRS ${PYTHON_ROOT}/Include) -set(PYTHON_LIBRARIES ${PYTHON_ROOT}/PCBuild/amd64/python310.lib) +set(PYTHON_LIBRARIES ${MO2_INSTALL_LIBS_PATH}/python${PYVERSION}.lib) add_subdirectory(pybind11) project(plugin_python) # order matters! -# add_subdirectory(src/runner) add_subdirectory(src/runner-pybind11) add_subdirectory(src/proxy) diff --git a/src/proxy/CMakeLists.txt b/src/proxy/CMakeLists.txt index 8080d9c..54202a7 100644 --- a/src/proxy/CMakeLists.txt +++ b/src/proxy/CMakeLists.txt @@ -5,4 +5,4 @@ mo2_configure_plugin(plugin_python WARNINGS OFF EXTRA_TRANSLATIONS ${CMAKE_CURRENT_SOURCE_DIR}/../runner) target_link_libraries(plugin_python PRIVATE pythonrunner) -mo2_install_target(plugin_python) +mo2_install_target(plugin_python FOLDER) diff --git a/src/proxy/plugin_python_en.ts b/src/proxy/plugin_python_en.ts index 99302dd..f47d583 100644 --- a/src/proxy/plugin_python_en.ts +++ b/src/proxy/plugin_python_en.ts @@ -4,58 +4,58 @@ ProxyPython - + Python Proxy - + Proxy Plugin to allow plugins written in python to be loaded - + ModOrganizer path contains a semicolon - + Python DLL not found - + Invalid Python DLL - + Initializing Python failed - - + + invalid problem key %1 - + The path to Mod Organizer (%1) contains a semicolon. <br>While this is legal on NTFS drives, many softwares do not handle it correctly.<br>Unfortunately MO depends on libraries that seem to fall into that group.<br>As a result the python plugin cannot be loaded, and the only solution we canoffer is to remove the semicolon or move MO to a path without a semicolon. - + The Python plugin DLL was not found, maybe your antivirus deleted it. Re-installing MO2 might fix the problem. - + The Python plugin DLL is invalid, maybe your antivirus is blocking it. Re-installing MO2 and adding exclusions for it to your AV might fix the problem. - + The initialization of the Python plugin DLL failed, unfortunately without any details. diff --git a/src/proxy/proxypython.cpp b/src/proxy/proxypython.cpp index 035063c..354b9d8 100644 --- a/src/proxy/proxypython.cpp +++ b/src/proxy/proxypython.cpp @@ -16,19 +16,41 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with python proxy plugin. If not, see . */ - #include "proxypython.h" -#include "log.h" + +#include + #include #include #include #include #include + +#include "log.h" #include #include +namespace fs = std::filesystem; using namespace MOBase; +// retrieve the path to the folder containing the proxy DLL +fs::path getPluginFolder() +{ + wchar_t path[MAX_PATH]; + HMODULE hm = NULL; + + if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCWSTR)&getPluginFolder, &hm) == 0) { + return {}; + } + if (GetModuleFileName(hm, path, sizeof(path)) == 0) { + return {}; + } + + return fs::path(path).parent_path(); +} + ProxyPython::ProxyPython() : m_MOInfo{nullptr}, m_RunnerLib{nullptr}, m_Runner{nullptr}, m_LoadFailure(FailureType::NONE) @@ -50,15 +72,32 @@ bool ProxyPython::init(IOrganizer* moInfo) return true; } + const auto pluginFolder = getPluginFolder(); + + if (pluginFolder.empty()) { + DWORD error = ::GetLastError(); + m_LoadFailure = FailureType::DLL_NOT_FOUND; + log::error("failed to resolve Python proxy directory ({}): {}", error, + qUtf8Printable(windowsErrorString(::GetLastError()))); + return false; + } + // load the pythonrunner library - m_RunnerLib = ::LoadLibraryW( - QDir::toNativeSeparators(IOrganizer::getPluginDataPath() + "/pythonRunner.dll") - .toStdWString() - .c_str()); + const auto dllPaths = pluginFolder / "dlls"; + if (SetDllDirectoryW(dllPaths.c_str()) == 0) { + DWORD error = ::GetLastError(); + m_LoadFailure = FailureType::DLL_NOT_FOUND; + log::error("failed to add python DLL directory ({}): {}", error, + qUtf8Printable(windowsErrorString(::GetLastError()))); + return false; + } + + const auto runnerPath = pluginFolder / "pythonrunner.dll"; + m_RunnerLib = ::LoadLibraryW(runnerPath.c_str()); if (!m_RunnerLib) { DWORD error = ::GetLastError(); - log::error("failed to load python runner ({}): {}", + log::error("failed to load python runner ({}): {}", error, qUtf8Printable(windowsErrorString(error))); if (error == ERROR_MOD_NOT_FOUND) { m_LoadFailure = FailureType::DLL_NOT_FOUND; @@ -95,17 +134,20 @@ bool ProxyPython::init(IOrganizer* moInfo) if (m_MOInfo) { m_MOInfo->setPersistent(name(), "tryInit", true); + + m_Runner = std::unique_ptr{createPythonRunner()}; } - m_Runner = std::unique_ptr{createPythonRunner()}; + if (!m_Runner->initialize(pluginFolder / "libs")) { + m_LoadFailure = FailureType::INITIALIZATION; + } if (m_MOInfo) { m_MOInfo->setPersistent(name(), "tryInit", false); } - if (!m_Runner) { - m_LoadFailure = FailureType::INITIALIZATION; - } + // reset DLL directory + SetDllDirectoryW(NULL); return true; } @@ -184,7 +226,7 @@ std::vector ProxyPython::activeProblems() const auto failure = m_LoadFailure; // don't know how this could happen but wth - if (m_Runner && !m_Runner->isPythonInitialized()) { + if (m_Runner && !m_Runner->isInitialized()) { failure = FailureType::INITIALIZATION; } diff --git a/src/runner-pybind11/CMakeLists.txt b/src/runner-pybind11/CMakeLists.txt index ae9bc41..3800ff1 100644 --- a/src/runner-pybind11/CMakeLists.txt +++ b/src/runner-pybind11/CMakeLists.txt @@ -19,4 +19,25 @@ target_include_directories(pythonrunner PRIVATE ${PYTHON_ROOT}/Include) # this is kind of broken but it only works with this... target_compile_definitions(pythonrunner PRIVATE QT_NO_KEYWORDS PYTHONRUNNER_LIBRARY) -mo2_install_target(pythonrunner INSTALLDIR bin/plugins/data) +mo2_install_target(pythonrunner INSTALLDIR bin/plugins/plugin_python) + +# install DLLs files needed +set(DLL_DIRS ${MO2_INSTALL_PATH}/bin/plugins/plugin_python/dlls) +file(GLOB dlls_to_install + ${PYTHON_BUILD_PATH}/libffi*.dll + ${PYTHON_BUILD_PATH}/python${PYVERSION}.dll) +install(FILES ${dlls_to_install} DESTINATION ${DLL_DIRS}) + +# install Python files +set(PYLIB_DIR ${MO2_INSTALL_PATH}/bin/plugins/plugin_python/libs) +file(GLOB libs_to_install ${PYTHON_BUILD_PATH}/pythoncore/*.pyd) +install(FILES ${libs_to_install} DESTINATION ${PYLIB_DIR}) +install(FILES ${PYTHON_BUILD_PATH}/pythoncore/python${PYVERSION}.zip + DESTINATION ${PYLIB_DIR} RENAME pythoncore.zip) + +# install PyQt6 +file(GLOB PYQT_DIR ${MO2_BUILD_PATH}/PyQt${QT_MAJOR_VERSION}*) +set(PYQT_LIB_DIR ${PYQT_DIR}/Lib/site-packages/PyQt${QT_MAJOR_VERSION}) +set(PYQT_TARGET_DIR ${PYLIB_DIR}/PyQt${QT_MAJOR_VERSION}) +file(GLOB pyqt_files ${PYQT_LIB_DIR}/*.py ${PYQT_LIB_DIR}/*.pyd ${PYQT_LIB_DIR}/*.pyi) +install(FILES ${pyqt_files} DESTINATION ${PYQT_TARGET_DIR}) diff --git a/src/runner-pybind11/pybind11_qt/pybind11_qt.h b/src/runner-pybind11/pybind11_qt/pybind11_qt.h index 206d7c7..9091d3a 100644 --- a/src/runner-pybind11/pybind11_qt/pybind11_qt.h +++ b/src/runner-pybind11/pybind11_qt/pybind11_qt.h @@ -1,6 +1,21 @@ #ifndef PYTHON_PYBIND11_QT_HPP #define PYTHON_PYBIND11_QT_HPP +// this header defines many type casters for Qt types, including: +// - basic Qt types such as QString and QVariant - those do not have PyQt6 equivalent +// - QFlags<> class templates +// - containers such as QList<>, QSet<>, etc., the QList<> casters is more flexible than +// the std::vector<> or std::list<> ones as it accepts any iterable +// - many Qt enumeration types (see pybind11_qt_enums) +// - many Qt classes with PyQt6 equivalent +// - copyable type are copied between Python and C++ +// - non-copyable type (QObject, QWidget, QMainWindow) are always owned by the C++ +// side, even when constructed on the Python side, and owned their corresponding +// Python object, e.g., an instance of a class inheriting QWidget created on the +// Python side can be safely used in C++ since the Python object will be owned by +// the C++ QWidget object +// + #include "pybind11_qt_basic.h" #include "pybind11_qt_containers.h" #include "pybind11_qt_enums.h" @@ -17,7 +32,7 @@ namespace pybind11::qt { * @param owner QObject that will own the python object. * @param child Python object that the QObject will own. */ - inline void set_owner(QObject* owner, object child) + inline void set_qt_owner(QObject* owner, object child) { new detail::qt::qobject_holder{owner, child}; } @@ -31,7 +46,7 @@ namespace pybind11::qt { * @param object Object to tie. */ template - void set_owner(Class* object) + void set_qt_owner(Class* object) { static_assert(std::is_base_of_v); new detail::qt::qobject_holder{object}; diff --git a/src/runner-pybind11/pybind11_qt/pybind11_qt_enums.h b/src/runner-pybind11/pybind11_qt/pybind11_qt_enums.h index b6e6483..e6cdfe0 100644 --- a/src/runner-pybind11/pybind11_qt/pybind11_qt_enums.h +++ b/src/runner-pybind11/pybind11_qt/pybind11_qt_enums.h @@ -22,8 +22,93 @@ namespace pybind11::detail { + PYQT_ENUM(QtCore, Qt::AlignmentFlag); + PYQT_ENUM(QtCore, Qt::AnchorPoint); + PYQT_ENUM(QtCore, Qt::ApplicationAttribute); + PYQT_ENUM(QtCore, Qt::ApplicationState); + PYQT_ENUM(QtCore, Qt::ArrowType); + PYQT_ENUM(QtCore, Qt::AspectRatioMode); + PYQT_ENUM(QtCore, Qt::Axis); + PYQT_ENUM(QtCore, Qt::BGMode); + PYQT_ENUM(QtCore, Qt::BrushStyle); + PYQT_ENUM(QtCore, Qt::CaseSensitivity); + PYQT_ENUM(QtCore, Qt::CheckState); + PYQT_ENUM(QtCore, Qt::ChecksumType); + PYQT_ENUM(QtCore, Qt::ClipOperation); + PYQT_ENUM(QtCore, Qt::ConnectionType); + PYQT_ENUM(QtCore, Qt::ContextMenuPolicy); + PYQT_ENUM(QtCore, Qt::CoordinateSystem); + PYQT_ENUM(QtCore, Qt::Corner); + PYQT_ENUM(QtCore, Qt::CursorMoveStyle); + PYQT_ENUM(QtCore, Qt::CursorShape); + PYQT_ENUM(QtCore, Qt::DateFormat); + PYQT_ENUM(QtCore, Qt::DayOfWeek); + PYQT_ENUM(QtCore, Qt::DockWidgetArea); + PYQT_ENUM(QtCore, Qt::DropAction); + PYQT_ENUM(QtCore, Qt::Edge); + PYQT_ENUM(QtCore, Qt::EnterKeyType); + PYQT_ENUM(QtCore, Qt::EventPriority); + PYQT_ENUM(QtCore, Qt::FillRule); + PYQT_ENUM(QtCore, Qt::FindChildOption); + PYQT_ENUM(QtCore, Qt::FocusPolicy); + PYQT_ENUM(QtCore, Qt::FocusReason); + PYQT_ENUM(QtCore, Qt::GestureFlag); + PYQT_ENUM(QtCore, Qt::GestureState); + PYQT_ENUM(QtCore, Qt::GestureType); PYQT_ENUM(QtCore, Qt::GlobalColor); + PYQT_ENUM(QtCore, Qt::HitTestAccuracy); + PYQT_ENUM(QtCore, Qt::ImageConversionFlag); + PYQT_ENUM(QtCore, Qt::InputMethodHint); + PYQT_ENUM(QtCore, Qt::InputMethodQuery); + PYQT_ENUM(QtCore, Qt::ItemDataRole); + PYQT_ENUM(QtCore, Qt::ItemFlag); + PYQT_ENUM(QtCore, Qt::ItemSelectionMode); + PYQT_ENUM(QtCore, Qt::ItemSelectionOperation); + PYQT_ENUM(QtCore, Qt::Key); + PYQT_ENUM(QtCore, Qt::KeyboardModifier); + PYQT_ENUM(QtCore, Qt::LayoutDirection); + PYQT_ENUM(QtCore, Qt::MaskMode); + PYQT_ENUM(QtCore, Qt::MatchFlag); + PYQT_ENUM(QtCore, Qt::Modifier); + PYQT_ENUM(QtCore, Qt::MouseButton); + PYQT_ENUM(QtCore, Qt::MouseEventFlag); + PYQT_ENUM(QtCore, Qt::MouseEventSource); + PYQT_ENUM(QtCore, Qt::NativeGestureType); + PYQT_ENUM(QtCore, Qt::NavigationMode); + PYQT_ENUM(QtCore, Qt::Orientation); + PYQT_ENUM(QtCore, Qt::PenCapStyle); + PYQT_ENUM(QtCore, Qt::PenJoinStyle); + PYQT_ENUM(QtCore, Qt::PenStyle); + PYQT_ENUM(QtCore, Qt::ScreenOrientation); + PYQT_ENUM(QtCore, Qt::ScrollBarPolicy); + PYQT_ENUM(QtCore, Qt::ScrollPhase); + PYQT_ENUM(QtCore, Qt::ShortcutContext); + PYQT_ENUM(QtCore, Qt::SizeHint); + PYQT_ENUM(QtCore, Qt::SizeMode); + PYQT_ENUM(QtCore, Qt::SortOrder); + PYQT_ENUM(QtCore, Qt::TabFocusBehavior); + PYQT_ENUM(QtCore, Qt::TextElideMode); + PYQT_ENUM(QtCore, Qt::TextFlag); + PYQT_ENUM(QtCore, Qt::TextFormat); + PYQT_ENUM(QtCore, Qt::TextInteractionFlag); + PYQT_ENUM(QtCore, Qt::TileRule); + PYQT_ENUM(QtCore, Qt::TimeSpec); + PYQT_ENUM(QtCore, Qt::TimerType); + PYQT_ENUM(QtCore, Qt::ToolBarArea); + PYQT_ENUM(QtCore, Qt::ToolButtonStyle); + PYQT_ENUM(QtCore, Qt::TransformationMode); + PYQT_ENUM(QtCore, Qt::WhiteSpaceMode); + PYQT_ENUM(QtCore, Qt::WidgetAttribute); + PYQT_ENUM(QtCore, Qt::WindowFrameSection); + PYQT_ENUM(QtCore, Qt::WindowModality); + PYQT_ENUM(QtCore, Qt::WindowState); + PYQT_ENUM(QtCore, Qt::WindowType); + + PYQT_ENUM(QtWidgets, QMessageBox::ButtonRole); + PYQT_ENUM(QtWidgets, QMessageBox::DialogCode); PYQT_ENUM(QtWidgets, QMessageBox::Icon); + PYQT_ENUM(QtWidgets, QMessageBox::PaintDeviceMetric); + PYQT_ENUM(QtWidgets, QMessageBox::RenderFlag); PYQT_ENUM(QtWidgets, QMessageBox::StandardButton); } // namespace pybind11::detail diff --git a/src/runner-pybind11/pythonrunner.cpp b/src/runner-pybind11/pythonrunner.cpp index df2bf79..95b0786 100644 --- a/src/runner-pybind11/pythonrunner.cpp +++ b/src/runner-pybind11/pythonrunner.cpp @@ -279,13 +279,11 @@ public: PythonRunner() = default; ~PythonRunner() = default; - bool initPython(); + QList load(const QString& identifier) override; + void unload(const QString& identifier) override; - QList load(const QString& identifier); - void unload(const QString& identifier); - - bool isPythonInitialized() const; - bool isPythonVersionSupported() const; + bool initialize(std::filesystem::path const& libpath) override; + bool isInitialized() const override; private: /** @@ -302,13 +300,7 @@ private: IPythonRunner* CreatePythonRunner() { - std::unique_ptr result = std::make_unique(); - if (result->initPython()) { - return result.release(); - } - else { - return nullptr; - } + return new PythonRunner(); } PYBIND11_MODULE(moprivate, m) @@ -318,7 +310,7 @@ PYBIND11_MODULE(moprivate, m) mo2::python::add_make_tree_function(m); } -bool PythonRunner::initPython() +bool PythonRunner::initialize(std::filesystem::path const& libpath) { // we only initialize Python once for the whole lifetime of the program, even if MO2 // is restarted and the proxy or PythonRunner objects are deleted and recreated, @@ -340,10 +332,10 @@ bool PythonRunner::initPython() static const char* argv0 = "ModOrganizer.exe"; // initialize the core Path of Python, this must be done before initialization - const QStringList paths = { - QCoreApplication::applicationDirPath() + "/pythoncore.zip", - QCoreApplication::applicationDirPath() + "/pythoncore", - IOrganizer::getPluginDataPath()}; + // + const QStringList paths{ + QFileInfo(libpath / "pythoncore.zip").absoluteFilePath(), + QFileInfo(libpath).absoluteFilePath(), IOrganizer::getPluginDataPath()}; Py_SetPath(paths.join(';').toStdWString().c_str()); if (PyImport_AppendInittab("mobase", &PyInit_mobase) == -1) { @@ -508,11 +500,6 @@ QList PythonRunner::load(const QString& identifier) identifier); } - // tie the lifetime of the Python object to the lifetime of the QObject - for (auto* object : interfaceList) { - py::qt::set_owner(object, pluginObj); - } - // Append the plugins to the main list: allInterfaceList.append(interfaceList); } @@ -572,7 +559,7 @@ void PythonRunner::unload(const QString& identifier) } } -bool PythonRunner::isPythonInitialized() const +bool PythonRunner::isInitialized() const { return Py_IsInitialized() != 0; } diff --git a/src/runner-pybind11/pythonrunner.h b/src/runner-pybind11/pythonrunner.h index 5be80c7..5ebef40 100644 --- a/src/runner-pybind11/pythonrunner.h +++ b/src/runner-pybind11/pythonrunner.h @@ -1,18 +1,29 @@ #ifndef PYTHONRUNNER_H #define PYTHONRUNNER_H +#include +#include + #include #include + #include #include -#include class IPythonRunner { public: virtual QList load(const QString& identifier) = 0; virtual void unload(const QString& identifier) = 0; - virtual bool isPythonInitialized() const = 0; + // initialize Python + // + // libpath should be the folder containing the Python library (pythonxxx.zip, etc.) + // + virtual bool initialize(std::filesystem::path const& libpath) = 0; + + // check if the runner has been initialized, i.e., initialize() has been called and + // succeeded + virtual bool isInitialized() const = 0; virtual ~IPythonRunner() {} }; @@ -23,6 +34,8 @@ public: #define PYDLLEXPORT Q_DECL_IMPORT #endif // PYTHONRUNNER_LIBRARY +// create the Python runner +// extern "C" PYDLLEXPORT IPythonRunner* CreatePythonRunner(); #endif // PYTHONRUNNER_H diff --git a/src/runner-pybind11/wrappers/pyfiletree.cpp b/src/runner-pybind11/wrappers/pyfiletree.cpp index 5712bda..c77a00b 100644 --- a/src/runner-pybind11/wrappers/pyfiletree.cpp +++ b/src/runner-pybind11/wrappers/pyfiletree.cpp @@ -308,8 +308,6 @@ namespace mo2::python { // Special methods: iFileTreeClass.def("__getitem__", py::overload_cast(&IFileTree::at)); - // , py::return_value_policy>() iFileTreeClass.def("__iter__", [](IFileTree* tree) { return py::make_iterator(*tree); diff --git a/src/runner-pybind11/wrappers/pyplugins.cpp b/src/runner-pybind11/wrappers/pyplugins.cpp index f100100..9d2103b 100644 --- a/src/runner-pybind11/wrappers/pyplugins.cpp +++ b/src/runner-pybind11/wrappers/pyplugins.cpp @@ -260,6 +260,11 @@ namespace mo2::python { helper.append_if_instance(plugin_obj); } + // tie the lifetime of the Python object to the lifetime of the QObject + for (auto* object : helper.objects) { + py::qt::set_qt_owner(object, plugin_obj); + } + return helper.objects; } diff --git a/src/runner-pybind11/wrappers/pyplugins.h b/src/runner-pybind11/wrappers/pyplugins.h index 03a1b53..e80d2f1 100644 --- a/src/runner-pybind11/wrappers/pyplugins.h +++ b/src/runner-pybind11/wrappers/pyplugins.h @@ -28,6 +28,11 @@ namespace mo2::python { public: using PluginBase::PluginBase; + ~PyPluginBaseNoFinal() + { + std::cout << "~PyPluginBaseNoFinal() " << typeid(this).name() << std::endl; + } + bool init(IOrganizer* organizer) override { PYBIND11_OVERRIDE_PURE(bool, PluginBase, init, organizer); diff --git a/src/runner-pybind11/wrappers/wrappers.cpp b/src/runner-pybind11/wrappers/wrappers.cpp index adbe275..42e04e2 100644 --- a/src/runner-pybind11/wrappers/wrappers.cpp +++ b/src/runner-pybind11/wrappers/wrappers.cpp @@ -20,7 +20,6 @@ using namespace MOBase; namespace mo2::python { - // this can be extended in C++, so why not in Python class PyPluginRequirement : public IPluginRequirement { public: std::optional check(IOrganizer* organizer) const override @@ -30,7 +29,6 @@ namespace mo2::python { }; }; - // this needs to be extendable in Python, so actually needs a wrapper class PySaveGame : public ISaveGame { public: QString getFilepath() const override @@ -57,6 +55,8 @@ namespace mo2::python { { PYBIND11_OVERRIDE_PURE(QStringList, ISaveGame, allFiles, ); } + + ~PySaveGame() { std::cout << "~PySaveGame()" << std::endl; } }; class PySaveGameInfoWidget : public ISaveGameInfoWidget { @@ -68,11 +68,14 @@ namespace mo2::python { { PYBIND11_OVERRIDE_PURE(void, ISaveGameInfoWidget, setSave, &save); } + + ~PySaveGameInfoWidget() { std::cout << "~PySaveGameInfoWidget()" << std::endl; } }; void add_wrapper_bindings(pybind11::module_ m) { - // ISaveGame + // ISaveGame - custom type_caster<> for shared_ptr<> to keep the Python object + // alive when returned from Python (see shared_cpp_owner.h) py::class_>(m, "ISaveGame") .def(py::init<>()) @@ -82,7 +85,8 @@ namespace mo2::python { .def("getSaveGroupIdentifier", &ISaveGame::getSaveGroupIdentifier) .def("allFiles", &ISaveGame::allFiles); - // ISaveGameInfoWidget + // ISaveGameInfoWidget - custom holder to keep the Python object alive alongside + // the widget py::class_> @@ -92,7 +96,8 @@ namespace mo2::python { .def("setSave", &ISaveGameInfoWidget::setSave, py::arg("save")); py::qt::add_qt_delegate(iSaveGameInfoWidget, "_widget"); - // IPluginRequirement + // IPluginRequirement - custom type_caster<> for shared_ptr<> to keep the Python + // object alive when returned from Python (see shared_cpp_owner.h) py::class_, PyPluginRequirement> diff --git a/src/runner-pybind11/wrappers/wrappers.h b/src/runner-pybind11/wrappers/wrappers.h index bef34c8..e097479 100644 --- a/src/runner-pybind11/wrappers/wrappers.h +++ b/src/runner-pybind11/wrappers/wrappers.h @@ -51,6 +51,9 @@ namespace mo2::python { * @brief Extract plugins from the given object. For each plugin implemented, an * object is returned. * + * The returned QObject* are set as owner of the given object so that the Python + * object lifetime does not end immediately after returning to C++. + * * @param object Python object to extract plugins from. * * @return a QObject* for each plugin implemented by the given object.