Creating a Child Process with Redirected Input and Output Program Example
The following example demonstrates how to create a child process using the CreateProcess() function from a console process. It also demonstrates a technique for using anonymous pipes to redirect the child process's standard input and output handles. Note that named pipes can also be used to redirect process I/O. The CreatePipe() function uses the SECURITY_ATTRIBUTES structure to create inheritable handles to the read and write ends of two pipes. The read end of one pipe serves as standard input for the child process, and the write end of the other pipe is the standard output for the child process. These pipe handles are specified in the STARTUPINFO structure, which makes them the standard handles inherited by the child process. The parent process uses the opposite ends of these two pipes to write to the child process's input and read from the child process's output. As specified in the STARTUPINFO structure, these handles are also inheritable. However, these handles must not be inherited. Therefore, before creating the child process, the parent process uses the SetHandleInformation() function to ensure that the write handle for the child process's standard input and the read handle for the child process's standard input cannot be inherited. The following is the code for the parent process. It takes a single command-line argument: the name of a text file.
Create a new empty Win32 console application project. Give a suitable project name and change the project location if needed.
Then, add the source file and give it a suitable name.
Next, add the following source code.
#include <windows.h>
#include <stdio.h>
#include <strsafe.h>
// Constant
#define BUFSIZE 4096
// Global variables
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;
// Prototypes, needed for C++
void CreateChildProcess(void);
void WriteToPipe(void);
void ReadFromPipe(void);
void ErrorExit(PTSTR);
// wmain() is a main process...
int wmain(int argc, WCHAR *argv[])
{
SECURITY_ATTRIBUTES saAttr;
wprintf(LStart of parent execution.\n);
wprintf(LParent process ID %u\n, GetCurrentProcessId());
wprintf(LParent thread ID %u\n, GetCurrentThreadId());
// 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(LCreatePipe() - pipe for child process\'s STDOUT failed);
else
wprintf(LCreatePipe() - pipe for child process\'s STDOUT pipe was created!\n);
// Ensure the read handle to the pipe for STDOUT is not inherited
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
ErrorExit(LSetHandleInformation() - pipe STDOUT read handle failed for inheritance);
else
wprintf(LSetHandleInformation() - pipe STDOUT read handle is not inherited!\n);
// Create a pipe for the child process's STDIN
if (! CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
ErrorExit(LCreatePipe() - pipe for child\'s STDIN failed);
else
wprintf(LCreatePipe() - pipe for child process\'s STDIN was created!\n);
// Ensure the write handle to the pipe for STDIN is not inherited
if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
ErrorExit(LSetHandleInformation() - pipe STDIN write handle failed for inheritance);
else
wprintf(LStdin SetHandleInformation() - pipe STDIN read handle is not inherited!\n);
// Create the child process
wprintf(LVerify: argv[1] = %s\n, argv[1]);
wprintf(LCreating the child process...\n);
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(LPlease specify an input file.\n);
wprintf(L%s [sample_text_file.txt]\n, argv[0]);
}
g_hInputFile = CreateFile(
argv[1],
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_READONLY,
NULL);
if(g_hInputFile == INVALID_HANDLE_VALUE)
ErrorExit(LCreateFile());
else
wprintf(LCreateFile() - %s was successfully opened!\n, argv[1]);
// 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
WriteToPipe();
wprintf(L Contents of %s written to child STDIN pipe.\n, argv[1]);
// Read from pipe that is the standard output for child process
wprintf(L Contents of %s child process STDOUT:\n, argv[1]);
ReadFromPipe();
wprintf(L 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;
}
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
void CreateChildProcess()
{
// The following should be the child executable, see the next program example
// Change the path accordingly...
WCHAR szCmdline[]=L\\\\?\\C:\\amad\\ChildProcess\\Debug\\ChildProcess.exe;
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, // Use szCmdLine
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
// If an error occurs, exit the application.
if (!bSuccess)
ErrorExit(LCreateProcess() - child);
else
{
wprintf(L\nChild process ID is: %u\n, GetCurrentProcessId());
wprintf(LChild thread ID is: %u\n, GetCurrentThreadId());
// 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
if(CloseHandle(piProcInfo.hProcess) != 0)
wprintf(LpiProcInfo.hProcess handle was closed!\n);
else
ErrorExit(LCloseHandle(piProcInfo.hProcess));
if(CloseHandle(piProcInfo.hThread) != 0)
wprintf(LpiProcInfo.hThread handle was closed!\n);
else
ErrorExit(LCloseHandle(piProcInfo.hThread));
}
}
// Read from a file and write its contents to the pipe for the child's STDIN.
// Stop when there is no more data.
void WriteToPipe(void)
{
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
for(;;)
{
bSuccess = ReadFile(g_hInputFile, chBuf, BUFSIZE, &dwRead, NULL);
if (! bSuccess || dwRead == 0)
{
// Cannot use ErrorExit() lol
wprintf(L\nReadFile() - Failed to read file! Error %u\n, GetLastError());
break;
}
else
wprintf(L\nReadFile() - Reading from a file...\n);
bSuccess = WriteFile(g_hChildStd_IN_Wr, chBuf, dwRead, &dwWritten, NULL);
if (!bSuccess)
{
wprintf(L\nWriteFile() - Failed to write to pipe for child\'s STDIN! Error %u\n, GetLastError());
break;
}
else
wprintf(L\nWriteFile() - writing to the pipe for the child\'s STDIN...\n);
}
// Close the pipe handle so the child process stops reading
if (!CloseHandle(g_hChildStd_IN_Wr))
ErrorExit(LCloseHandle());
else
wprintf(LClosing the pipe handle...\n);
}
// 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(void)
{
DWORD dwRead, dwWritten;
WCHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
// Close the write end of the pipe before reading from the
// read end of the pipe, to control child process execution.
// The pipe is assumed to have enough buffer space to hold the
// data the child process has already written to it
if (!CloseHandle(g_hChildStd_OUT_Wr))
ErrorExit(LCloseHandle() - ReadFromPipe());
for(;;)
{
bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
if(!bSuccess || dwRead == 0)
{
wprintf(L\nReadFile() from child's standard output failed! Error %u\n, GetLastError());
break;
}
else
{
wprintf(L\nReadFile() from child's standard output is OK!\n);
}
bSuccess = WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL);
if(!bSuccess)
{
wprintf(L\nWriteFile() to parent's standard output failed! Error %u\n, GetLastError());
break;
}
else
{
wprintf(L\nWriteFile() to parent's standard output is OK!\n);
}
}
}
// Format a readable error message, display a message box, and exit from the application.
void ErrorExit(PTSTR lpszFunction)
{
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)lpszFunction)+40)*sizeof(WCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeof(WCHAR), L%s failed with error %d: %s, lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, LError, MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
ExitProcess(1);
}
Build the project. In order to see the output, create a text file with some texts under the project’s Debug folder as an input file.
Run the project without any argument. Click the OK button for the Error message.
After dismissing the Error message, the following is the output.
The following is the sample output with a text file (already created) as the argument.
The following sample output is possible because the child program already created (see next program example).