1

I want to get the live output of a running process. Therefore I have created in ExecuteCommand() pipes for stdout, stdin and stderr. Afterwards I have started a process and send a QTimer::singleShot() executing CheckOutput() one second later. Here the Code:

bool CommandExecutor_C::ExecuteCommand(QString &aCommand)
{
  aCommand.push_front(std::getenv("ComSpec") + QString(" /c "));
  mCommand = aCommand;
  LOGINFO(FB_TDI,"Launch command: " + aCommand.toStdString());

  SECURITY_ATTRIBUTES secattr;
  ZeroMemory(&secattr,sizeof(secattr));
  secattr.nLength = sizeof(secattr);
  secattr.bInheritHandle = TRUE;

  //in/out/err Handle
  HANDLE StdInHandleRd, StdInHandleWr;//[0] read -> [1] write
  HANDLE StdOutHandleRd, StdOutHandleWr;
  HANDLE StdErrHandleRd, StdErrHandleWr;

  //creating pipes to have access to handle contents
  CreatePipe(&StdInHandleRd , &StdInHandleWr  , &secattr, 4096);
  CreatePipe(&StdOutHandleRd, &StdOutHandleWr , &secattr, 4096);
  CreatePipe(&StdErrHandleRd, &StdErrHandleWr , &secattr, 4096);

  //information for process
  STARTUPINFO si;
  PROCESS_INFORMATION pi;

  ZeroMemory( &si, sizeof(si) );
  si.cb = sizeof(si);

  si.dwFlags |= STARTF_USESTDHANDLES;
  si.hStdInput = StdInHandleRd;   /**< read handle/pipe */
  si.hStdOutput = StdOutHandleWr; /**< write handle/pipe */
  si.hStdError = StdErrHandleWr;  /**< write handle/pipe */

  mHandles.StdOutHandle = StdOutHandleRd;
  mHandles.StdErrHandle = StdErrHandleRd;

  LPWSTR cmdTmp = reinterpret_cast<LPWSTR>(aCommand.data());

  //creates process
  bool res = CreateProcess(NULL,
                           cmdTmp, //command casted to LPWSTR
                           NULL,  // process security attributes
                           NULL,  // primary thread security attributes
                           TRUE,  // handles are inherited
                           NORMAL_PRIORITY_CLASS|CREATE_NO_WINDOW,
                           NULL,  // use parent's environment
                           NULL,  // use parent's current directory
                           &si,   // STARTUPINFO pointer
                           &pi);  // receives PROCESS_INFORMATION

  if(!res)  //not successfully created
  {
    LOGERROR(FB_TDI,"Failed to create Process for cmd: " + aCommand.toStdString() + boost::lexical_cast<std::string>(GetLastError()));

    CloseHandle(StdInHandleRd);
    CloseHandle(StdInHandleWr);
    CloseHandle(StdOutHandleRd);
    CloseHandle(StdOutHandleWr);
    CloseHandle(StdErrHandleRd);
    CloseHandle(StdErrHandleWr);
    return false; //failed
  }
  else
  {
    DWORD bytesWritten;
    //wait for 1 second
    QTimer::singleShot(1000, this, SLOT(CheckOutput()));
  }

  return true;
}

void CommandExecutor_C::CheckOutput()
{
    QString StdOut;
    QString StdErr;

    //Logs Process output
    LogProcessOutput(mHandles.StdOutHandle, StdOut);
    //Logs Process error
    LogProcessOutput(mHandles.StdErrHandle, StdErr);

    mProcessStatus = CheckTdiAutomationInterface(StdOut.toStdString(), StdErr.toStdString());

    if(mProcessStatus == AI_UNKNOWN)
    {
      //check output again 1 second later
      QTimer::singleShot(1000, this, SLOT(CheckOutput()));
    }
    else
    {
      OnTdiActive(mProcessStatus);
    }
}

Hint::The function CheckTdiAutomationInterface() does just compare a given string with some other defined strings and returns afterwards the Process Status.

But the problem seems to appear in LogProcessOutput(). The function gets called but PeekNamedPipe() does always return 0 bytes available. Only if the process has been finished bytes are available. Therefore it seems like the process output gets buffered and send to stdout only if the process has finished. Here the Code:

void CommandExecutor_C::LogProcessOutput(HANDLE &aReadOutHandle, QString &aProcessOutput)
{
  //Copy data from pipe
  DWORD bytesAvailable = 0;

  if(!PeekNamedPipe(aReadOutHandle,NULL,0,NULL,&bytesAvailable,NULL))
  {
    LOGERROR(FB_TDI,"Failed to call PeekNamedPipe to print process output");
  }

  if(bytesAvailable)
  {
    DWORD dwRead;
    char chBuf[BUFSIZ];
    bool bSuccess = FALSE;
    std::string out;
    do
    {
      //blocks process and waits for more data, this cause errors because no more data is produced
      //thats why PeekNamedPipe should look before if data is available
      bSuccess=::ReadFile( aReadOutHandle, chBuf, BUFSIZ, &dwRead, NULL);

      std::string s(chBuf, dwRead);
      out += s;

      //1. when the last read was successful
      //2. when there is more data available then the bufsize size
      //3. when the read bytes are not less then the BUFSIZE > would mean no more data..
    }
    while( bSuccess && (bytesAvailable>BUFSIZ) && !(dwRead<BUFSIZ) );

    LOGINFO(FB_TDI,"OUTHANDLE:\n"+out);
    aProcessOutput = QString::fromStdString(out);
  }
  else
  {
    aProcessOutput = "";
  }
}

Edit: I want to get the Output live while the Process is running. If the Process has been finished everything works fine and the output can be read out.

Lehtim
  • 125
  • 1
  • 12
  • Possible duplicate of [Capture Output of Spawned Process to string](http://stackoverflow.com/questions/14147138/capture-output-of-spawned-process-to-string) – rustyx Aug 25 '16 at 08:28
  • Does the process flush buffers after each out? – ilotXXI Aug 25 '16 at 10:46
  • @ilotXXI May you explain to me how to check if a buffer has been flushed. I have no idea how to check this. – Lehtim Aug 25 '16 at 11:22
  • @Lehtim If it is not a third party program, look at the code. Otherwise I don't know. Does it print anything before exit when you launch it in console? – ilotXXI Aug 25 '16 at 13:11
  • Since you're using Qt for some of your program, is there any reason why you're not using `QProcess` to simplify your IPC? – RobbieE Aug 25 '16 at 22:19
  • @RobbieE I have a second approach explained in another post named [link] (http://stackoverflow.com/questions/39095885/qt-how-to-get-live-output-of-a-running-qprocess) `Qt: How to get live output of a QProcess`. There I have used QProcess. But I had the same problem. – Lehtim Aug 26 '16 at 06:22
  • @ilotXXI I know that the executable does print out some information at the cmd "live" if I am starting the executable in a cmd. If everything works fine something like "waiting forever" is printed and the process runs permanently. If some error occurs (e.g. no connection) the process is `finished` and an Error written. – Lehtim Aug 26 '16 at 06:33
  • @Lehtim So, process seems to flush the out. Otherwise it printed all the messages after exit. Sorry, cannot help. – ilotXXI Aug 26 '16 at 07:26
  • @ilotXXI, that may be not the case since interactive shell is involved. If on Linux, I'd try to launch a QProcess with `stdbuf -o0 my-command` or `stdbuf -oL my-command`. – Velkan Aug 29 '16 at 09:02

0 Answers0