May 22, 2015

Qt: Workaround for the leaky QProcess output channel

It started with a pretty simple task. I wanted to run a QProcess and put the console output into a QTextEdit.

My initial solution was this:
 ### MainWindow.h ###  
 class MainWindow  
   
  : public QMainWindow  
 {  
  Q_OBJECT  
   
  private:  
   QTextEdit* text_edit;  
   Process process;   
 }  

First, it seemed to work fine. But then I noticed, that sometimes the end of the output is not written to the QTextEdit. Some digging in the web revealed that this is quite a common problem. On Windows there seems to be a buffering problem that can truncate the output when piping it to something other than a file.
 ### MainWindow.cpp ###  
 MainWindow::MainWindow(QWidget* parent)  
 {   
  process.setProcessChannelMode(QProcess::MergedChannels);  
  connect(&process, SIGNAL(readyReadStandardOutput()), this, SLOT(printOutput()));  
 }   
 void MainWindow::execute(QString command)  
 {  
  text_edit->clear();  
  process.start(command);  
 }  
 void MainWindow::printOutput()  
 {  
  QString text = process->readAllStandardOutput();  
  text_edit.moveCursor(QTextCursor::End);  
  text_edit.insertPlainText(text);  
 }  

I tried many different suggestions from several forums (unbuffered output etc.), but in the end only writing the output to a file instead to the QProcess directly fixed the problem. Unfortunately that requires a bit more work. We have to continuously check for new output in the file ourselves, e.g. triggered by a QTimer:

 ### MainWindow.h ###  
 class MainWindow  
  : public QMainWindow  
 {  
  Q_OBJECT  
   
  private:  
   QTextEdit* text_edit;  
   QProcess process;  
   QTimer process_timer;  
   QString process_file;  
   qint64 process_file_pos;  
 }   

 ### MainWindow.cpp ###  
 MainWindow::MainWindow(QWidget* parent)  
 {   
  process_file = QDir::temp() + "some_file_name.txt";  
  process.setProcessChannelMode(QProcess::MergedChannels);  
  process.setStandardOutputFile(process_file);  
  connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(executeFinished()));  
  connect(&process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(executeError(QProcess::ProcessError)));  
   
  process_timer.setInterval(100);  
  process_timer.setSingleShot(false);  
  connect(&process_timer, SIGNAL(timeout()), this, SLOT(printOutput()));  
 }  
 void MainWindow::execute(QString command)  
 {   
   QFile::remove(process_file);  
   process_file_pos = 0;  
   process.start(command);  
   process_timer.start();  
 }  
 void MainWindow::printOutput()  
 {  
  QFile file(process_file);  
  if (!file.open(QIODevice::ReadOnly)) return;  
   
  if (file.size()>process_file_pos)  
  {  
   file.seek(process_file_pos);  
   text_edit->moveCursor(QTextCursor::End);  
   text_edit->insertPlainText(file.readAll());   
   process_file_pos = file.pos();  
  }   
  file.close();  
 }   
 void MainWindow::executeFinished()  
 {  
  process_timer.stop();  
  printOutput();  
 }  
 void MainWindow::executeError(QProcess::ProcessError)  
 {  
   process_timer.stop();  
   printOutput();  
 }  
The code above is of cause not complete. A working example project for QtCreator can be found here.

Any suggestions how my approach can be simplified are welcome :)

No comments:

Post a Comment