How to get live output of a running process? - c++

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.

Related

CreateProcessA fails for some programs

Found the below snippet here on SO:
https://stackoverflow.com/a/35658917/9265719.
It executes a command without creating a window. CreateProcessA() returns TRUE for cmd.exe but for any program in C:\Program Files(x86)\Windows Kits\10\Debuggers\x64\ it returns FALSE and GetLastError() returns 2 (ERROR_PATH_NOT_FOUND).
Why is it failing to create a process for programs in this directory?
#include <iostream>
#include <windows.h>
//
// Execute a command and get the results. (Only standard output)
//
std::string ExecCmd(
char cmd[] // [in] command to execute
)
{
std::string strResult;
HANDLE hPipeRead, hPipeWrite;
SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES) };
saAttr.bInheritHandle = TRUE; // Pipe handles are inherited by child process.
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe to get results from child's stdout.
if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0))
return strResult;
STARTUPINFOA si = { sizeof(STARTUPINFOA) };
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.hStdOutput = hPipeWrite;
si.hStdError = hPipeWrite;
si.wShowWindow = SW_HIDE; // Prevents cmd window from flashing.
// Requires STARTF_USESHOWWINDOW in dwFlags.
PROCESS_INFORMATION pi = { 0 };
BOOL fSuccess = ::CreateProcessA(NULL, cmd, NULL, NULL, TRUE,
CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
if (!fSuccess)
{
DWORD dw = GetLastError();
CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
return strResult;
}
bool bProcessEnded = false;
for (; !bProcessEnded;)
{
// Give some timeslice (50 ms), so we won't waste 100% CPU.
bProcessEnded = WaitForSingleObject(pi.hProcess, 50) == WAIT_OBJECT_0;
// Even if process exited - we continue reading, if
// there is some data available over pipe.
for (;;)
{
char buf[1024];
DWORD dwRead = 0;
DWORD dwAvail = 0;
if (!::PeekNamedPipe(hPipeRead, NULL, 0, NULL, &dwAvail, NULL))
break;
if (!dwAvail) // No data available, return
break;
if (!::ReadFile(hPipeRead, buf, min(sizeof(buf) - 1, dwAvail), &dwRead, NULL) || !dwRead)
// Error, the child process might ended
break;
buf[dwRead] = 0;
strResult += buf;
}
} //for
CloseHandle(hPipeWrite);
CloseHandle(hPipeRead);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return strResult;
} //ExecCmd
int main()
{
//char cmd[1000] = R"("C:\WINDOWS\system32\cmd.exe")";
char cmd[1000] = R"("C:\Program Files(x86)\Windows Kits\10\Debuggers\x64\cdb.exe")";
std::string op = ExecCmd(cmd);
std::cout << op.c_str();
}
You are missing a space in the path, between "Program Files" and "(x86)". Should be:
char cmd[1000] = R"("C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe")";

CreateProcess cmd.exe read/write pipes deadlock

Hello I am trying to make a front end GUI for cmd.exe so I can make it wider but I got stuck.
I try to design an API like this
char* Directory = WriteCommand("dir");
printf("- %s\n", Directory);
and the output look exactly like it would in a cmd window, except I have it in a string, so it would be
DATE TIME FILESIZE FILENAME
etc etc etc
and then I can issue
char* Up = WriteCommand ("cd ..");
and it will give me the above directory listing. So I want a terminal control through using pipes to read and write.
I have tried many things based on this MSDN sample code - https://msdn.microsoft.com/en-us/library/ms682499.aspx
But I think this code is only good to issue one command, and read one response, because right after it deadlocks as described here - https://blogs.msdn.microsoft.com/oldnewthing/20110707-00/?p=10223
I see several other questions here, like this one with similar problems - How to read output from cmd.exe using CreateProcess() and CreatePipe() but no solutions posted work for me.
So here is my code.
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>
#define BUFSIZE 4096
HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
HANDLE g_hInputFile = NULL;
void CreateChildProcess(void);
void WriteToPipe(char* Arg1);
void ReadFromPipe(void);
void ErrorExit(PTSTR);
int _tmain(int argc, TCHAR *argv[])
{
SECURITY_ATTRIBUTES saAttr;
printf("\n->Start of parent execution.\n");
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
ErrorExit(TEXT("StdoutRd CreatePipe"));
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
ErrorExit(TEXT("Stdout SetHandleInformation"));
// Create a pipe for the child process's STDIN.
if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
ErrorExit(TEXT("Stdin CreatePipe"));
// Ensure the write handle to the pipe for STDIN is not inherited.
if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
ErrorExit(TEXT("Stdin SetHandleInformation"));
// Create the child process.
CreateChildProcess();
// Get a handle to an input file for the parent.
// This example assumes a plain text file and uses string output to verify data flow.
/*if (argc == 1)
ErrorExit(TEXT("Please specify an input file.\n"));
g_hInputFile = CreateFile(
argv[1],
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_READONLY,
NULL);
if (g_hInputFile == INVALID_HANDLE_VALUE)
ErrorExit(TEXT("CreateFile"));*/
// Write to the pipe that is the standard input for a child process.
// Data is written to the pipe's buffers, so it is not necessary to wait
// until the child process is running before writing data.
// Read from pipe that is the standard output for child process.
ReadFromPipe();
WriteToPipe("ipconfig");
// THIS IS WHERE DEADLOCK OCCURS, FROM HERE
// PROGRAM BECOMES UNRESPONSIVE - HOW TO FIX THIS?
ReadFromPipe();
printf("\n->End of parent execution.\n");
// The remaining open handles are cleaned up when this process terminates.
// To avoid resource leaks in a larger application, close handles explicitly.
return 0;
}
void CreateChildProcess()
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
{
TCHAR szCmdline[] = TEXT("cmd.exe /k");
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
bSuccess = CreateProcess(NULL,
"cmd.exe", // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
// If an error occurs, exit the application.
if (!bSuccess)
ErrorExit(TEXT("CreateProcess"));
else
{
// Close handles to the child process and its primary thread.
// Some applications might keep these handles to monitor the status
// of the child process, for example.
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
}
}
void WriteToPipe(char* Command)
// Read from a file and write its contents to the pipe for the child's STDIN.
// Stop when there is no more data.
{
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
bSuccess = WriteFile(g_hChildStd_IN_Wr, Command, strlen(Command), &dwWritten, NULL);
if (bSuccess == FALSE)
printf("write fail\n");
printf("written = %i\n", dwWritten);
//for (;;)
//{
//bSuccess = ReadFile(g_hInputFile, chBuf, BUFSIZE, &dwRead, NULL);
//if (!bSuccess || dwRead == 0) break;
//bSuccess = WriteFile(g_hChildStd_IN_Wr, Command, strlen(Command), &dwWritten, NULL);
//if (bSuccess == FALSE)
//printf("write fail\n");
//printf("written = %i\n", dwWritten);
//}
// Close the pipe handle so the child process stops reading.
//if (!CloseHandle(g_hChildStd_IN_Wr))
//ErrorExit(TEXT("StdInWr CloseHandle"));
}
void ReadFromPipe(void)
// Read output from the child process's pipe for STDOUT
// and write to the parent process's pipe for STDOUT.
// Stop when there is no more data.
{
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
int i;
for (i = 0; i < 4; i++)
{
/*DWORD dwAvail = 0;
if (!PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, 0, NULL, &dwAvail, NULL)) {
// error, the child process might have ended
break;
}
if (!dwAvail) {
// no data available in the pipe
break;
}*/
bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
if (!bSuccess || dwRead == 0) break;
/*bSuccess = WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL);
if (!bSuccess) break;*/
chBuf[dwRead] = '\0';
printf("%i - %s\n", i, chBuf);
}
printf("done\n");
}
I issue the initial "cmd.exe" command which gives me the start of the command prompt. I now want to issue "ipconfig" (or any other command) to get networking info. The program deadlocks and becomes unresponsive. I can no longer read output of child process. How can I fix this? Thanks for your help.
the most power and effective solution for avoid any deadlocks - use asynchronous io. never wait for IO (read,write,ioctl) complete in place, but handle this in callbacks.
also note about use pipes for redirect output - very common errancy that we need use different handles for STDIN and STDOUT and need create 2 different pipes pair - one for STDIN and another for STDOUT. this is false. we can use single pipe handle for both STDIN and STDOUT (and STDERROR).
we need create server pipe handle by using CreateNamedPipeW with
PIPE_ACCESS_DUPLEX|FILE_READ_DATA|FILE_WRITE_DATA|FILE_FLAG_OVERLAPPED
flags. by using PIPE_ACCESS_DUPLEX we create bi-directional pipe,
as result both server and client processes can read from and write
to the pipe. and FILE_FLAG_OVERLAPPED give to as asynchronous
mode. also we not make this handle inheritable, so not need call
SetHandleInformation on it
client handle we create by CreateFileW also with
FILE_GENERIC_READ|FILE_GENERIC_WRITE access - this give ability
assign it both to stdin and stdout. because clients (like
cmd.exe) usually assume synchronous io - we not use
FILE_FLAG_OVERLAPPED here. also by using lpSecurityAttributes we
just make this handle inheritable.
we need bind server handle to some IOCP, for callback called when io
is ended. here we have 3 variants - use
BindIoCompletionCallback - the most simply way or use
CreateThreadpoolIo. also we can create IOCP yourself and own
thread pool, but for redirect child process output, this way usually
not need.
after we create child process - we need close client pipe handle
(which we duplicate to child) and just call ReadFile on our pipe
handle. when this ReadFile complete - we need again call
ReadFile from callback and so on - until we not got error from
ReadFile in completion (usually ERROR_BROKEN_PIPE). so we need
all time have active read request from pipe, until disconnect.
and we free call WriteFile at any time and any place - this never
cause deadlock, because we use asynchronous io.
some time (very very rarely) if we need complex processing on read
data(based on previous results and state) and this much more easy
handle in plain procedure but not in callbacks, we can create fiber
for this task (CreateFiber) and from working thread callback,
when read complete - first call ConvertThreadToFiber (if we
call this more than once for same working thread - will be error
ERROR_ALREADY_FIBER on second and next calls, but this is ok. but
all this work begin from vista only. on xp error here). remember
current fiber, to where need retirn (GetCurrentFiber()) and
call SwitchToFiber (with our dedicated for read fiber)- where
we can handle read result and after this return back by call
SwitchToFiber (with fiber for worked thread). but all this
really can be need in in very rare and specific scenarios. usually
handle all is callbacks with state in object related to pipe handle - more than enough.
simply example with cmd
#define _XP_SUPPORT_
struct IO_COUNT
{
HANDLE _hFile;
HANDLE _hEvent;
LONG _dwIoCount;
IO_COUNT()
{
_dwIoCount = 1;
_hEvent = 0;
}
~IO_COUNT()
{
if (_hEvent)
{
CloseHandle(_hEvent);
}
}
ULONG Create(HANDLE hFile);
void BeginIo()
{
InterlockedIncrement(&_dwIoCount);
}
void EndIo()
{
if (!InterlockedDecrement(&_dwIoCount))
{
SetEvent(_hEvent);
}
}
void Wait()
{
WaitForSingleObject(_hEvent, INFINITE);
}
};
struct U_IRP : OVERLAPPED
{
enum { read, write };
IO_COUNT* _pIoObject;
ULONG _code;
LONG _dwRef;
char _buffer[256];
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef)) delete this;
}
U_IRP(IO_COUNT* pIoObject) : _pIoObject(pIoObject)
{
_dwRef = 1;
pIoObject->BeginIo();
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
}
~U_IRP()
{
_pIoObject->EndIo();
}
ULONG CheckIoResult(BOOL fOk)
{
if (fOk)
{
#ifndef _XP_SUPPORT_
OnIoComplete(NOERROR, InternalHigh);
#endif
return NOERROR;
}
ULONG dwErrorCode = GetLastError();
if (dwErrorCode != ERROR_IO_PENDING)
{
OnIoComplete(dwErrorCode, 0);
}
return dwErrorCode;
}
ULONG Read()
{
_code = read;
AddRef();
return CheckIoResult(ReadFile(_pIoObject->_hFile, _buffer, sizeof(_buffer), 0, this));
}
ULONG Write(const void* pvBuffer, ULONG cbBuffer)
{
_code = write;
AddRef();
return CheckIoResult(WriteFile(_pIoObject->_hFile, pvBuffer, cbBuffer, 0, this));
}
VOID OnIoComplete(DWORD dwErrorCode, DWORD_PTR dwNumberOfBytesTransfered)
{
switch (_code)
{
case read:
if (dwErrorCode == NOERROR)
{
if (dwNumberOfBytesTransfered)
{
if (int cchWideChar = MultiByteToWideChar(CP_OEMCP, 0, _buffer, (ULONG)dwNumberOfBytesTransfered, 0, 0))
{
PWSTR wz = (PWSTR)alloca(cchWideChar * sizeof(WCHAR));
if (MultiByteToWideChar(CP_OEMCP, 0, _buffer, (ULONG)dwNumberOfBytesTransfered, wz, cchWideChar))
{
if (int cbMultiByte = WideCharToMultiByte(CP_ACP, 0, wz, cchWideChar, 0, 0, 0, 0))
{
PSTR sz = (PSTR)alloca(cbMultiByte);
if (WideCharToMultiByte(CP_ACP, 0, wz, cchWideChar, sz, cbMultiByte, 0, 0))
{
DbgPrint("%.*s", cbMultiByte, sz);
}
}
}
}
}
Read();
}
break;
case write:
break;
default:
__debugbreak();
}
Release();
if (dwErrorCode)
{
DbgPrint("[%u]: error=%u\n", _code, dwErrorCode);
}
}
static VOID WINAPI _OnIoComplete(
DWORD dwErrorCode,
DWORD_PTR dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped
)
{
static_cast<U_IRP*>(lpOverlapped)->OnIoComplete(RtlNtStatusToDosError(dwErrorCode), dwNumberOfBytesTransfered);
}
};
ULONG IO_COUNT::Create(HANDLE hFile)
{
_hFile = hFile;
// error in declaration LPOVERLAPPED_COMPLETION_ROUTINE :
// second parameter must be DWORD_PTR but not DWORD
return BindIoCompletionCallback(hFile, (LPOVERLAPPED_COMPLETION_ROUTINE)U_IRP::_OnIoComplete, 0) &&
#ifndef _XP_SUPPORT_
SetFileCompletionNotificationModes(hFile, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS) &&
#endif
(_hEvent = CreateEvent(0, TRUE, FALSE, 0)) ? NOERROR : GetLastError();
}
void ChildTest()
{
static const WCHAR name[] = L"\\\\?\\pipe\\somename";
HANDLE hFile = CreateNamedPipeW(name,
PIPE_ACCESS_DUPLEX|FILE_READ_DATA|FILE_WRITE_DATA|FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE|PIPE_READMODE_BYTE, 1, 0, 0, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
IO_COUNT obj;
if (obj.Create(hFile) == NOERROR)
{
BOOL fOk = FALSE;
SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi;
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdError = CreateFileW(name, FILE_GENERIC_READ|FILE_GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, 0);
if (si.hStdError != INVALID_HANDLE_VALUE)
{
si.hStdInput = si.hStdOutput = si.hStdError;
WCHAR ApplicationName[MAX_PATH];
if (GetEnvironmentVariableW(L"ComSpec", ApplicationName, RTL_NUMBER_OF(ApplicationName)))
{
if (CreateProcessW(ApplicationName, 0, 0, 0, TRUE, 0, 0, 0, &si, &pi))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
fOk = TRUE;
}
}
CloseHandle(si.hStdError);
}
if (fOk)
{
STATIC_ASTRING(help_and_exit, "help\r\nexit\r\n");
U_IRP* p;
if (p = new U_IRP(&obj))
{
p->Read();
p->Release();
}
obj.EndIo();
//++ simulate user commands
static PCSTR commands[] = { "help\r\n", "ver\r\n", "dir\r\n", "exit\r\n" };
ULONG n = RTL_NUMBER_OF(commands);
PCSTR* psz = commands;
do
{
if (MessageBoxW(0,0, L"force close ?", MB_YESNO) == IDYES)
{
DisconnectNamedPipe(hFile);
break;
}
if (p = new U_IRP(&obj))
{
PCSTR command = *psz++;
p->Write(command, (ULONG)strlen(command) * sizeof(CHAR));
p->Release();
}
} while (--n);
//--
obj.Wait();
}
}
CloseHandle(hFile);
}
}
I know is it a bit old so you probably won't need this answer anymore. But for those who came to StackOverflow for a solution for the same problem, I faced the same problem when building a similar project and I found a solution.
Basically, just add "\n" newline character to the end of the command. This is needed to simulate the "ENTER" button is pressed. Otherwise, WriteFile() works but ReadFile() is still waiting because the command was never executed in child process cmd.exe hence there is nothing for ReadFile() to read, causing it to hang there.
So the modified code is (I didn't test run the following code but is just modified based on the example the original author posted):
void WriteToPipe(char* Command)
// Read from a file and write its contents to the pipe for the child's STDIN.
// Stop when there is no more data.
{
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
// Fix for the issue
strcat_s(command, strlen(command) + 1, "\n", 1);
bSuccess = WriteFile(g_hChildStd_IN_Wr, Command, strlen(Command), &dwWritten, NULL);
if (bSuccess == FALSE)
printf("write fail\n");
printf("written = %i\n", dwWritten);
//for (;;)
//{
//bSuccess = ReadFile(g_hInputFile, chBuf, BUFSIZE, &dwRead, NULL);
//if (!bSuccess || dwRead == 0) break;
//bSuccess = WriteFile(g_hChildStd_IN_Wr, Command, strlen(Command), &dwWritten, NULL);
//if (bSuccess == FALSE)
//printf("write fail\n");
//printf("written = %i\n", dwWritten);
//}
// Close the pipe handle so the child process stops reading.
//if (!CloseHandle(g_hChildStd_IN_Wr))
//ErrorExit(TEXT("StdInWr CloseHandle"));
}

Reading from child console pipe hanging under some conditions

In my Win32 program I implemented executing a console application and reading its std/err output. Basically it's the same code as given in MSDN: Creating a Child Process with Redirected Input and Output
So far, so good. It worked like a charm, reading both std and err streams with all my console applications. But obviously (due to the global HANDLE variables) the code is designed to run console applications one by one, never together. So I've changed it a bit:
The global HANDLE variables replaced with the local ones. They are passed into the helper functions.
A parameter named bWait added. If it's false, no reading from the console pipe after starting and no waiting for a process handle (a flavor of asynchrony).
Instead, the reading handles are returned to a caller (thru given pointers). They can be used for reading from the pipe later.
Why do I need this? I want to start tshark (a console version of Wireshark, a traffic sniffer) with bWait = false, then start my own utility with bWait = true and wait until my utility stops working. Then I want to check, whether my utility pings a server. (Since we have a lot of utilities, this would be important functionality of our auto-testing procedure). So, I'd like to read from the tshark console pipe after that and parse its log.
Here is my modifications:
// Create a child process that uses the previously created pipes
// for STDERR and STDOUT.
PROCESS_INFORMATION CreateChildProcess(HANDLE hChildStd_OUT_Wr, HANDLE hChildStd_ERR_Wr,
const std::wstring& cmd, bool& bSuccess, DWORD& exitCode, DWORD& lastError, bool bWait = true)
{
// Set the text I want to run
//char szCmdline[]="test --log_level=all --report_level=detailed";
bSuccess = false;
wchar_t wrBuffer[BUFSIZE];
::wcscpy_s(wrBuffer, cmd.c_str());
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDERR and STDOUT handles for redirection.
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = hChildStd_ERR_Wr;
siStartInfo.hStdOutput = hChildStd_OUT_Wr;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
bSuccess = CreateProcess(NULL,
wrBuffer, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo) != 0; // receives PROCESS_INFORMATION
if (!bSuccess)
{
lastError = ::GetLastError();
}
else
{
lastError = 0;
}
if (bWait && bSuccess && ::WaitForSingleObject(piProcInfo.hProcess, INFINITE) == WAIT_FAILED)
{
bSuccess = false;
}
if (bWait && FALSE == ::GetExitCodeProcess(piProcInfo.hProcess, &exitCode))
{
bSuccess = false;
}
if (bWait)
{
::CloseHandle(hChildStd_ERR_Wr);
::CloseHandle(hChildStd_OUT_Wr);
}
return piProcInfo;
}
// Read output from the child process's pipe for STDOUT
// and write to the parent process's pipe for STDOUT.
// Stop when there is no more data.
void ReadFromPipe(HANDLE hChildStd_OUT_Rd, HANDLE hChildStd_ERR_Rd, std::wstring& stdS, std::wstring& errS)
{
DWORD dwRead;
CHAR chBuf[BUFSIZE];
bool bSuccess = FALSE;
std::string out = "", err = "";
for (;;)
{
bSuccess = ReadFile(hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL) != 0;
if (!bSuccess || dwRead == 0) break;
std::string s(chBuf, dwRead);
out += s;
}
dwRead = 0;
for (;;)
{
bSuccess = ReadFile(hChildStd_ERR_Rd, chBuf, BUFSIZE, &dwRead, NULL) != 0;
if (!bSuccess || dwRead == 0) break;
std::string s(chBuf, dwRead);
err += s;
}
wchar_t utf[10000] = { 0 };
::MultiByteToWideChar(866, 0, (LPCCH) out.c_str(), -1, utf, sizeof(utf));
stdS = utf;
StringReplace(stdS, std::wstring(L"\n"), std::wstring(L"\r\n"));
::MultiByteToWideChar(866, 0, (LPCCH) err.c_str(), -1, utf, sizeof(utf));
errS = utf;
StringReplace(errS, std::wstring(L"\n"), std::wstring(L"\r\n"));
}
bool ExecuteCmd(std::wstring cmd, std::wstring& std, std::wstring& err, std::wstring& code, DWORD& lastError,
bool bWait = true, HANDLE* phChildStd_OUT_Rd = nullptr, HANDLE* phChildStd_ERR_Rd = nullptr)
{
HANDLE hChildStd_OUT_Rd = NULL;
HANDLE hChildStd_OUT_Wr = NULL;
HANDLE hChildStd_ERR_Rd = NULL;
HANDLE hChildStd_ERR_Wr = NULL;
SECURITY_ATTRIBUTES sa;
// Set the bInheritHandle flag so pipe handles are inherited.
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDERR.
if (!CreatePipe(&hChildStd_ERR_Rd, &hChildStd_ERR_Wr, &sa, 0))
{
return false;
}
// Ensure the read handle to the pipe for STDERR is not inherited.
if (!SetHandleInformation(hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0))
{
return false;
}
// Create a pipe for the child process's STDOUT.
if (!CreatePipe(&hChildStd_OUT_Rd, &hChildStd_OUT_Wr, &sa, 0))
{
return false;
}
// Ensure the read handle to the pipe for STDOUT is not inherited
if (!SetHandleInformation(hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
{
return false;
}
// Create the child process.
bool bSuccess = false;
DWORD dwExitCode = 9999;
PROCESS_INFORMATION piProcInfo = CreateChildProcess(hChildStd_OUT_Wr, hChildStd_ERR_Wr, cmd, bSuccess, dwExitCode, lastError, bWait);
if (phChildStd_OUT_Rd)
*phChildStd_OUT_Rd = hChildStd_OUT_Rd;
if (phChildStd_ERR_Rd)
*phChildStd_ERR_Rd = hChildStd_ERR_Rd;
if (!bWait)
return true;
wchar_t buffer[10] = { 0 };
code = ::_itow((int) dwExitCode, buffer, 10);
if (!bSuccess)
{
return false;
}
// Read from pipe that is the standard output for child process.
ReadFromPipe(hChildStd_OUT_Rd, hChildStd_ERR_Rd, std, err);
::CloseHandle(hChildStd_OUT_Rd);
::CloseHandle(hChildStd_ERR_Rd);
return true;
}
Now, the problem. When I tried starting tshark in the no-waiting mode, reading from pipe hanged up. Namely, in ReadFile.
if (g_iConnection != -1 && g_Products[i].PingbackDomain.size() > 0)
{
wchar_t buf[5] = { 0 };
std::wstring list, err, code;
DWORD dwErr = 0;
std::wstring cmd = L"C:\\Program Files\\Wireshark\\tshark -a duration:5 -l -i ";
cmd += ::_itow(g_iConnection + 1, buf, 10);
cmd += L" -f \"host ";
cmd += g_Products[i].PingbackDomain;
cmd += L"\"";
ExecuteCmd(cmd, list, err, code, dwErr, false, &hChildStd_OUT_Rd, &hChildStd_ERR_Rd);
::Sleep(500);
}
...
// Starting my utility (if this section is commented out, ReadFile still hangs).
...
if (hChildStd_OUT_Rd && hChildStd_ERR_Rd)
{
std::wstring traffic, tsharkErr;
ReadFromPipe(hChildStd_OUT_Rd, hChildStd_ERR_Rd, traffic, tsharkErr);
::CloseHandle(hChildStd_OUT_Rd);
::CloseHandle(hChildStd_ERR_Rd);
if (tsharkErr.size() > 0)
{
std::wstring msg = L"There has been an issue, while logging with Wireshark:\r\n\r\n";
msg += tsharkErr;
::MessageBox(NULL, msg.c_str(), L"uhelper", MB_ICONERROR | MB_OK);
}
else if (traffic.length() > 0)
{
newOutput += L"\r\nTraffic to ";
newOutput += g_Products[i].PingbackDomain;
newOutput += L":\r\n";
newOutput += traffic;
if (newOutput[newOutput.length() - 1] != L'\n')
newOutput += L"\r\n";
}
}
Did I break the MSDN code with my modifications? Unfortunately, I can't find how (and where).
This solves the problem (before creating a process!):
if (!bWait)
{
DWORD mode = PIPE_READMODE_BYTE | PIPE_NOWAIT;
::SetNamedPipeHandleState(hChildStd_OUT_Rd, &mode, NULL, NULL);
::SetNamedPipeHandleState(hChildStd_ERR_Rd, &mode, NULL, NULL);
}
After applying PIPE_NOWAIT reading stopped hanging.

How to read output from cmd.exe using CreateProcess() and CreatePipe()

How to read output from cmd.exe using CreateProcess() and CreatePipe()
I have been trying to create a child process executing cmd.exe with a command-line designating /K dir. The purpose is to read the output from the command back into the parent process using pipes.
I've already got CreateProcess() working, however the step involving pipes are causing me trouble. Using pipes, the new console window is not displaying (like it was before), and the parent process is stuck in the call to ReadFile().
Does anyone have an idea of what I'm doing wrong?
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#define BUFFSZ 4096
HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
int wmain(int argc, wchar_t* argv[])
{
int result;
wchar_t aCmd[BUFFSZ] = TEXT("/K dir"); // CMD /?
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
printf("Starting...\n");
ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
// Create one-way pipe for child process STDOUT
if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0)) {
printf("CreatePipe() error: %ld\n", GetLastError());
}
// Ensure read handle to pipe for STDOUT is not inherited
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) {
printf("SetHandleInformation() error: %ld\n", GetLastError());
}
// Create one-way pipe for child process STDIN
if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &sa, 0)) {
printf("CreatePipe() error: %ld\n", GetLastError());
}
// Ensure write handle to pipe for STDIN is not inherited
if (!SetHandleInformation(g_hChildStd_IN_Rd, HANDLE_FLAG_INHERIT, 0)) {
printf("SetHandleInformation() error: %ld\n", GetLastError());
}
si.cb = sizeof(STARTUPINFO);
si.hStdError = g_hChildStd_OUT_Wr;
si.hStdOutput = g_hChildStd_OUT_Wr;
si.hStdInput = g_hChildStd_IN_Rd;
si.dwFlags |= STARTF_USESTDHANDLES;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
// Pipe handles are inherited
sa.bInheritHandle = true;
// Creates a child process
result = CreateProcess(
TEXT("C:\\Windows\\System32\\cmd.exe"), // Module
aCmd, // Command-line
NULL, // Process security attributes
NULL, // Primary thread security attributes
true, // Handles are inherited
CREATE_NEW_CONSOLE, // Creation flags
NULL, // Environment (use parent)
NULL, // Current directory (use parent)
&si, // STARTUPINFO pointer
&pi // PROCESS_INFORMATION pointer
);
if (result) {
printf("Child process has been created...\n");
}
else {
printf("Child process could not be created\n");
}
bool bStatus;
CHAR aBuf[BUFFSZ + 1];
DWORD dwRead;
DWORD dwWrite;
// GetStdHandle(STD_OUTPUT_HANDLE)
while (true) {
bStatus = ReadFile(g_hChildStd_OUT_Rd, aBuf, sizeof(aBuf), &dwRead, NULL);
if (!bStatus || dwRead == 0) {
break;
}
aBuf[dwRead] = '\0';
printf("%s\n", aBuf);
}
// Wait until child process exits
WaitForSingleObject(pi.hProcess, INFINITE);
// Close process and thread handles
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
printf("Stopping...\n");
return 0;
}
The subtle way out of your problem is to make sure you close the ends of the pipe you don't need.
Your parent process has four handles:
two of them are your ends of the pipe
g_hChildStd_IN_Wr
g_hChildStd_OUT_Rd
two of them are the child's end of the pipe
g_hChildStd_IN_Rd
g_hChildStd_OUT_Wr
 
╔══════════════════╗ ╔══════════════════╗
║ Parent Process ║ ║ Child Process ║
╠══════════════════╣ ╠══════════════════╣
║ ║ ║ ║
║ g_hChildStd_IN_Wr╟───────────────>║g_hChildStd_IN_Rd ║
║ ║ ║ ║
║g_hChildStd_OUT_Rd║<───────────────╢g_hChildStd_OUT_Wr║
║ ║ ║ ║
╚══════════════════╝ ╚══════════════════╝
Your parent process only needs one end of each pipe:
writable end of the child input pipe: g_hChildStd_IN_Wr
readable end of the child output pipe: g_hChildStd_OUT_Rd
Once you've launched your child process: be sure to close those ends of the pipe you no longer need:
CloseHandle(g_hChildStd_IN_Rd)
CloseHandle(g_hChildStd_OUT_Wr)
Leaving:
╔══════════════════╗ ╔══════════════════╗
║ Parent Process ║ ║ Child Process ║
╠══════════════════╣ ╠══════════════════╣
║ ║ ║ ║
║ g_hChildStd_IN_Wr╟───────────────>║ ║
║ ║ ║ ║
║g_hChildStd_OUT_Rd║<───────────────╢ ║
║ ║ ║ ║
╚══════════════════╝ ╚══════════════════╝
Or more fully:
STARTUP_INFO si;
PROCESS_INFO pi;
result = CreateProcess(..., ref si, ref pi);
//Bonus chatter: A common bug among a lot of programmers:
// they don't realize they are required to call CloseHandle
// on the two handles placed in PROCESS_INFO.
// That's why you should call ShellExecute - it closes them for you.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
/*
We've given the console app the writable end of the pipe during CreateProcess; we don't need it anymore.
We do keep the handle for the *readable* end of the pipe; as we still need to read from it.
The other reason to close the writable-end handle now is so that there's only one out-standing reference to the writeable end: held by the child process.
When the child processes closes, it will close the pipe, and
your call to ReadFile will fail with error code:
109 (The pipe has been ended).
That's how we'll know the console app is done. (no need to wait on process handles with buggy infinite waits)
*/
CloseHandle(g_hChildStd_OUT_Wr);
g_hChildStd_OUT_Wr = 0;
CloseHandle(g_hChildStd_IN_Rd);
g_hChildStd_OUT_Wr = 0;
Waiting on the Child Process (aka the deadlock waiting to happen)
The common problem with most solutions is that people try to wait on a process handle.
they create event objects
they try to MsgWait for events to be signaled
they try to MsgWait for child processes to end
That's wrong. That's all wrong.
There are many problems with these ideas; the main one being:
if you try to wait for the child the terminate
the child will never be able to terminate
If the child is trying to send you output through the pipe, and you're INFINITE waiting, you're not emptying your end of the pipe. Eventually the pipe the child is writing to becomes full. When the child tries to write to a pipe that is full, its WriteFile call waits (i.e. Blocks) for the pipe to have some room.
you're blocked waiting on the child
the child attempts to write to the pipe
you're blocked waiting on the child, so you're not reading data out of the pipe
the pipe becomes full
the child blocks waiting on you
both parent and child are blocked waiting on the other
deadlock
As a result the child process will never terminate; you've deadlocked everything.
The Right Approach - let the client do it's thing
The correct solution comes by simply reading from the pipe.
Once the child process terminates,
it will CloseHandle on its end of the pipes.
The next time you try to read from the pipe
you'll be told the pipe has been closed (ERROR_BROKEN_PIPE).
That's how you know the process is done and you have no more stuff to read.
 
String outputText = "";
//Read will return when the buffer is full, or if the pipe on the other end has been broken
while (ReadFile(stdOutRead, aBuf, Length(aBuf), &bytesRead, null)
outputText = outputText + Copy(aBuf, 1, bytesRead);
//ReadFile will either tell us that the pipe has closed, or give us an error
DWORD le = GetLastError;
//And finally cleanup
CloseHandle(g_hChildStd_IN_Wr);
CloseHandle(g_hChildStd_OUT_Rd);
if (le != ERROR_BROKEN_PIPE) //"The pipe has been ended."
RaiseLastOSError(le);
All without a dangerous MsgWaitForSingleObject - which is error-prone, difficult to use correctly, and causes the very bug you want to avoid.
Complete Example
We all know what we are using this for: run a child process, and capture it's console output.
Here is some sample Delphi code:
function ExecuteAndCaptureOutput(CommandLine: string): string;
var
securityAttributes: TSecurityAttributes;
stdOutRead, stdOutWrite: THandle;
startupInfo: TStartupInfo;
pi: TProcessInformation;
buffer: AnsiString;
bytesRead: DWORD;
bRes: Boolean;
le: DWORD;
begin
{
Execute a child process, and capture it's command line output.
}
Result := '';
securityAttributes.nlength := SizeOf(TSecurityAttributes);
securityAttributes.bInheritHandle := True;
securityAttributes.lpSecurityDescriptor := nil;
if not CreatePipe({var}stdOutRead, {var}stdOutWrite, #securityAttributes, 0) then
RaiseLastOSError;
try
// Set up members of the STARTUPINFO structure.
startupInfo := Default(TStartupInfo);
startupInfo.cb := SizeOf(startupInfo);
// This structure specifies the STDIN and STDOUT handles for redirection.
startupInfo.dwFlags := startupInfo.dwFlags or STARTF_USESTDHANDLES; //The hStdInput, hStdOutput, and hStdError handles will be valid.
startupInfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE); //don't forget to make it valid (zero is not valid)
startupInfo.hStdOutput := stdOutWrite; //give the console app the writable end of the pipe
startupInfo.hStdError := stdOutWrite; //give the console app the writable end of the pipe
// We also want the console window to be hidden
startupInfo.dwFlags := startupInfo.dwFlags or STARTF_USESHOWWINDOW; //The nShowWindow member member will be valid.
startupInfo.wShowWindow := SW_HIDE; //default is that the console window is visible
// Set up members of the PROCESS_INFORMATION structure.
pi := Default(TProcessInformation);
//WARNING: The Unicode version of CreateProcess can modify the contents of CommandLine.
//Therefore CommandLine cannot point to read-only memory.
//We can ensure it's not read-only with the RTL function UniqueString
UniqueString({var}CommandLine);
bRes := CreateProcess(nil, PChar(CommandLine), nil, nil, True, 0, nil, nil, startupInfo, {var}pi);
if not bRes then
RaiseLastOSError;
//CreateProcess demands that we close these two populated handles when we're done with them. We're done with them.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
{
We've given the console app the writable end of the pipe during CreateProcess; we don't need it anymore.
We do keep the handle for the *readable* end of the pipe; as we still need to read from it.
The other reason to close the writable-end handle now is so that there's only one out-standing reference to the writeable end: held by the console app.
When the app closes, it will close the pipe, and ReadFile will return code 109 (The pipe has been ended).
That's how we'll know the console app is done. (no need to wait on process handles)
}
CloseHandle(stdOutWrite);
stdOutWrite := 0;
SetLength(buffer, 4096);
//Read will return when the buffer is full, or if the pipe on the other end has been broken
while ReadFile(stdOutRead, buffer[1], Length(buffer), {var}bytesRead, nil) do
Result := Result + string(Copy(buffer, 1, bytesRead));
//ReadFile will either tell us that the pipe has closed, or give us an error
le := GetLastError;
if le <> ERROR_BROKEN_PIPE then //"The pipe has been ended."
RaiseLastOSError(le);
finally
CloseHandle(stdOutRead);
if stdOutWrite <> 0 then
CloseHandle(stdOutWrite);
end;
end;
Ian Boyd's answer had this gem: Once you've launched your child process: be sure to close those ends of the pipe you no longer need.
I've produced another version of the CreatePipe + CreateProcess solution which, I hope, is more clear:
int main()
{
BOOL ok = TRUE;
HANDLE hStdInPipeRead = NULL;
HANDLE hStdInPipeWrite = NULL;
HANDLE hStdOutPipeRead = NULL;
HANDLE hStdOutPipeWrite = NULL;
// Create two pipes.
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
ok = CreatePipe(&hStdInPipeRead, &hStdInPipeWrite, &sa, 0);
if (ok == FALSE) return -1;
ok = CreatePipe(&hStdOutPipeRead, &hStdOutPipeWrite, &sa, 0);
if (ok == FALSE) return -1;
// Create the process.
STARTUPINFO si = { };
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdError = hStdOutPipeWrite;
si.hStdOutput = hStdOutPipeWrite;
si.hStdInput = hStdInPipeRead;
PROCESS_INFORMATION pi = { };
LPCWSTR lpApplicationName = L"C:\\Windows\\System32\\cmd.exe";
LPWSTR lpCommandLine = (LPWSTR)L"C:\\Windows\\System32\\cmd.exe /c dir";
LPSECURITY_ATTRIBUTES lpProcessAttributes = NULL;
LPSECURITY_ATTRIBUTES lpThreadAttribute = NULL;
BOOL bInheritHandles = TRUE;
DWORD dwCreationFlags = 0;
LPVOID lpEnvironment = NULL;
LPCWSTR lpCurrentDirectory = NULL;
ok = CreateProcess(
lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttribute,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
&si,
&pi);
if (ok == FALSE) return -1;
// Close pipes we do not need.
CloseHandle(hStdOutPipeWrite);
CloseHandle(hStdInPipeRead);
// The main loop for reading output from the DIR command.
char buf[1024 + 1] = { };
DWORD dwRead = 0;
DWORD dwAvail = 0;
ok = ReadFile(hStdOutPipeRead, buf, 1024, &dwRead, NULL);
while (ok == TRUE)
{
buf[dwRead] = '\0';
OutputDebugStringA(buf);
puts(buf);
ok = ReadFile(hStdOutPipeRead, buf, 1024, &dwRead, NULL);
}
// Clean up and exit.
CloseHandle(hStdOutPipeRead);
CloseHandle(hStdInPipeWrite);
DWORD dwExitCode = 0;
GetExitCodeProcess(pi.hProcess, &dwExitCode);
return dwExitCode;
}
Some notes:
The pipe for StdIn is not really required:
This is because the DIR command doesn't require user input (but, I left it in the code, since it is a good template for running other commands)
everyting to do with hStdInPipeRead & hStdInPipeWrite can be omitted
setting si.hStdInput can be omitted
Replace hardcoded L"C:\\Windows\\System32\\cmd.exe" with reading the COMSPEC environment variable.
Replace LPWSTR with LPTSTR if we wish to compile for non-UNICODE.
Replace cmd.exe /k DIR with cmd.exe /c DIR since when the DIR command finishes we don't really want the cmd.exe to stick around.
I too have same scenario. in my case from Lib, need to execute internal exe and read output. The following works without any issues.
void executeCMDInNewProcessAndReadOutput(LPSTR lpCommandLine)
{
STARTUPINFO si;
SECURITY_ATTRIBUTES sa;
PROCESS_INFORMATION pi;
HANDLE g_hChildStd_IN_Rd, g_hChildStd_OUT_Wr, g_hChildStd_OUT_Rd, g_hChildStd_IN_Wr; //pipe handles
char buf[1024]; //i/o buffer
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
if (CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &sa, 0)) //create stdin pipe
{
if (CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0)) //create stdout pipe
{
//set startupinfo for the spawned process
/*The dwFlags member tells CreateProcess how to make the process.
STARTF_USESTDHANDLES: validates the hStd* members.
STARTF_USESHOWWINDOW: validates the wShowWindow member*/
GetStartupInfo(&si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
//set the new handles for the child process
si.hStdOutput = g_hChildStd_OUT_Wr;
si.hStdError = g_hChildStd_OUT_Wr;
si.hStdInput = g_hChildStd_IN_Rd;
//spawn the child process
if (CreateProcess(NULL, lpCommandLine, NULL, NULL, TRUE, CREATE_NEW_CONSOLE,
NULL, NULL, &si, &pi))
{
unsigned long bread; //bytes read
unsigned long avail; //bytes available
memset(buf, 0, sizeof(buf));
for (;;)
{
PeekNamedPipe(g_hChildStd_OUT_Rd, buf, 1023, &bread, &avail, NULL);
//check to see if there is any data to read from stdout
if (bread != 0)
{
if (ReadFile(g_hChildStd_OUT_Rd, buf, 1023, &bread, NULL))
{
break;
}
}
}
//clean up all handles
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(g_hChildStd_IN_Rd);
CloseHandle(g_hChildStd_OUT_Wr);
CloseHandle(g_hChildStd_OUT_Rd);
CloseHandle(g_hChildStd_IN_Wr);
}
else
{
CloseHandle(g_hChildStd_IN_Rd);
CloseHandle(g_hChildStd_OUT_Wr);
CloseHandle(g_hChildStd_OUT_Rd);
CloseHandle(g_hChildStd_IN_Wr);
}
}
else
{
CloseHandle(g_hChildStd_IN_Rd);
CloseHandle(g_hChildStd_IN_Wr);
}
}
}
Here is an example (taken from a larger program) of a thread that does what you are looking for. It creates pipes for stdout and stderr for the process it creates then goes into a loop reading those pipes until the program finishes.
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
#define EVENT_NAME "Global\\RunnerEvt"
HANDLE hev;
SECURITY_ATTRIBUTES psa;
InitSAPtr(&psa);
DWORD waitRc;
DWORD bytesRead;
int manual_triggered = 1;
hev = CreateEvent(&psa, FALSE, FALSE, EVENT_NAME);
// Create pipes we'll read
for(;;)
{
if (manual_triggered)
{
waitRc = WAIT_OBJECT_0;
manual_triggered = 0;
}
else
{
waitRc = WaitForSingleObject(hev, 500);
}
if (waitRc == WAIT_OBJECT_0)
{
`logprint`f(LOG_DBG, "Received command to run process\n");
CreateChildOutFile();
stdOutEvt = CreateEvent(&psa, TRUE, FALSE, 0);
stdOutOvl.hEvent = stdOutEvt;
stdErrEvt = CreateEvent(&psa, TRUE, FALSE, 0);
stdErrOvl.hEvent = stdErrEvt;
gStdOutReadHand = CreateNamedPipe(STD_OUT_PIPE_NAME, PIPE_ACCESS_DUPLEX + FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE + PIPE_READMODE_BYTE,
PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &psa);
if (gStdOutReadHand == INVALID_HANDLE_VALUE)
{
log(LOG_DBG, "Error %d on create STDOUT pipe\n", GetLastError());
}
gStdErrReadHand = CreateNamedPipe(STD_ERR_PIPE_NAME, PIPE_ACCESS_DUPLEX + FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE + PIPE_READMODE_BYTE,
PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &psa);
if (gStdErrReadHand == INVALID_HANDLE_VALUE)
{
log(LOG_DBG, "Error %d on create STDERR pipe\n", GetLastError());
}
runProcess();
log(LOG_DBG, "After runProcess, new PID is %d/%x\n", piProcInfo.dwProcessId, piProcInfo.dwProcessId);
if (piProcInfo.dwProcessId == 0)
{
log(LOG_DBG, "runProcess failed, closing child STDIN/STDERR\n");
closeChildPipes();
#define FAIL_MSG "Child process failed to start\n"
writeChildOutFile(FAIL_MSG, strlen(FAIL_MSG) );
CloseHandle(hChildOut);
}
else
{
log(LOG_DBG, "Child process created, setting up for redir/restart/termination\n");
issueRead(gStdOutReadHand, &stdOutOvl, stdOutBuff, &stdOutBytesAvail);
//log(LOG_DBG, "After read set on STDOUT\n");
issueRead(gStdErrReadHand, &stdErrOvl, stdErrBuff, &stdErrBytesAvail);
//log(LOG_DBG, "After read set on STDERR\n");
HANDLE harr[4];
for(;;)
{
harr[0] = hev;
harr[1] = piProcInfo.hProcess;
harr[2] = stdOutEvt;
harr[3] = stdErrEvt;
DWORD waitRc2 = WaitForMultipleObjects(4, harr, FALSE, 500);
#if 0
if (waitRc2 == -1)
{
log(LOG_DBG, "Wait error %d\n", GetLastError());
Sleep(500);
}
log(LOG_DBG, "waitRc2 %d\n", waitRc2);
#endif
if ((waitRc2 - WAIT_OBJECT_0) == 0)
{
log(LOG_DBG, "Woke up because another trigger command was received\n");
#define NEW_CMD_MSG "Child process is being terminated because new trigger received\n"
writeChildOutFile(NEW_CMD_MSG, strlen(NEW_CMD_MSG));
terminateChild();
CloseHandle(hChildOut);
manual_triggered = 1;
break;
}
else if ((waitRc2 - WAIT_OBJECT_0) == 1)
{
//log(LOG_DBG, "Woke up because child has terminated\n");
closeChildPipes();
#define NORM_MSG "Normal child process termination\n"
writeChildOutFile(NORM_MSG, strlen(NORM_MSG));
CloseHandle(hChildOut);
break;
}
else if ((waitRc2 - WAIT_OBJECT_0) == 2)
{
//log(LOG_DBG, "Woke up because child has stdout\n");
if (GetOverlappedResult(gStdOutReadHand, &stdOutOvl, &bytesRead, TRUE))
{
writeChildOutFile(stdOutBuff, bytesRead);
ResetEvent(stdOutEvt);
issueRead(gStdOutReadHand, &stdOutOvl, stdOutBuff, &stdOutBytesAvail);
}
}
else if ((waitRc2 - WAIT_OBJECT_0) == 3)
{
//log(LOG_DBG, "Woke up because child has stderr\n");
if (GetOverlappedResult(gStdErrReadHand, &stdErrOvl, &bytesRead, TRUE))
{
writeChildOutFile(stdErrBuff, bytesRead);
ResetEvent(stdErrEvt);
issueRead(gStdErrReadHand, &stdErrOvl, stdErrBuff, &stdErrBytesAvail);
}
}
else
{
if (gShuttingDown)
{
log(LOG_DBG, "Woke with active child and service is terminating\n");
#define SHUTDOWN_MSG "Child process is being terminated because the service is shutting down\n"
writeChildOutFile(SHUTDOWN_MSG, strlen(SHUTDOWN_MSG));
terminateChild();
CloseHandle(hChildOut);
break;
}
}
if (gShuttingDown)
{
break;
}
}
}
}
else if (gShuttingDown)
{
break;
}
CloseHandle(gStdOutReadHand);
CloseHandle(gStdErrReadHand);
}
return 0;
}
void writeChildOutFile(char *msg, int len)
{
DWORD bytesWritten;
WriteFile(hChildOut, msg, len, &bytesWritten, 0);
}
void terminateChild(void)
{
if (piProcInfo.dwProcessId != 0)
{
TerminateProcess(piProcInfo.hProcess, -1);
CloseHandle(piProcInfo.hThread);
CloseHandle(piProcInfo.hProcess);
closeChildPipes();
}
}
void closeChildPipes(void)
{
CloseHandle(g_hChildStd_OUT_Wr);
CloseHandle(g_hChildStd_ERR_Wr);
}
void runProcess(void)
{
SECURITY_ATTRIBUTES saAttr;
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
TCHAR szCmdline[]=TEXT("cmd.exe /C C:\\temp\\RunnerService.bat");
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
g_hChildStd_OUT_Wr = CreateFile (STD_OUT_PIPE_NAME,
FILE_WRITE_DATA,
0,
&saAttr,
OPEN_EXISTING,
0,
NULL);
if (g_hChildStd_OUT_Wr == INVALID_HANDLE_VALUE)
{
log(LOG_DBG, "Error creating child proc stdout file %d\n", GetLastError());
}
g_hChildStd_ERR_Wr = CreateFile (STD_ERR_PIPE_NAME,
FILE_WRITE_DATA,
0,
&saAttr,
OPEN_EXISTING,
0,
NULL);
if (g_hChildStd_ERR_Wr == INVALID_HANDLE_VALUE)
{
log(LOG_DBG, "Error creating child proc stderr file %d\n", GetLastError());
}
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdError = g_hChildStd_ERR_Wr;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
bSuccess = CreateProcess(NULL,
szCmdline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
}
void CreateChildOutFile(void)
{
SYSTEMTIME st;
SECURITY_ATTRIBUTES sa;
char fName[_MAX_PATH];
InitSAPtr(&sa);
GetLocalTime(&st);
sprintf(fName, "C:\\TEMP\\runsvcchild_%02d_%02d_%02d_%04d.out", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
hChildOut = CreateFile(fName, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
}
void issueRead(HANDLE hFile, OVERLAPPED *overLapped, char *buf, DWORD *dwRead)
{
//log(LOG_DBG, "Start of issueRead, hfile %08x, ovl is %08x\n", hFile, overLapped);
BOOL brc = ReadFile(hFile, buf, 4096, dwRead, overLapped);
if (!brc)
{
DWORD dwle = GetLastError();
if (dwle != ERROR_IO_PENDING)
{
log(LOG_DBG, "Error %d on ReadFile\n", dwle);
}
}
else
{
// log(LOG_DBG, "Read issued\n");
}
}
I think you did everything right. But cmd.exe prints nothing or very little amount of data after start and your ReadFile blocks. If you move your cycle
while (true) {
bStatus = ReadFile(g_hChildStd_OUT_Rd, aBuf, sizeof(aBuf), &dwRead, NULL);
if (!bStatus || dwRead == 0) {
break;
}
aBuf[dwRead] = '\0';
printf("%s\n", aBuf);
}
into background thread and run other cycle which will read your input and send it to cmd.exe, I think you can see any effect.
Either you can make read buffer smaller (16 bytes e.g.).
I tried Stephen Quan's answer out and got a segfault. Perhaps someone with more experience might know why that is. At any rate, this should be a more correct example of what he was trying to do:
#include <windows.h>
#include <cstddef>
#include <string>
#include <vector>
#include <cwchar>
using std::string;
using std::wstring;
using std::vector;
using std::size_t;
static inline wstring widen(string str) {
size_t wchar_count = str.size() + 1;
vector<wchar_t> buf(wchar_count);
return wstring{ buf.data(), (size_t)MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buf.data(), (int)wchar_count) };
}
static inline string narrow(wstring wstr) {
int nbytes = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), NULL, 0, NULL, NULL);
vector<char> buf(nbytes);
return string{ buf.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), buf.data(), nbytes, NULL, NULL) };
}
string evaluate_shell(string command) {
string output;
wstring wstr_command = widen(command);
wchar_t cwstr_command[32768];
wcsncpy(cwstr_command, wstr_command.c_str(), 32768);
BOOL ok = TRUE;
HANDLE hStdInPipeRead = NULL;
HANDLE hStdInPipeWrite = NULL;
HANDLE hStdOutPipeRead = NULL;
HANDLE hStdOutPipeWrite = NULL;
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
ok = CreatePipe(&hStdInPipeRead, &hStdInPipeWrite, &sa, 0);
if (ok == FALSE) return "";
ok = CreatePipe(&hStdOutPipeRead, &hStdOutPipeWrite, &sa, 0);
if (ok == FALSE) return "";
STARTUPINFOW si = { };
si.cb = sizeof(STARTUPINFOW);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdError = hStdOutPipeWrite;
si.hStdOutput = hStdOutPipeWrite;
si.hStdInput = hStdInPipeRead;
PROCESS_INFORMATION pi = { };
if (CreateProcessW(NULL, cwstr_command, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
while (WaitForSingleObject(pi.hProcess, 5) == WAIT_TIMEOUT) {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
CloseHandle(hStdOutPipeWrite);
CloseHandle(hStdInPipeRead);
char buffer[4096] = { };
DWORD dwRead = 0;
ok = ReadFile(hStdOutPipeRead, buffer, 4095, &dwRead, NULL);
while (ok == TRUE) {
buffer[dwRead] = 0;
ok = ReadFile(hStdOutPipeRead, buffer, 4095, &dwRead, NULL);
}
CloseHandle(hStdOutPipeRead);
CloseHandle(hStdInPipeWrite);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
output = narrow(widen(buffer));
while (output.back() == '\r' || output.back() == '\n')
output.pop_back();
}
return output;
}

I've managed to port some code from msdn to MinGW to capture stdout from child app, but it won't exit, what wrong here?

The code, sory it is abit too long but I've managed it to shorten it only to such size, the key issue is (I think) with this strange for loop at the end. No, I don't know why the loop header is empty, microsoft want's it that way.
The problem is that the code waits to eternity for yet more data from child app.
The page with full algorighm: http://msdn.microsoft.com/en-us/library/ms682499(VS.85).aspx
(Yes, I know it's a mess, but it is self sustained mess at least.)
#include <iostream>
#include <stdio.h>
#include <windows.h>
using namespace std;
#define BUFSIZE 4096
int main() {
SECURITY_ATTRIBUTES saAttr;
printf("\n->Start of parent execution.\n");
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0);
// Ensure the read handle to the pipe for STDOUT is not inherited.
SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0);
// Create a pipe for the child process's STDIN.
HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0);
// Ensure the write handle to the pipe for STDIN is not inherited.
SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0);
// Create the child process.
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
char szCmdline[]="cmd /c dir";
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bCreateSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
bCreateSuccess = CreateProcess(NULL,
szCmdline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bWriteSuccess = FALSE;
BOOL bReadSuccess = FALSE;
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
for (;;) {
bReadSuccess = ReadFile( g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
if( ! bReadSuccess || dwRead == 0 ) break;
bReadSuccess = WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL);
if (! bReadSuccess ) break;
}
printf("\n->End of parent execution.\n");
return 0;
}
From the looks of things, you've forgotten to close the parent's handles to the write-end of the pipes you're passing to the child process. Since there's still a valid write handle to the pipe, the system can't detect that writing to the pipe is no longer possible, and you'll wait infinitely for the child to finish.
If you only need to capture the child's standard output, _popen may be a lot easier way to do it.
Edit: Okay, some ancient code to spawn a child process with all three of its standard streams directed to pipes that connect to the parent. This is a lot longer than it should be for such a simple task, but such is life with the Windows API. To be fair, it probably could be shorter, but it's 20 years old (or so). Neither the API nor the way I wrote code then is quite what it is now (though some might not consider my newer code any improvement).
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <ctype.h>
#include <io.h>
#include <fcntl.h>
#include <stdlib.h>
#include "spawn.h"
static void system_error(char const *name) {
// A function to retrieve, format, and print out a message from the
// last error. The `name' that's passed should be in the form of a
// present tense noun (phrase) such as "opening file".
//
char *ptr = NULL;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
0,
GetLastError(),
0,
(char *)&ptr,
1024,
NULL);
fprintf(stderr, "%s\n", ptr);
LocalFree(ptr);
}
static void InitializeInheritableSA(SECURITY_ATTRIBUTES *sa) {
sa->nLength = sizeof *sa;
sa->bInheritHandle = TRUE;
sa->lpSecurityDescriptor = NULL;
}
static HANDLE OpenInheritableFile(char const *name) {
SECURITY_ATTRIBUTES sa;
HANDLE retval;
InitializeInheritableSA(&sa);
retval = CreateFile(
name,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if (INVALID_HANDLE_VALUE == retval) {
char buffer[100];
sprintf(buffer, "opening file %s", name);
system_error(buffer);
return retval;
}
}
static HANDLE CreateInheritableFile(char const *name, int mode) {
SECURITY_ATTRIBUTES sa;
HANDLE retval;
DWORD FSmode = mode ? OPEN_ALWAYS : CREATE_NEW;
InitializeInheritableSA(&sa);
retval = CreateFile(
name,
GENERIC_WRITE,
FILE_SHARE_READ,
&sa,
FSmode,
FILE_ATTRIBUTE_NORMAL,
0);
if (INVALID_HANDLE_VALUE == retval) {
char buffer[100];
sprintf(buffer, "creating file %s", name);
system_error(buffer);
return retval;
}
if ( mode == APPEND )
SetFilePointer(retval, 0, 0, FILE_END);
}
enum inheritance { inherit_read = 1, inherit_write = 2 };
static BOOL CreateInheritablePipe(HANDLE *read, HANDLE *write, int inheritance) {
SECURITY_ATTRIBUTES sa;
InitializeInheritableSA(&sa);
if ( !CreatePipe(read, write, &sa, 0)) {
system_error("Creating pipe");
return FALSE;
}
if (!inheritance & inherit_read)
DuplicateHandle(
GetCurrentProcess(),
*read,
GetCurrentProcess(),
NULL,
0,
FALSE,
DUPLICATE_SAME_ACCESS);
if (!inheritance & inherit_write)
DuplicateHandle(
GetCurrentProcess(),
*write,
GetCurrentProcess(),
NULL,
0,
FALSE,
DUPLICATE_SAME_ACCESS);
return TRUE;
}
static BOOL find_image(char const *name, char *buffer) {
// Try to find an image file named by the user.
// First search for the exact file name in the current
// directory. If that's found, look for same base name
// with ".com", ".exe" and ".bat" appended, in that order.
// If we can't find it in the current directory, repeat
// the entire process on directories specified in the
// PATH environment variable.
//
#define elements(array) (sizeof(array)/sizeof(array[0]))
static char *extensions[] = {".com", ".exe", ".bat", ".cmd"};
int i;
char temp[FILENAME_MAX];
if (-1 != access(name, 0)) {
strcpy(buffer, name);
return TRUE;
}
for (i=0; i<elements(extensions); i++) {
strcpy(temp, name);
strcat(temp, extensions[i]);
if ( -1 != access(temp, 0)) {
strcpy(buffer, temp);
return TRUE;
}
}
_searchenv(name, "PATH", buffer);
if ( buffer[0] != '\0')
return TRUE;
for ( i=0; i<elements(extensions); i++) {
strcpy(temp, name);
strcat(temp, extensions[i]);
_searchenv(temp, "PATH", buffer);
if ( buffer[0] != '\0')
return TRUE;
}
return FALSE;
}
static HANDLE DetachProcess(char const *name, HANDLE const *streams) {
STARTUPINFO s;
PROCESS_INFORMATION p;
char buffer[FILENAME_MAX];
memset(&s, 0, sizeof s);
s.cb = sizeof(s);
s.dwFlags = STARTF_USESTDHANDLES;
s.hStdInput = streams[0];
s.hStdOutput = streams[1];
s.hStdError = streams[2];
if ( !find_image(name, buffer)) {
system_error("Finding Image file");
return INVALID_HANDLE_VALUE;
}
// Since we've redirected the standard input, output and error handles
// of the child process, we create it without a console of its own.
// (That's the `DETACHED_PROCESS' part of the call.) Other
// possibilities include passing 0 so the child inherits our console,
// or passing CREATE_NEW_CONSOLE so the child gets a console of its
// own.
//
if (!CreateProcess(
NULL,
buffer, NULL, NULL,
TRUE,
DETACHED_PROCESS,
NULL, NULL,
&s,
&p))
{
system_error("Spawning program");
return INVALID_HANDLE_VALUE;
}
// Since we don't need the handle to the child's thread, close it to
// save some resources.
CloseHandle(p.hThread);
return p.hProcess;
}
static HANDLE StartStreamHandler(ThrdProc proc, HANDLE stream) {
DWORD ignore;
return CreateThread(
NULL,
0,
proc,
(void *)stream,
0,
&ignore);
}
HANDLE CreateDetachedProcess(char const *name, stream_info *streams) {
// This Creates a detached process.
// First parameter: name of process to start.
// Second parameter: names of files to redirect the standard input, output and error
// streams of the child to (in that order.) Any file name that is NULL will be
// redirected to an anonymous pipe connected to the parent.
// Third Parameter: handles of the anonymous pipe(s) for the standard input, output
// and/or error streams of the new child process.
//
// Return value: a handle to the newly created process.
//
HANDLE child_handles[3];
HANDLE process;
int i;
// First handle the child's standard input. This is separate from the
// standard output and standard error because it's going the opposite
// direction. Basically, we create either a handle to a file the child
// will use, or else a pipe so the child can communicate with us.
//
if ( streams[0].filename != NULL ) {
streams[0].handle = NULL;
child_handles[0] = OpenInheritableFile(streams[0].filename);
}
else
CreateInheritablePipe(child_handles, &(streams[0].handle), inherit_read);
// Now handle the child's standard output and standard error streams. These
// are separate from the code above simply because they go in the opposite
// direction.
//
for ( i=1; i<3; i++)
if ( streams[i].filename != NULL) {
streams[i].handle = NULL;
child_handles[i] = CreateInheritableFile(streams[i].filename, APPEND);
}
else
CreateInheritablePipe(&(streams[i].handle), child_handles+i, inherit_write);
// Now that we've set up the pipes and/or files the child's going to use,
// we're ready to actually start up the child process:
process = DetachProcess(name, child_handles);
if (INVALID_HANDLE_VALUE == process)
return process;
// Now that we've started the child, we close our handles to its ends of the pipes.
// If one or more of these happens to a handle to a file instead, it doesn't really
// need to be closed, but it doesn't hurt either. However, with the child's standard
// output and standard error streams, it's CRUCIAL to close our handles if either is a
// handle to a pipe. The system detects the end of data on a pipe when ALL handles to
// the write end of the pipe are closed -- if we still have an open handle to the
// write end of one of these pipes, we won't be able to detect when the child is done
// writing to the pipe.
//
for ( i=0; i<3; i++) {
CloseHandle(child_handles[i]);
if ( streams[i].handler )
streams[i].handle =
StartStreamHandler(streams[i].handler, streams[i].handle);
}
return process;
}
#ifdef TEST
#define buf_size 256
unsigned long __stdcall handle_error(void *pipe) {
// The control (and only) function for a thread handling the standard
// error from the child process. We'll handle it by displaying a
// message box each time we receive data on the standard error stream.
//
char buffer[buf_size];
HANDLE child_error_rd = (HANDLE)pipe;
unsigned bytes;
while (ERROR_BROKEN_PIPE != GetLastError() &&
ReadFile(child_error_rd, buffer, 256, &bytes, NULL))
{
buffer[bytes+1] = '\0';
MessageBox(NULL, buffer, "Error", MB_OK);
}
return 0;
}
unsigned long __stdcall handle_output(void *pipe) {
// A similar thread function to handle standard output from the child
// process. Nothing special is done with the output - it's simply
// displayed in our console. However, just for fun it opens a C high-
// level FILE * for the handle, and uses fgets to read it. As
// expected, fgets detects the broken pipe as the end of the file.
//
char buffer[buf_size];
int handle;
FILE *file;
handle = _open_osfhandle((long)pipe, _O_RDONLY | _O_BINARY);
file = _fdopen(handle, "r");
if ( NULL == file )
return 1;
while ( fgets(buffer, buf_size, file))
printf("%s", buffer);
return 0;
}
int main(int argc, char **argv) {
stream_info streams[3];
HANDLE handles[3];
int i;
if ( argc < 3 ) {
fputs("Usage: spawn prog datafile"
"\nwhich will spawn `prog' with its standard input set to"
"\nread from `datafile'. Then `prog's standard output"
"\nwill be captured and printed. If `prog' writes to its"
"\nstandard error, that output will be displayed in a"
"\nMessageBox.\n",
stderr);
return 1;
}
memset(streams, 0, sizeof(streams));
streams[0].filename = argv[2];
streams[1].handler = handle_output;
streams[2].handler = handle_error;
handles[0] = CreateDetachedProcess(argv[1], streams);
handles[1] = streams[1].handle;
handles[2] = streams[2].handle;
WaitForMultipleObjects(3, handles, TRUE, INFINITE);
for ( i=0; i<3; i++)
CloseHandle(handles[i]);
return 0;
}
#endif