3

I am trying to create a C++/Qt5 program that uses an embedded Python3 interpreter for scripting and customization purposes.

This is my approach: I create a QApplication instance in C++, create a QMainwindow instance, and add a QVBoxLayout and a QPushbutton.

I then initialize the Python interpreter, import a python module containing some very basic PyQt code for adding a second QPushButton to the QVBoxLayout, and execute that code.

Then, back in C++, I call 'show()' on the QMainWindow instance and execute the QApplication.

After QApplication::exec() returnenter code heres, I try to clean up references to PyObjects and try to finalize the Python interpreter.

The call of Py_Finalize() causes a segmentation fault.

Here is the code:

#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QPushButton>
#include <QMainWindow>
#pragma push_macro("slots")
#undef slots
#include "Python.h"
#pragma pop_macro("slots")

int main(int argc, char *argv[])
{
    PyObject *pName, *pModule, *pDict, *pFunc;


    // Initialize QApplication and setup basic widgets.
    QApplication app (argc, argv);
    QMainWindow mainWindow (nullptr);
    QPushButton button ("test", &mainWindow);
    QWidget * widget = new QWidget(&mainWindow);
    mainWindow.setCentralWidget(widget);
    auto l = new QVBoxLayout ();
    widget->setLayout(l);
    l->addWidget(&button);

    // Initialize the Python Interpreter
    Py_Initialize();


    // Build the name object
    pName = PyUnicode_FromString("runPyQt");

    // Load the module object
    pModule = PyImport_Import(pName);

    if (!pModule){
        Py_Finalize();
        return 1;
    }

    pDict = PyModule_GetDict(pModule);

    pFunc = PyDict_GetItemString(pDict, "main");

    if (PyCallable_Check(pFunc))
    {
        PyObject_CallObject(pFunc, nullptr);
    } else
    {
        PyErr_Print();
    }

    mainWindow.show();

    int r = 0;

    r = QApplication::exec();

    // Clean up
    Py_DECREF(pModule);
    Py_DECREF(pName);

    // Finish the Python Interpreter
    Py_Finalize();

    return r;
}

And the Python module:

import sys
if sys.platform.startswith( 'linux' ) :
    from OpenGL import GL

from PyQt5.QtWidgets import QApplication, QPushButton, QMainWindow



def main():
    app = QApplication.instance()
    widgets = app.topLevelWidgets()
    for widget in widgets:
        if type(widget) is QMainWindow:
            break
    widget = widget.centralWidget()

    widget.layout().addWidget(QPushButton('Hello from PyQt'))

I assumed that it had something to do with not holding the GIL when finalizing the Interpreter. I tried calling PyEval_AcquireLock() right before finalizing, which causes the program to hang at that point.

Maybe PyQt somewhere acquires the lock and doesn't release it before terminating, but that is just a guess.

Up until the call of Py_Finalize() the program works exactly as I expect. Even using PyQt to add some non-trivial widgets works fine.

My question is: How do I correctly finalize the Python interpreter in this situation?

sandwood
  • 2,038
  • 20
  • 38
Arrahed
  • 101
  • 1
  • 5
  • Why do not you use http://pythonqt.sourceforge.net/? – eyllanesc Apr 13 '18 at 01:22
  • That looks very promising, thanks. However, for several reasons I am stuck with using PyQt on the Python end of the application. But I suppose the answer I am looking for is hidden somewhere in the PythonQt code. I'll try to find it. – Arrahed Apr 13 '18 at 05:56
  • I can't see anything glaringly wrong with your code at a glance, but speaking from experience these crashes are generally caused by either not holding the GIL, or incorrect reference counting (e.g. missing a `Py_DECREF` or having one where you don't need it) before calling `Py_Finalize`. Since you don't appear to release the GIL I would assume the latter. – user3419537 Apr 13 '18 at 12:30
  • Actually, I think you might need to store and `Py_DECREF` the result of a successful call to `PyObject_CallObject`. Even a function without an explicit `return` statement will return a `None` object by default. – user3419537 Apr 13 '18 at 12:41
  • @user3419537 I just tried that. It does not solve the problem. Everything works fine if I comment out the line widget.layout().addWidget(QPushButton('Hello from PyQt')) in the Python module. So maybe QApplication::exec() deletes objects that Py_finalize() still expects to be there? – Arrahed Apr 13 '18 at 12:48
  • You might need to explicitly set `widget` as the parent of the `QPushButton` so that Qt takes ownership – user3419537 Apr 13 '18 at 13:34
  • That doesn't seem to do the trick either. – Arrahed Apr 16 '18 at 13:37

0 Answers0