【问题标题】:Creating QT Application as GUI for existing console-based application on windows在 Windows 上为现有的基于控制台的应用程序创建 QT 应用程序作为 GUI
【发布时间】:2017-12-25 16:02:42
【问题描述】:

我正在尝试使用 Qt 为要在 Windows 命令行中运行的现有应用程序设置 GUI。它不仅仅是运行应用程序 system() 命令,但我需要通过命令行与现有应用程序交互。

当我启动现有的可执行文件时,system() 命令会阻止 GUI。如何在后台运行此可执行文件并通过我自己的 GUI 元素(例如按钮)触发一些输入?

我想为我的一些同事简化这个命令行工具的使用。

它将主要用于Windows。

感谢您的帮助。

【问题讨论】:

  • 看看QProcess
  • 谢谢,我认为这将适合我的阅读/写作渠道问题。
  • 从它的声音来看,我不确定 QProcess 是否足以满足您的需求。您必须重构控制台应用程序,以便抽象出它的输入源和输出目标。在一种情况下,您可以同时使用控制台。在 GUI 应用案例中,GUI 的各种元素将充当输入源和输出目标。希望我没有过度考虑您的需求。祝你好运。
  • 我不确定 Qt,但是当我们在 Windows 上执行此操作时(使用 GTK+,但这不是问题),我们遇到了父进程被阻塞直到子进程完成的问题。 (我们通过在一个额外的线程中生成来解决它。)我们还遇到了在子进程完成之前没有收到子进程的 std 输出的问题。 (我忘记了我们是如何修复它的。)如果你在为 Qt 解决它时能留下一个关于这个的注释,我会很高兴。 (同时我们也改用 Qt,我对此很好奇......)
  • @Scheff 在 Qt 中,QProcess 基于事件,默认情况下不阻塞父进程。如果你想以阻塞方式运行它或者没有 Qt 事件循环,你也有一个同步 API。我认为该课程可以通过异步行为涵盖此处的所有问题需求。

标签: c++ qt shell user-interface command-line


【解决方案1】:

我找到了满足我需求的解决方案,并且可以做我想做的事。实际上我有点失望。我认为它会更复杂。

首先我不得不说它是一个 QtQuick 应用程序。也许我应该早点说。

我只是将流程功能外包给了另一个班级。此类通过qmlRegisterType<>() 函数传递给QML。我将来自进程(QProcess)的一些信号连接到我自己的类中的插槽,并编写了自己的函数来处理从控制台应用程序读取/写入数据的函数。使用 QML-onClicked 事件,我可以将我的参数和字符串传递给控制台应用程序。通过一些应用程序逻辑,我可以处理输入/输出请求和时间。

WrapperClass.h

class WrapperClass: public QObject
{
    Q_OBJECT

public:
    explicit WrapperClass(QObject *parent = nullptr);

    QProcess *process;
    QString str_proc_output;

    Q_INVOKABLE void startProcess();
    Q_INVOKABLE void stopProcess();

    Q_INVOKABLE QString getOutput();
    Q_INVOKABLE void writeByte(QString str);


    Q_INVOKABLE QString getAllOutput();
private:

signals:

public slots:
    void mReadyRead();
    void mReadyReadStandardOutput();
    void mFinished(int code);
    void mBytesWritten(qint64 written);

};

WrapperClass.cpp

WrapperClass::WrapperClass(QObject *parent) : QObject(parent)
{
    process = new QProcess();
    process->setProgram("untitled.exe");
    process->setProcessChannelMode(QProcess::MergedChannels);

    str_proc_output = "";

    connect(process, SIGNAL(readyRead()), this, SLOT(mReadyRead()));
    connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(mReadyReadStandardOutput()));
    connect(process, SIGNAL(finished(int)), this, SLOT(mFinished(int)));
    connect(process, SIGNAL(bytesWritten(qint64)), this, SLOT(mBytesWritten(qint64)));

}

void WrapperClass::startProcess() {
    if(process->state() == QProcess::Running) {
        stopProcess();
    } else {
        process->open(QProcess::ReadWrite);
    }
}

void WrapperClass::stopProcess() {
    process->close();
}



QString WrapperClass::getOutput() {
    return str_proc_output;
}


QString WrapperClass::getAllOutput() {
    QString str = process->readAll();

    std::cout << str.toStdString() << std::endl;
    return str;
}


void WrapperClass::writeByte(QString str) {

    char cArr[str.length()] = {};

    memcpy(cArr, str.toStdString().c_str(), str.length());

    QByteArray arr = QByteArray(cArr, -1);
    process->write(arr);
}




void WrapperClass::mReadyRead() {
    QString s = QString(process->readAll());

    std::cout << "ReadyRead: " << s.toStdString() << std::endl;
    str_proc_output = s;
}

void WrapperClass::mReadyReadStandardOutput() {
    QString s = QString(process->readAllStandardOutput());

    std::cout << "ReadyReadStandardOutput: " << s.toStdString() << std::endl;

}

void WrapperClass::mFinished(int code) {
    std::cout << "Process finished! (" << code << ')' << std::endl;
}


void WrapperClass::mBytesWritten(qint64 written) {

    std::cout << "Bytes written: " << written << std::endl;

}

Main.cpp

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    qmlRegisterType<WrapperClass>("com.example.WrapperClass", 0, 1, "WrapperClass");

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

通过将 Cpp-Class 注册到 QML,我可以通过来自 QML-MouseAreaButton 的 Click-Events 触发读/写功能。

【讨论】:

    【解决方案2】:

    出于好奇,我玩弄了QProcess

    我真的很佩服一切工作的简单和直接(恐惧地记得过去我们在没有 Qt 的情况下做这件事是多么困难)。

    因此,我可以提供我的小rather MCVE 进行演示。

    首先我做了一个简单的控制台应用testQProcessIOChild.cc:

    #include <chrono>
    #include <iostream>
    #include <sstream>
    #include <string>
    #include <thread>
    
    using namespace std;
    
    // inspired by:
    // https://try.kotlinlang.org/#/Examples/Longer%20examples/99%20Bottles%20of%20Beer/99%20Bottles%20of%20Beer.kt
    string bottlesOfBeer(int n)
    {
      switch (n) {
        case 0: return "no more bottles of beer";
        case 1: return "1 bottle of beer";
        default: {
          ostringstream outFmt;
          outFmt << n << "  bottles of beer";
          return outFmt.str();
        }
      }
    }
    
    int main()
    {
      enum { delay = 1000 };
      for (int n;;) {
        cout << "Initial number of bottles (-1 ... finish): " << flush;
        if (!(cin >> n)) {
          cerr << "Input error!" << endl;
          continue;
        }
        if (n < -1) {
          cerr << "Illegal input!" << endl;
          continue;
        }
        if (n < 0) break;
        if (n > 100) {
          cerr << "The ministry of health warns:" << endl
            << " Abuse of alcoholics may damage your health." << endl;
          n = 99;
        }
        cout << "Go to the store and buy some more, "
          << bottlesOfBeer(n) << " on the wall." << endl;
        while (n) {
          this_thread::sleep_for(chrono::milliseconds(delay));
          cout << bottlesOfBeer(n) << " on the wall, "
            << bottlesOfBeer(n) << '.' << endl
            << "Take one down, pass it around, ";
          --n;
          cout << bottlesOfBeer(n) << " on the wall." << endl;
        }
        this_thread::sleep_for(chrono::milliseconds(delay));
        cout << "No more bottles of beer on the wall, no more bottles of beer."
          << endl;
      }
      return 0;
    }
    

    我这样做是为了提供一些东西:

    • 标准输入的使用
    • 标准输出的使用
    • 标准错误的使用
    • 某些特定的耗时行为(查看父进程是否以及何时做出反应)。

    第二次我将 Qt GUI 应用程序testQProcessIO.cc 作为包装器:

    // Qt header:
    #include <QtWidgets>
    
    const char *childProgram = "./testQProcessIOChild";
    
    int main(int argc, char **argv)
    {
      qDebug() << QT_VERSION_STR;
      // main application
      QApplication app(argc, argv);
      QProcess qProcessChild;
      // GUI setup
      QWidget qWin;
      QGridLayout qGrid;
      QPushButton qBtnStart(QString::fromUtf8("Start"));
      qGrid.addWidget(&qBtnStart, 0, 0);
      QPushButton qBtnStop(QString::fromUtf8("Stop"));
      qBtnStop.setEnabled(false);
      qGrid.addWidget(&qBtnStop, 0, 1);
      QLabel qLblInput(QString::fromUtf8("Input: "));
      qLblInput.setEnabled(false);
      qGrid.addWidget(&qLblInput, 0, 2);
      QLineEdit qInput;
      qInput.setEnabled(false);
      qGrid.addWidget(&qInput, 0, 3);
      QTextEdit qTxtLog;
      qTxtLog.setReadOnly(true);
      qGrid.addWidget(&qTxtLog, 1, 0, 1, 4);
      qGrid.setRowStretch(1, 1);
      qGrid.setColumnStretch(3, 1);
      qWin.setLayout(&qGrid);
      qWin.show();
      // install signal handlers
      QObject::connect(&qBtnStart, &QPushButton::clicked,
        [&](bool) {
          qProcessChild.start(QString::fromLatin1(childProgram));
        });
      QObject::connect(&qBtnStop, &QPushButton::clicked,
        [&](bool) {
          qProcessChild.kill();
        });
      QObject::connect(&qInput, &QLineEdit::returnPressed,
        [&](){
          QString text = qInput.text() + '\n';
          qProcessChild.write(text.toLatin1());
        });
      QObject::connect(&qProcessChild, &QProcess::started,
        [&]() {
          qBtnStart.setEnabled(false);
          qBtnStop.setEnabled(true);
          qLblInput.setEnabled(true);
          qInput.setEnabled(true);
        });
      QObject::connect(&qProcessChild,
        // cast needed because QProcess::finished() is polymorph
        (void(QProcess::*)(int))&QProcess::finished,
        [&](int) {
          qBtnStart.setEnabled(true);
          qBtnStop.setEnabled(false);
          qLblInput.setEnabled(false);
          qInput.setEnabled(false);
          qTxtLog.clear();
        });
      QObject::connect(&qProcessChild, &QProcess::readyReadStandardOutput,
        [&]() {
          qTxtLog.append(qProcessChild.readAllStandardOutput());
        });
      QObject::connect(&qProcessChild, &QProcess::readyReadStandardError,
        [&]() {
          qTxtLog.append(qProcessChild.readAllStandardError());
        });
      // run application
      return app.exec();
    }
    

    我在 Windows 10(64 位)上使用 VS2013 和 Qt 5.9.2 编译并测试了它。

    为了说明测试会话,我后来写了一个 QMake 项目testQProcessIO.pro

    SOURCES = testQProcessIO.cc
    
    QT += widgets
    

    再次在cygwin上编译测试:

    $ g++ -std=c++11 -o testQProcessIOChild testQProcessIOChild.cc 
    
    $ ./testQProcessIOChild 
    Initial number of bottles (-1 ... finish): -1
    
    $ qmake-qt5 testQProcessIO.pro
    
    $ make
    g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQProcessIO.o testQProcessIO.cc
    g++  -o testQProcessIO.exe testQProcessIO.o   -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread 
    
    $ ./testQProcessIO
    5.9.2
    
    $
    

    我的测试会话快照:

      

    【讨论】:

    • 谢谢,这看起来像是一个直接的 QWidgets 解决方案!我已经在下面发布了我的 QtQuick 方法。
    猜你喜欢
    • 1970-01-01
    • 2014-01-01
    • 1970-01-01
    • 2013-06-19
    • 2018-01-12
    • 2011-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多