The Windows pipe, child process and redirection C program example
Compiler: Visual C++ Express Edition 2005
Compiled on Platform: Windows XP Pro SP2
Target platform: none, just for learning and fun
Header file: Standard and Windows
Additional library: Windows Platform SDK
Additional project setting: Set project to be compiled as C
Project -> your_project_name Properties -> Configuration Properties -> C/C++ -> Advanced -> Compiled As: Compiled as C Code (/TC)
Other info: non-CLR or unmanaged. Need to add netapi32.lib (netapi32.dll) to the project. Click the Project menu->Select the your_project_name Properties... sub menu->Expand the Configuration Properties folder on the left pane->Expand the Linker subfolder->Select the Input subfolder->Select the Additional Dependencies field on the right pane->Click the ... at the end of the field->Type in 'netapi32.lib' in the empty pane->Click the OK button->Click the OK button second time to close the project Properties dialog.
To do: Windows pipe, redirection and creating a child process
To show: The various Windows process, thread and pipe usage
#include <windows.h>
#include <stdio.h>
#include <strsafe.h>
// Constant
#define BUFSIZE 4096
// Global variables for standard child read/write handle
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;
// Function prototypes, needed for C++
void CreateChildProcess(void);
void WriteToPipe(void);
void ReadFromPipe(void);
void ErrorExit(PTSTR);
// wmain() is the main process...
int wmain(int argc, WCHAR *argv[])
{
SECURITY_ATTRIBUTES saAttr;
// Just some info
wprintf(L"Start of parent execution.\n");
wprintf(L"Parent process ID %u\n", GetCurrentProcessId());
wprintf(L"Parent thread ID %u\n", GetCurrentThreadId());
// Set the bInheritHandle flag so pipe handles are inherited
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
// Just a dummy (point to NULL), we do not implement it
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(L"CreatePipe() - pipe for child process\'s STDOUT failed");
else
wprintf(L"CreatePipe() - 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(L"SetHandleInformation() - pipe STDOUT read handle failed for inheritance");
else
wprintf(L"SetHandleInformation() - 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(L"CreatePipe() - pipe for child\'s STDIN failed");
else
wprintf(L"CreatePipe() - 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(L"SetHandleInformation() - pipe STDIN write handle failed for inheritance");
else
wprintf(L"Stdin SetHandleInformation() - pipe STDIN read handle is not inherited!\n");
// Create the child process
wprintf(L"Verify: argv[1] = %s\n", argv[1]);
wprintf(L"Creating 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(L"Please 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(L"CreateFile()");
else
wprintf(L"CreateFile() - %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(L"CreateProcess() - child");
else
{
wprintf(L"\nChild process ID is: %u\n", GetCurrentProcessId());
wprintf(L"Child 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(L"piProcInfo.hProcess handle was closed!\n");
else
ErrorExit(L"CloseHandle(piProcInfo.hProcess)");
if(CloseHandle(piProcInfo.hThread) != 0)
wprintf(L"piProcInfo.hThread handle was closed!\n");
else
ErrorExit(L"CloseHandle(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(L"CloseHandle()");
else
wprintf(L"Closing 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(L"CloseHandle() - 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, L"Error", MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
ExitProcess(1);
}
Output example: (run at command prompt)
(This program run at the command prompt)
C:\amad\buffalo\Debug>buffalo
Start of parent execution.
Parent process ID 3248
Parent thread ID 2492
CreatePipe() - pipe for child process's STDOUT pipe was created!
SetHandleInformation() - pipe STDOUT read handle is not inherited!
CreatePipe() - pipe for child process's STDIN was created!
Stdin SetHandleInformation() - pipe STDIN read handle is not inherited!
Verify: argv[1] = (null)
Creating the child process...
Next, we create a text file named justatextfile.txt under the debug folder (same folder as the executable) and rerun the program. We put some text in the file.
C:\amad\buffalo\Debug>buffalo justatextfile.txt
Start of parent execution.
Parent process ID 4180
Parent thread ID 836
CreatePipe() - pipe for child process's STDOUT pipe was created!
SetHandleInformation() - pipe STDOUT read handle is not inherited!
CreatePipe() - pipe for child process's STDIN was created!
Stdin SetHandleInformation() - pipe STDIN read handle is not inherited!
Verify: argv[1] = justatextfile.txt
Creating the child process...
Both runs generate the following error:
CreateProcess() - child failed with error 3: The system cannot find the path specified.
Because "\\\\?\\C:\\amad\\ChildProcess\\Debug\\ChildProcess.exe"; cannot be found in this case. We will provide the child executable in another example later or you can provide other process as the child process.
For example we create another program that create a process and run the Windows cmd command as the child process. The source code is given below (project name is : ChildProcess).
// For WinXp as a target, change accordingly
#define _WIN32_WINNT 0x0501
#include <windows.h>
#include <stdio.h>
void main(void)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// Start the child process.
if (!CreateProcess(L"C:\\WINDOWS\\system32\\cmd.exe", // module name.
NULL, // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
FALSE, // Set handle inheritance to FALSE.
0, // No creation flags.
NULL, // Use parents environment block.
NULL, // Use parents starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) // Pointer to PROCESS_INFORMATION structure.
)
printf("\nSorry! CreateProcess() failed.\n\n");
else
printf("\nWell, CreateProcess() looks OK.\n\n");
// Wait until child process exits (in milliseconds). If INFINITE, the functions time-out interval never elapses except with user or other intervention.
WaitForSingleObject(pi.hProcess, INFINITE);
printf("\n");
// Close process and thread handles.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
Then we change the path in the first program to:
WCHAR szCmdline[]=L"\\\\?\\C:\\amad\\ChildProcess\\Debug\\ChildProcess.exe";
And rerun the first program with the previous text file as an argument. The following is the sample output.
C:\amad\buffalo\Debug>buffalo justatextfile.txt
Start of parent execution.
Parent process ID 1144
Parent thread ID 4856
CreatePipe() - pipe for child process's STDOUT pipe was created!
SetHandleInformation() - pipe STDOUT read handle is not inherited!
CreatePipe() - pipe for child process's STDIN was created!
Stdin SetHandleInformation() - pipe STDIN read handle is not inherited!
Verify: argv[1] = justatextfile.txt
Creating the child process...
Child process ID is: 1144
Child thread ID is: 4856
piProcInfo.hProcess handle was closed!
piProcInfo.hThread handle was closed!
CreateFile() - justatextfile.txt was successfully opened!
ReadFile() - Reading from a file...
WriteFile() - writing to the pipe for the child's STDIN...
ReadFile() - Failed to read file! Error 0
Closing the pipe handle...
Contents of justatextfile.txt written to child STDIN pipe.
Contents of justatextfile.txt child process STDOUT:
ReadFile() from child's standard output is OK!
Microsoft Windows XP [Version 5.1.2600]
WriteFile() to parent's standard output is OK!
ReadFile() from child's standard output is OK!
(C) Copyright 1985-2001 Microsoft Corp.
C:\amad\buffalo\Debug>
WriteFile() to parent's standard output is OK!
ReadFile() from child's standard output is OK!
Well, CreateProcess() looks OK.
WriteFile() to parent's standard output is OK!
ReadFile() from child's standard output failed! Error 109
End of parent execution.
C:\amad\buffalo\Debug>
Error 109 (ERROR_BROKEN_PIPE) means the pipe has been ended which is normal in this case.