QProcess调用外部程序

来源:互联网 发布:中序遍历 php 编辑:程序博客网 时间:2024/06/08 11:58

1. 简介

最近一段时间,需要写一些数据处理的代码。在写之前翻看了一下之前的代码,发现已经有同事做成了控制台的小程序,可以通过调用Windows下的命令行来处理这些数据。既然可以使用已有的成果,那问题就转变成如何集成这些工具到软件中了。在Qt中翻看了一下文档,正好有一个来处理这种任务的类QProcess,于是开始编码,在使用过程中发现了不少的问题,在一一解决之后,记录在此,方便还有类似困惑的朋友们。

2. 使用方式

在QProcess中如何调用外部的应用程序呢?在QProcess类的文档中已经解释的很清楚了,使用方式如下:

    QString program = "./path/to/Qt/examples/widgets/analogclock";    QStringList arguments;    arguments << "-style" << "fusion";    QProcess *myProcess = new QProcess(parent);    myProcess->start(program, arguments);

将命令行参数放在一个QStringList中就可以了,之后调用start,这时候就启动了该命令行工具的进程开始处理了。

2.1 QProcess的调用方式

上文中启动外部程序使用的是start函数,当我们使用这个函数之后,外部的进程启动,程序开始运行,这时候它和调用它的线程(一般是我们的GUI线程)之间就分离了,二者再无任何关系,即使这时候关闭了我们的程序,外部的程序还是会自己运行直到处理任务结束。有时候这并不是我们需要的,我们想让主线程等待这个处理工具处理结束,这个时候可以在start之后调用一个等待的函数:

bool QProcess::waitForFinished(int msecs = 30000)

这里面msecs函数是等待结束的时间(单位是毫秒),如果设置为-1,那么启用外部程序的这个线程会等待外部工具运行完成。这个时候如果是GUI线程,那么画面会假死。
也就是说我们的代码变成:

    QString program = "./path/to/Qt/examples/widgets/analogclock";    QStringList arguments;    arguments << "-style" << "fusion";    QProcess *myProcess = new QProcess(parent);    myProcess->start(program, arguments);    myProcess->waritForFinished(-1);

2.2 QProcess与调用程序的通信

使用QProcess工具的时候,一般来说我们希望外部的应用程序给出一些输出,我们针对这些输出作一些分析,在程序界面输出提示。一个简单的应用场景(我使用QProcess的场景),我需要在处理数据的时候给出处理的进度,让我在软件界面显示出进度条信息,让用户实时知道处理的进度。

首先要明确的是,如果QProcess启动的外部程序如果没有任何输出(控制台输出),那么我们要去分析它,给出任务处理进度,这个是无法办到的。只能去修改外部程序的源码,让它嵌入一些控制台的输出信息。『需要注意的是:在输出信息的时候最好让输出信息立刻输出到控制台,不要使用带有缓存策略的输出方式,比如默认C++标准库中的std::cout就使用了缓存的机制,为了让它实时输出,可以强制它输出(例如设置flush标识),这样处理是为了让我们能实时得到控制台的输出日志,方便我们分析,否则可能造成程序处理完了,日志缓冲区还没有充满,结束的时候一并输出了日志,导致我们没法分析处理过程中的日志』如果正好这个工具有控制台的输出信息,那么我们就可以获取到这些输出,获取的方式是使用QProcess提供的信号:

void    readyReadStandardOutput()

这个信号会在外部程序启动后开始处理之前被发出,我们可以连接这个信号到我们自己定义的插槽函数,来获取输出的控制台信息:

    connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(readOutput()));

我们可以在readOutput 里面来捕获输出的日志,并分析:

void MyMainWindow::readOutput(){    QString readAllStandardQString = process->readAllStandardOutput();    //do your stuff    ...}

以我之前做的数据处理工具为例,我会根据输出日志中记录的输出分块数,来确定我处理的进度,并在获取进度之后发送一个当前进度的信号

void myProgressSignal(currentProgress);

我在代码中连接这个信号,然后使用进度条来显示这个进度即可。

另一个需要注意的问题是:如果这个控制台的外部程序的输出杂乱无章,根本没有规律可言,那么也很抱歉,没有办法来处理进度。必须至少是有一些规律的信息才能做进度这样的工作(当然如果你只是想输出这些信息给用户看,那就没有关系了)。实际上我使用的那个外部程序它的输出日志只有后半段有规律,前面也是一些杂乱无章的信息,那么我怎么去处理进度呢?一直让进度停滞在一个地方显然是不合理的。
这里我用到了一个小技巧,我在程序中定义了一个定时器,当处理前半段杂乱无章的信息时,我使用定时器让信号中进度递增,直到输出的日志出现规律之后,便停止计时器,开始后半段的处理(后半段的进度是整个进度100%减去前半段没有规律定时器所走的进度之差),这样处理之后,整个进度条看起来效果还不错。

2.3 QProcess完成的处理

在上文中我们在做进度处理时,有可能进度计算并不能在我们处理完输出日志的时候结束(因为可能外部程序在输出完日志之后,还需要销毁,也会暂用一小段时间),所以外部程序什么时候真正的结束对我们也很重要,因为真正结束的时候,一般来说我们需要让进度条到达100%,并且让进度条消失。这个任务在QProcess中使用结束的信号的实现。

void    finished(int exitCode, QProcess::ExitStatus exitStatus)

当这个信号发出时,调用的外部程序就已经结束。我们可以连接这个信号到我们的插槽中,在插槽函数中让我们的进度条进度到达100%,并让进度条隐藏。也可以给出消息框让用户知道已经处理结束。

3. 循环多次调用

有时候我们希望使用在一个循环中重复地调用外部工具来做处理。例如:我们需要重复的调用一个工具来压缩很多文件,这个时候我们编写的代码可能是在一个for循环中,但是这时候有一个问题需要注意:
1. 如果我们使用start这种方式(没有调用waitForFinish),当我们处理第一个文件没有结束是,循环进入到下一次,那么QProcess会给出提示,当前进程还在继续,那么后续的处理都不会完成。也就是说这种情况下,实际上我们只处理了一个文件(for循环的第一次)
2. 如果调用了waitForFinish,那么程序会处理完所有的文件,但是如果我们是主线程调用的这些代码,那么会导致整个程序界面假死,这种体验可能不是很好。可以有两种方案来解决这个问题:

3.1 多线程方式

这个时候我们可以不在主线程(GUI线程)中启动QProcess,而是开另一个线程来调用QProcess,让主线程可以不出现假死的情况,继续处理和用户的交互。(这种方式我个人未使用,读者可以自己去尝试)

3.2 结束事件中处理

第二种方式可以不使用for循环来处理,而是将要处理的参数列表(一般是文件名和一些其他的参数),放在一个数组中,首先处理数组的第一个,然后在处理完成QProcess结束信号连接的插槽函数中开启新的QProcess继续处理下一个,并删除参数数组中已经处理完的参数。直到整个参数列表数组为空,结束整个处理工程。

    connect(_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readOutput()));    connect(_process, SIGNAL(finished(int, QProcess::ExitStatus)),        this, SLOT(processFinishedSlot(int, QProcess::ExitStatus)));

接着在processFinishedSlot中继续开始新的处理

    if (_processFileList.size()>=1)        _processFileList.pop_front();    if (_processFileList.size() == 0)    {        emit allFinishedSignal();        _processFileList.clear();        disconnect(_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readOutput()));        disconnect(_process, SIGNAL(finished(int, QProcess::ExitStatus)),            this, SLOT(processFinishedSlot(int, QProcess::ExitStatus)));        return;    }    QString program = "./MyTool.exe";    if (_processFileList.size() >= 1)    {        QString fileName = _processFileList.at(0);        QStringList arguments;        arguments << "-i" << fileName;        _process->start(program, arguments);    }

参考文献:

  1. Qt Assistant QProcess
  2. 从QProcess说开来(一)