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.