目录

通过Qt优雅地调用命令行计算程序并抓取输出显示在窗口上

编写大型工程计算软件时,计算核心与图形操作界面混在一起不是一个好的实践。一则不容易实现跨平台,二则难以在非图形环境下部署和使用,三则这样写容易让二者的代码混在一起提高维护难度,四则图形库往往引入不必要的性能开销和难以预知的输入。所以,分别单独实现计算核心和图形操作界面,然后通过命令行调用、消息输出是一个很合适的方案。

这些天基于Qt搞了一个这样一套方案,同时以此为契机搞清楚了Qt信号(signal)/槽(slot)机制,这里做一个总结。

功能点

实现

调用计算核心

比较容易,Qt已经提供了很好的封装:

computeProcess.setProgram(launchOption.executable);
computeProcess.setArguments(launchOption.arguments);
computeProcess.setWorkingDirectory(launchOption.runDirectory);
 
computeProcess.start();
 
while (!computeProcess.waitForFinished()) {
    QThread::sleep(1);
}
 
emit computingFinished(computeProcess.exitStatus() == QProcess::NormalExit);

launchOption就是个结构体,存放了一些字符串。start是实际启动外部程序。computeProcess.waitForFinished()看名字是等待到程序结束,但实际上默认最长只等三十秒,然后就返回一个false回来。理论上可以传一个参数让他永久等待,不过想想还是用一个循环多次调用它比较好,需要的话,循环里面可以做一些别的事情,比如计数什么的。

最后一句emit ...是发送一个信号,信号中包含了一个数据:程序是否正常退出。

抓取计算核心的输出

可以对Process对象执行相关的抓取函数来获得。但是单开一个线程反复尝试抓取(我在Java里面就是这样干的!)相当的不优雅。利用Qt的消息机制,可以将他们封装为信号:

// 在一个继承了QObject的类中
 
// 已经声明了:
// public slots:
//    void doCompute();
// 以及
// signals:
//    void newMessageIncoming(QString newMessage, bool isError);
//    void computingFinished(bool isExitedNormally);
 
connect(&computeProcess, &QProcess::readyReadStandardOutput, [this]() {
    emit newMessageIncoming(computeProcess.readAllStandardOutput(), false);
});
connect(&computeProcess, &QProcess::readyReadStandardError, [this]() {
    emit newMessageIncoming(computeProcess.readAllStandardError(), true);
});

等于是,把Process中Qt原生提供的的“有标准输出/错误输出”信号,连接到了两个lambda表达式史上,这两个表达式所执行的内容则是读出所有的新来的信息,并且将他们封装到一个自定义的信号newMessageIncoming中,发射出去。

对于信号的槽的几点认识:

GUI的更新

刚才已经封装出来了两个信号:newMessageIncoming(QString newMessage, bool isError)以及computingFinished(bool isExitedNormally),在窗体这头connect一下,把数据和状态反映在GUI中,就OK了。

核心代码

ComputeProcessWrapper.h
#ifndef COMPUTEPROCESSWRAPPER_H
#define COMPUTEPROCESSWRAPPER_H
 
#include "LaunchOption.h"
#include <QObject>
#include <QProcess>
 
class ComputeProcessWrapper : public QObject {
    Q_OBJECT
 
public:
    ComputeProcessWrapper();
 
    LaunchOption launchOption;
    QProcess computeProcess;
 
public slots:
    void doCompute();
 
signals:
    void newMessageIncoming(QString newMessage, bool isError);
    void computingFinished(bool isExitedNormally);
 
private:
    void setProcess();
};
 
#endif // COMPUTEPROCESSWRAPPER_H
ComputeProcessWrapper.cpp
#include "ComputeProcessWrapper.h"
#include <QThread>
 
ComputeProcessWrapper::ComputeProcessWrapper()
{
    connect(&computeProcess, &QProcess::readyReadStandardOutput, [this]() {
        emit newMessageIncoming(computeProcess.readAllStandardOutput(), false);
    });
    connect(&computeProcess, &QProcess::readyReadStandardError, [this]() {
        emit newMessageIncoming(computeProcess.readAllStandardError(), true);
    });
}
 
void ComputeProcessWrapper::setProcess()
{
    computeProcess.setProgram(launchOption.executable);
    computeProcess.setArguments(launchOption.arguments);
    computeProcess.setWorkingDirectory(launchOption.runDirectory);
}
 
void ComputeProcessWrapper::doCompute()
{
    setProcess();
 
    computeProcess.start();
 
    while (!computeProcess.waitForFinished()) {
        QThread::sleep(1);
    }
 
    emit computingFinished(computeProcess.exitStatus() == QProcess::NormalExit);
}