如何在同一程序中崩溃时自动重启 Qt 应用程序?

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

有没有比较"standard"的设计让Qt应用程序在异常崩溃时自动重启?

特定于 Windows,我必须使用任何 windows 服务吗?
或者如果我必须单独编写另一个程序,那么该怎么做?

我不知道有任何标准的 Qt 方法可以在应用程序崩溃时重新启动应用程序。但是有一个很好的 class 可用,这使得编写 supervisor/monitor class 变得非常容易。它被称为 QProcess。

您可以这样开始这个过程:

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);
}

然后当计时器到期时(每秒 - 或其他)

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;
        }
    }
}

备注

这实际上是伪代码,它不是一个可编译的示例 - 它只是向您大致展示使用 QProcess 执行此操作有多么容易...

如果应用程序崩溃,那就完了。

你的监控想法不错,可以用QProcess来实现。使用 "monitor" 到 bootstrap 您的实际应用程序。为此,使用 QProcess 成员实现监视对象。在伪代码中:

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

然后在main:

  • 在栈上创建一个QCoreApplication和一个监控对象
  • 向您的监视器对象发送排队信号,以便它知道主事件循环何时开始。您可以使用 QMetaObject::invokeQt::QueuedConnection:

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

在你的MonitorObject中:

  • QProcessfinished 信号连接到 onFinished
  • 调用MonitorObject::onStarted时,启动进程。
  • QProcess::finished 信号触发时,重新启动有问题的程序或退出,具体取决于发出的信号中的 exitCode 参数。

以下是您可以如何使用可以充当监视器或业务逻辑的单个应用程序来完成此操作。这类似于 Jon Harper 的回答,除了代码,而不是散文:)

注意事项

  1. 监视器不应实例化 QApplicationQGuiApplication:它没有 UI。否则,在某些平台(即OS X、Win 10)上会出现冗余的运行进程指示符。

  2. monitor/business逻辑选择是通过在被调用进程中设置环境变量实现的。

  3. 通过命令行参数传递 monitor/business 逻辑选择是有问题的,因为需要过滤掉命令行开关——在没有 运行 的情况下可移植地执行此操作案例很棘手。

  4. 监控进程转发业务逻辑进程的控制台I/O,以及return代码。

// https://github.com/KubaO/Whosebugn/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);
}