0

This was a problem in Qt 5.4.0. and has been fixed in Qt 5.6.0


I have an application that allows the user to launch a process with QProcess. Initially I wanted to connect the QProcess::finished signal to a lambda function, but since it is an overloaded function, it appears that it can't be done due to ambiguity of which function to connect with.

Therefore, I've experimented with monitoring the state change of QProcess.

void MainWindow::on_actionLaunchApplication_triggered()
{
    // launch the file open dialog for the user to select a file
    QString filePath = QFileDialog::getOpenFileName(this, "Select Application to Launch", "/Applications");

    if(filePath == "")
        return;

    QProcess* proc = new QProcess(this);
    // can't connect to QProcess::exited with lambda, due to its overloaded function, so will check state changed instead
    connect(proc, &QProcess::stateChanged, [filePath, proc, this](QProcess::ProcessState state){
        if(state == QProcess::NotRunning)
        {
            qDebug << "Deleting proc";
            disconnect(proc, &QProcess::stateChanged, 0 , 0);
            proc->deleteLater();
        }
    });
    proc->start(filePath);
}

Generally this works as expected; the application selected is executed and different applications can be selected to run this way, one after another. Quitting such an application results in execution of the tidyup code that deletes the QProcess.

However, if an application that has been launched with QProcess is quit and then selected again for execution, it fails to launch and instead the process is deleted immediately from the call to deleteLater in the lambda function.

So, what's going on? Considering that a new QProcess is created each time, why would it work the first time for each application, but if such an application is quit and selected to launch again, it is instantly deleted?

I'm fully aware that I can connect to QProcess::finished without a lambda function or via the SIGNAL and SLOT macros. This question is academic and I'm looking for an understanding of what's going on here.


In response to answers and comments so far, it looks like this is a Qt bug. Connecting to the QProcess::finished slot results in the same problem of an application only being launched the first time.

// launch the file open dialog for the user to select a file
QString filePath = QFileDialog::getOpenFileName(this, "Select Application to Launch", "/Applications");

if(filePath == "")
    return;

QProcess* proc = new QProcess();
connect(proc, static_cast<void (QProcess::*)(int)>(&QProcess::finished), [filePath, proc, this](int exitStatus) {
    Q_UNUSED(exitStatus);
    Log("Deleting proc for launched app");
    proc->deleteLater();
    proc->disconnect(proc, static_cast<void (QProcess::*)(int)>(&QProcess::finished), 0, 0);
});
proc->start(filePath);
TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85

2 Answers2

2

In fact, you can connect to the signal! All you have to do is to tell you compiler which signal it should choose, because it can't decide this.

There is a good answere to that problem in this question: Qt5 overloaded Signals and Slots.

This won't solve your problem with the strange delete behavior, but maybe the problem will solve itself this way.

Community
  • 1
  • 1
Felix
  • 6,885
  • 1
  • 29
  • 54
  • 1
    Thanks @Felix, that's really useful to know. Interestingly, it doesn't solve the problem and trying to launch the same application a 2nd time fails to launch. I'm starting to think it's an internal issue with Qt. – TheDarkKnight Mar 31 '16 at 09:15
1

The finished signal indicates a state transition. But instead, you're checking for a static state, not a transition.

You should keep a property related to the process to indicate that it is running or starting, and then only delete the process when it stops running or fails to start.

void MainWindow::on_actionLaunchApplication_triggered()
{
  auto locations = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
  if (locations.isEmpty())
    locations << QString();

  auto filePath = QFileDialog::getOpenFileName(this, "Select Application to Launch", 
                                               locations.first());
  if (filePath.isEmpty())
    return;

  bool wasActive = false; // upon capture, it becomes a per-process field
  auto proc = new QProcess(this);
  connect(proc, &QProcess::stateChanged, [=](QProcess::ProcessState state) mutable {
    if (state == QProcess::Running) {
      qDebug() << "Process" << proc << "is running";
      wasActive = true;
    }
    else if (state == QProcess::Starting) {
      qDebug() << "Process" << proc << "is starting";
      wasActive = true;
    }
    else if (state == QProcess::NotRunning && wasActive) {
      qDebug() << "Will delete a formerly active process" << proc;
      proc->deleteLater();
    }
    else /* if (state == QProcess::NotRunning) */
      qDebug() << "Ignoring a non-running process" << proc;
  });
  proc->start(filePath);
}
TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Thanks Kuba, I'd tried something similar, but neither that, nor your code work; the same occurs in that launching an application for the 2nd time results in the state of `QProcess::NotRunning` first and is exited. In contrast, the first time an application is launched, it enters `QProcess::Starting`followed by `QProcess::Running` and finally calls `QProcess::NotRunning` when quit. So I'm still in the dark why the states are different the 2nd time round, even though a new QProcess is instantiated. – TheDarkKnight Mar 31 '16 at 08:50
  • @TheDarkKnight I don't know if you are using Qt 5.6, but if you are, does it emit the [`errorOccurred`](http://doc.qt.io/qt-5/qprocess.html#errorOccurred) signal? – thuga Mar 31 '16 at 09:21
  • @thuga, I'm currently on 5.4, but am now downloading 5.6 and will report back. – TheDarkKnight Mar 31 '16 at 09:29
  • 1
    The issue is resolved in Qt 5.6.0. Thanks all for discussion. – TheDarkKnight Mar 31 '16 at 10:37