Wow! What a great program... However, there is one
feature that would make it a gem of the internet:
being able to do multiple rights and reads without
closing down the input stream.
Ex:
exec_stream_t prog("some_cmd", "");
while(true)
{
prog.in() << data_in;
prog.out() >> data_out;
}
as opposed to
while(true)
{
prog.in() << data_in;
prog.close_in();
prog.out() >> data_out;
break;
}
I know PStreams does this for unix implementations but
not for windows.
Luckily, Microsoft has a complete demonstration of how
this would be done, however their implementation
requires the use of their handles. It would be
awesome if their implementation was wrapped up using
the stdlib.
Here is M$ implementation (I've tested this and
confirmed that it does indead work).
I would implement it myself, but I'm not clever enough
(yet).
Thanks for making a wonderful program!
~Zach
//////////////////////////////////////////
//////////////////////////////////////////
/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
Redirect.c
Description:
This sample illustrates how to spawn a child
console based
application with redirected standard handles.
The following import libraries are required:
user32.lib
Dave McPherson (davemm) 11-March-98
--*/
#include<windows.h>
#pragma comment(lib, "User32.lib")
void DisplayError(char *pszAPI);
void ReadAndHandleOutput(HANDLE hPipeRead);
void PrepAndLaunchRedirectedChild(HANDLE
hChildStdOut,
HANDLE
hChildStdIn,
HANDLE
hChildStdErr);
DWORD WINAPI GetAndSendInputThread(LPVOID
lpvThreadParam);
HANDLE hChildProcess = NULL;
HANDLE hStdIn = NULL; // Handle to parents std
input.
BOOL bRunThread = TRUE;
void main ()
{
HANDLE hOutputReadTmp,hOutputRead,hOutputWrite;
HANDLE hInputWriteTmp,hInputRead,hInputWrite;
HANDLE hErrorWrite;
HANDLE hThread;
DWORD ThreadId;
SECURITY_ATTRIBUTES sa;
// Set up the security attributes struct.
sa.nLength= sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
// Create the child output pipe.
if (!CreatePipe
(&hOutputReadTmp,&hOutputWrite,&sa,0))
DisplayError("CreatePipe");
// Create a duplicate of the output write handle
for the std error
// write handle. This is necessary in case the
child application
// closes one of its std output handles.
if (!DuplicateHandle(GetCurrentProcess
(),hOutputWrite,
GetCurrentProcess
(),&hErrorWrite,0,
TRUE,DUPLICATE_SAME_ACCESS))
DisplayError("DuplicateHandle");
// Create the child input pipe.
if (!CreatePipe
(&hInputRead,&hInputWriteTmp,&sa,0))
DisplayError("CreatePipe");
// Create new output read handle and the input
write handles. Set
// the Properties to FALSE. Otherwise, the child
inherits the
// properties and, as a result, non-closeable
handles to the pipes
// are created.
if (!DuplicateHandle(GetCurrentProcess
(),hOutputReadTmp,
GetCurrentProcess(),
&hOutputRead, // Address of
new handle.
0,FALSE, // Make it
uninheritable.
DUPLICATE_SAME_ACCESS))
DisplayError("DupliateHandle");
if (!DuplicateHandle(GetCurrentProcess
(),hInputWriteTmp,
GetCurrentProcess(),
&hInputWrite, // Address of
new handle.
0,FALSE, // Make it
uninheritable.
DUPLICATE_SAME_ACCESS))
DisplayError("DupliateHandle");
// Close inheritable copies of the handles you
do not want to be
// inherited.
if (!CloseHandle(hOutputReadTmp)) DisplayError
("CloseHandle");
if (!CloseHandle(hInputWriteTmp)) DisplayError
("CloseHandle");
// Get std input handle so you can close it and
force the ReadFile to
// fail when you want the input thread to exit.
if ( (hStdIn = GetStdHandle(STD_INPUT_HANDLE)) ==
INVALID_HANDLE_VALUE )
DisplayError("GetStdHandle");
PrepAndLaunchRedirectedChild
(hOutputWrite,hInputRead,hErrorWrite);
// Close pipe handles (do not continue to modify
the parent).
// You need to make sure that no handles to the
write end of the
// output pipe are maintained in this process or
else the pipe will
// not close when the child process exits and
the ReadFile will hang.
if (!CloseHandle(hOutputWrite)) DisplayError
("CloseHandle");
if (!CloseHandle(hInputRead )) DisplayError
("CloseHandle");
if (!CloseHandle(hErrorWrite)) DisplayError
("CloseHandle");
// Launch the thread that gets the input and
sends it to the child.
hThread = CreateThread
(NULL,0,GetAndSendInputThread,
(LPVOID)
hInputWrite,0,&ThreadId);
if (hThread == NULL) DisplayError
("CreateThread");
// Read the child's output.
ReadAndHandleOutput(hOutputRead);
// Redirection is complete
// Force the read on the input to return by
closing the stdin handle.
if (!CloseHandle(hStdIn)) DisplayError
("CloseHandle");
// Tell the thread to exit and wait for thread
to die.
bRunThread = FALSE;
if (WaitForSingleObject(hThread,INFINITE) ==
WAIT_FAILED)
DisplayError("WaitForSingleObject");
if (!CloseHandle(hOutputRead)) DisplayError
("CloseHandle");
if (!CloseHandle(hInputWrite)) DisplayError
("CloseHandle");
}
////////////////////////////////////////////////////
///////////////////
// PrepAndLaunchRedirectedChild
// Sets up STARTUPINFO structure, and launches
redirected child.
////////////////////////////////////////////////////
///////////////////
void PrepAndLaunchRedirectedChild(HANDLE
hChildStdOut,
HANDLE
hChildStdIn,
HANDLE
hChildStdErr)
{
PROCESS_INFORMATION pi;
STARTUPINFO si;
// Set up the start up info struct.
ZeroMemory(&si,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdOutput = hChildStdOut;
si.hStdInput = hChildStdIn;
si.hStdError = hChildStdErr;
// Use this if you want to hide the child:
// si.wShowWindow = SW_HIDE;
// Note that dwFlags must include
STARTF_USESHOWWINDOW if you want to
// use the wShowWindow flags.
// Launch the process that you want to redirect
(in this case,
// Child.exe). Make sure Child.exe is in the
same directory as
// redirect.c launch redirect from a command
line to prevent location
// confusion.
if (!CreateProcess
(NULL,"Child.EXE",NULL,NULL,TRUE,
CREATE_NEW_CONSOLE,NULL,NULL,&si,&pi))
DisplayError("CreateProcess");
// Set global child process handle to cause
threads to exit.
hChildProcess = pi.hProcess;
// Close any unnecessary handles.
if (!CloseHandle(pi.hThread)) DisplayError
("CloseHandle");
}
////////////////////////////////////////////////////
///////////////////
// ReadAndHandleOutput
// Monitors handle for input. Exits when child
exits or pipe breaks.
////////////////////////////////////////////////////
///////////////////
void ReadAndHandleOutput(HANDLE hPipeRead)
{
CHAR lpBuffer[256];
DWORD nBytesRead;
DWORD nCharsWritten;
while(TRUE)
{
if (!ReadFile(hPipeRead,lpBuffer,sizeof
(lpBuffer),
&nBytesRead,NULL) || !nBytesRead)
{
if (GetLastError() == ERROR_BROKEN_PIPE)
break; // pipe done - normal exit path.
else
DisplayError("ReadFile"); // Something
bad happened.
}
// Display the character read on the screen.
if (!WriteConsole(GetStdHandle
(STD_OUTPUT_HANDLE),lpBuffer,
nBytesRead,&nCharsWritten,NULL))
DisplayError("WriteConsole");
}
}
////////////////////////////////////////////////////
///////////////////
// GetAndSendInputThread
// Thread procedure that monitors the console for
input and sends input
// to the child process through the input pipe.
// This thread ends when the child application
exits.
////////////////////////////////////////////////////
///////////////////
DWORD WINAPI GetAndSendInputThread(LPVOID
lpvThreadParam)
{
CHAR read_buff[256];
DWORD nBytesRead,nBytesWrote;
HANDLE hPipeWrite = (HANDLE)lpvThreadParam;
// Get input from our console and send it to
child through the pipe.
while (bRunThread)
{
if(!ReadConsole
(hStdIn,read_buff,1,&nBytesRead,NULL))
DisplayError("ReadConsole");
read_buff[nBytesRead] = '\0'; // Follow input
with a NULL.
if (!WriteFile
(hPipeWrite,read_buff,nBytesRead,&nBytesWrote,NULL))
{
if (GetLastError() == ERROR_NO_DATA)
break; // Pipe was closed (normal exit
path).
else
DisplayError("WriteFile");
}
}
return 1;
}
////////////////////////////////////////////////////
///////////////////
// DisplayError
// Displays the error number and corresponding
message.
////////////////////////////////////////////////////
///////////////////
void DisplayError(char *pszAPI)
{
LPVOID lpvMessageBuffer;
CHAR szPrintBuffer[512];
DWORD nCharsWritten;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYST
EM,
NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL,
SUBLANG_DEFAULT),
(LPTSTR)&lpvMessageBuffer, 0, NULL);
wsprintf(szPrintBuffer,
"ERROR: API = %s.\n error code = %d.\n
message = %s.\n",
pszAPI, GetLastError(), (char *)
lpvMessageBuffer);
WriteConsole(GetStdHandle
(STD_OUTPUT_HANDLE),szPrintBuffer,
lstrlen
(szPrintBuffer),&nCharsWritten,NULL);
LocalFree(lpvMessageBuffer);
ExitProcess(GetLastError());
}
////////////////////////////////////////////////////
//////////////////
// child.c
// Echoes all input to stdout. This will be
redirected by the redirect
// sample. Compile and build child.c as a Win32
Console application and
// put it in the same directory as the redirect
sample.
//
#include<windows.h>
#include<stdio.h>
#include<string.h>
void main ()
{
FILE* fp;
CHAR szInput[1024];
// Open the console. By doing this, you can send
output directly to
// the console that will not be redirected.
fp = fopen("CON", "w");
if (!fp) {
printf("Error opening child console - perhaps
there is none.\n");
fflush(NULL);
}
else
{
// Write a message direct to the console (will
not be redirected).
fprintf(fp,"This data is being printed
directly to the\n");
fprintf(fp,"console and will not be
redirected.\n\n");
fprintf(fp,"Since the standard input and
output have been\n");
fprintf(fp,"redirected data sent to and from
those handles\n");
fprintf(fp,"will be redirected.\n\n");
fprintf(fp,"To send data to the std input of
this process.\n");
fprintf(fp,"Click on the console window of
the parent process\n");
fprintf(fp,"(redirect), and enter data from
it's console\n\n");
fprintf(fp,"To exit this process send the
string 'exit' to\n");
fprintf(fp,"it's standard input\n");
fflush(fp);
}
ZeroMemory(szInput,1024);
while (TRUE)
{
gets(szInput);
printf("Child echoing [%s]\n",szInput);
fflush(NULL); // Must flush output buffers
or else redirection
// will be problematic.
if (!_stricmp(szInput,"Exit") )
break;
ZeroMemory(szInput,strlen(szInput) );
}
}