How to auto restart a Qt application when it crashes, within the same program?

半腔热情 提交于 2019-12-07 07:32:11

问题


Is there any relatively "standard" design to auto restart a Qt application program, when it crashes abnormally?

Specific to Windows, do I have to use any windows service?
Or if I have to write another program separately, then how to do that?


回答1:


Here's how you might do it using a single application that can act either as a monitor or as business logic. It's akin to Jon Harper's answer, except in code, not prose :)

Of Note

  1. The monitor should not instantiate a QApplication nor QGuiApplication: it has no UI. Otherwise, redundant running process indicators will appear on some platforms (i.e. OS X, Win 10).

  2. The monitor/business logic selection is achieved via setting an environment variable in the called process.

  3. Passing the monitor/business logic selection via command line arguments is problematic, as the command line switch would need to be filtered out -- doing that portably without running into corner cases is tricky.

  4. The monitor process forwards the console I/O of the business logic process, as well as the return code.

// https://github.com/KubaO/stackoverflown/tree/master/questions/appmonitor-37524491
#include <QtWidgets>
#include <cstdlib>
#if defined(Q_OS_WIN32)
#include <windows.h>
#else
static void DebugBreak() { abort(); }
#endif

static int businessLogicMain(int &argc, char **argv) {
   QApplication app{argc, argv};
   qDebug() << __FUNCTION__ << app.arguments();
   QWidget w;
   QHBoxLayout layout{&w};
   QPushButton crash{"Crash"};  // purposefully crash for testing
   QPushButton quit{"Quit"};    // graceful exit, which doesn't need restart
   layout.addWidget(&crash);
   layout.addWidget(&quit);
   w.show();

   QObject::connect(&crash, &QPushButton::clicked, DebugBreak);
   QObject::connect(&quit, &QPushButton::clicked, &QCoreApplication::quit);
   return app.exec();
}

static char const kRunLogic[] = "run__business__logic";
static char const kRunLogicValue[] = "run__business__logic";

#if defined(Q_OS_WIN32)
static QString getWindowsCommandLineArguments() {
   const wchar_t *args = GetCommandLine();
   bool oddBackslash = false, quoted = false, whitespace = false;
   // skip the executable name according to Windows command line parsing rules
   while (auto c = *args) {
      if (c == L'\\')
         oddBackslash ^= 1;
      else if (c == L'"')
         quoted ^= !oddBackslash;
      else if (c == L' ' || c == L'\t')
         whitespace = !quoted;
      else if (whitespace)
         break;
      else
         oddBackslash = false;
      args++;
   }
   return QString::fromRawData(reinterpret_cast<const QChar*>(args), lstrlen(args));
}
#endif

static int monitorMain(int &argc, char **argv) {
#if !defined(Q_OS_WIN32)
   QStringList args;
   args.reserve(argc-1);
   for (int i = 1; i < argc; ++i)
     args << QString::fromLocal8Bit(argv[i]);
#endif
   QCoreApplication app{argc, argv};
   QProcess proc;
   auto onFinished = [&](int retcode, QProcess::ExitStatus status) {
      qDebug() << status;
      if (status == QProcess::CrashExit)
         proc.start();      // restart the app if the app crashed
      else
         app.exit(retcode); // no restart required
   };
   QObject::connect(&proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), onFinished);

   auto env = QProcessEnvironment::systemEnvironment();
   env.insert(kRunLogic, kRunLogicValue);
   proc.setProgram(app.applicationFilePath()); // logic and monitor are the same executable
#if defined(Q_OS_WIN32)
   SetErrorMode(SEM_NOGPFAULTERRORBOX);        // disable Windows error reporting
   proc.setNativeArguments(getWindowsCommandLineArguments()); // pass command line arguments natively
   env.insert("QT_LOGGING_TO_CONSOLE", "1");   // ensure that the debug output gets passed along
#else
   proc.setArguments(args);
#endif
   proc.setProcessEnvironment(env);
   proc.setProcessChannelMode(QProcess::ForwardedChannels);
   proc.start();
   return app.exec();
}

int main(int argc, char **argv) {
   if (qgetenv(kRunLogic) != kRunLogicValue)
      return monitorMain(argc, argv);
   else
      return qunsetenv(kRunLogic), businessLogicMain(argc, argv);
}



回答2:


If an application crashes, it's done.

Your monitor idea is a good one, and can be achieved using QProcess. Use the "monitor" to bootstrap your actual application. To do this, implement a monitoring object with a QProcess member. In pseudocode:

 class MonitorObject : public QObject
 {
     ...
 public Q_SLOTS:
     void onStarted();
     void onFinished(int, QProcess::ExitStatus);
     ...
 private:
     QProcess m_process;
 }

Then in main:

  • Create a QCoreApplication and a monitoring object on the stack.
  • Send a queued signal to your monitor object so it knows when the main event loop starts. You can achieve this using QMetaObject::invoke with a Qt::QueuedConnection:

    int main(...)
    {
        QCoreApplication app;
        MonitorObject monitor;
    
        ... // other initialization code here
    
        QMetaObject::invoke(&monitor, "onStarted", Qt::QueuedConnection);
        return app.exec();
    }
    

And in your MonitorObject:

  • Connect QProcess's finished signal to onFinished.
  • When MonitorObject::onStarted is called, start the process.
  • When the QProcess::finished signal fires, either restart the offending program or exit, depending on the exitCode argument in the emitted signal.



回答3:


I don't know of any standard Qt method for restarting apps when they crash. But there is a nice class available which makes writing a supervisor/monitor class very easy. It is called QProcess.

You can start the process like this:

monitorClass::startProcess(QString commandLine) // e.g. "c:\mytestapp.exe param1 param2"
{
    mp_Process = new QProcess(this);
    mp_Process->start(commandLine);
    mp_Process->waitForStarted();

    // Start a timer
    mp_Timer->start(1000);
}

Then when the timer expires (every second - or whatever)

void monitorClass::TimerExpired(void)
{
    switch (mp_Process->state())
    {
        default:
        case QProcess::NotRunning:
        {
            qDebug("Process has stopped un-expectedly\n");

            // Tell the supervisor that the process has terminated
            // restart the process
            startProcess("c:\mytestapp.exe param1 param2"); // just an example
            break;
        }
        case QProcess::Starting:
        case QProcess::Running:
        {
            qDebug("Process is running ok\n");
            break;
        }
    }
}

Note

This is really pseudo code, its not a compilable example - it is just to show you roughly the how easy it is to do this with QProcess...



来源:https://stackoverflow.com/questions/37524491/how-to-auto-restart-a-qt-application-when-it-crashes-within-the-same-program

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!