I have a standard Window program that starts a console "child" process. The
child process sends various pipe messages to the Window program.
I now want to catch any output from the console program's STDOUT and STDERR
and send it to the Window program. I am using the following code:
void start_run ()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
DWORD bytes_written;
if (!WriteFile (hChildStdoutWr, "Testing\0", 9, &bytes_written, NULL))
msg ("writefile failed with errno %d", GetLastError ());
memset (&si, 0, sizeof (si));
si.cb = sizeof (si);
si.wShowWindow = SW_MINIMIZE;
si.hStdError = hChildStdoutWr;
si.hStdOutput = hChildStdoutWr;
si.hStdInput = INVALID_HANDLE_VALUE;
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
/************ start run *****************/
if (!CreateProcess (
NULL, // Application Name
run_command, // Command Line
NULL, // Process Attributes
NULL, // Thread Attributes
TRUE, // Inherit Handles
0, //CREATE_NO_WINDOW, // Creation Flags
NULL, // Environment
NULL, // Current Directory
&si, // Startup Information
&pi // Process Information
))
msg ("Create Process failed with error %d: %s", GetLastError (),
run_command);
run_handle = pi.hProcess;
CloseHandle (pi.hThread);
Sleep (1000);
CloseHandle (hChildStdoutWr);
} /* start_run */
*************************
The first "WriteFile" works correctly. The "Testing" message gets correctly
sent. The redirection of the new process STDOUT and STDERR doesn't. When I
CloseHandle (hChildStdoutWr) at the end, the pipe is completely closed (the
child program doesn't have it open).
I have used both unnamed pipes and named pipes and get the same result.
Output from the child program doesn't make it to the Window program.
Any suggestions??
Thanks,
Jeff
The CreatePipe() approach should work, make sure the
SECURITY_ATTRIBUTES argument has bInheritHandle = TRUE.
If it still doesn't work, check your code against this MS
Knowledge Base example:
http://support.microsoft.com/kb/190351
If it's still not working, try posting a complete, compilable
example that is a page or so, keeping it as simple as possible.
(ie. complete with #include's and main/WinMain).
Unrelated: not sure if setting si.hStdInput to INVALID_HANDLE_VALUE
is a good idea or not.. maybe attach it to an open handle to "nul"
so that it's at least a valid handle.
DWORD dwExecuteWithRedir(char *p_szCmd, char **pp_szResult,
DWORD dwTimeout, char *p_szStdInData)
{ // returns process exit code (= errorlevel)
STARTUPINFO sui;
PROCESS_INFORMATION pi;
char *p_sz;
DWORD dw, dwResult;
SECURITY_ATTRIBUTES sa;
HANDLE hStdChild[2], // childs StdIn/StdOut
hStdHost[2], // hosts Readside/WriteSide
hTmp; // temporary handle
LONG lTmp;
if (pp_szResult!=NULL) *pp_szResult = NULL;
// initialize SUI
memset(&sui, 0, sizeof(STARTUPINFO)); sui.cb = sizeof(STARTUPINFO);
lTmp=InterlockedCompareExchange(&_lActiveExecution s, 0, 100000L);
if (lTmp>40) Sleep(300*lTmp); else if (lTmp>20) Sleep(5000); else if
(lTmp>10) Sleep(2000);
InterlockedIncrement(&_lActiveExecutions);
// Create NULL security attribute
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL;
// Create pipe for Us <- child process, childs's StdOut
CreatePipe(&hTmp, &hStdChild[1], &sa, 0);
DuplicateHandle(GetCurrentProcess(), hTmp,
GetCurrentProcess(), &hStdHost[0], 0,
FALSE, // not inherited
DUPLICATE_SAME_ACCESS);
CloseHandle(hTmp);
// create pipe for childs StdIn
CreatePipe(&hStdChild[0], &hTmp, &sa, 0);
DuplicateHandle(GetCurrentProcess(), hTmp,
GetCurrentProcess(), &hStdHost[1], 0,
FALSE, // not inherited
DUPLICATE_SAME_ACCESS);
CloseHandle(hTmp);
sui.wShowWindow = SW_HIDE;
sui.hStdOutput=hStdChild[1]; sui.hStdInput=hStdChild[0];
sui.hStdError=hStdChild[1];
sui.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWIN DOW;
// Create the process
if (CreateProcess(NULL, p_szCmd, NULL, NULL, TRUE,
NORMAL_PRIORITY_CLASS, NULL, NULL, &sui, &pi) == 0)
{
InterlockedDecrement(&_lActiveExecutions);
dwResult = GetLastError();
CloseHandle(hStdHost[0]); CloseHandle(hStdHost[1]);
CloseHandle(hStdChild[0]); CloseHandle(hStdChild[1]);
p_sz=malloc(10000);
dw=sprintf(p_sz, "Fault trying SrvCmd(%s)! ErrCode:%lu. User:%s.",
p_szCmd, dwResult, p_opi->p_user->szAccount30);
p_sz=realloc(p_sz, dw+1);
if (pp_szResult !=NULL) *pp_szResult = p_sz;
return OP_EXEC_ERROR;
}
CloseHandle(pi.hThread);
if (p_szStdInData!=NULL)
{ // Provide some simulated keyboard input to the application
dw=strlen(p_szStdInData);
WriteFile(hStdHost[1], p_szStdInData, dw, &dw, NULL);
}
// Wait for completion with timeout
WaitForSingleObject(pi.hProcess, dwTimeout);
GetExitCodeProcess(pi.hProcess, &dwResult);
InterlockedDecrement(&_lActiveExecutions);
if (dwResult==STILL_ACTIVE)
{
TerminateProcess(pi.hProcess, 4711);
CloseHandle(hStdHost[0]); CloseHandle(hStdHost[1]);
CloseHandle(hStdChild[0]); CloseHandle(hStdChild[1]);
CloseHandle(pi.hProcess);
if (pp_szResult!=NULL)
{
p_sz=malloc(200); sprintf(p_sz, "%s timeout!", p_szCmd);
*pp_szResult = p_sz;
}
return OP_EXEC_ERROR;
}
CloseHandle(hStdHost[1]); CloseHandle(hStdChild[0]);
CloseHandle(hStdChild[1]);
// Read the output if we want it
if (pp_szResult!=NULL)
{
p_sz=malloc(5001);
if (hStdHost[0]==INVALID_HANDLE_VALUE || !ReadFile(hStdHost[0], p_sz,
5000, &dw, NULL))
{
dw=sprintf(p_sz, "NoData from %s!", p_szCmd);
p_sz=realloc(p_sz, dw+1);
CloseHandle(hStdHost[0]); CloseHandle(pi.hProcess);
*pp_szResult = p_sz; return dwResult;
}
p_sz=realloc(p_sz, dw+10); p_sz[dw]=0;
*pp_szResult = p_sz;
}
CloseHandle(hStdHost[0]); CloseHandle(pi.hProcess);
return dwResult;
}
That would have to do with C library buffering.
You can likely replicate the problem by doing a simple
test opening two DOS windows, running the following in
one window:
test.exe > foo.out
..and in the other:
type foo.out
..you likely won't see any data in foo.out until the
test program finishes running, due to stdout buffering.
Try instead:
----
for (i=0; i<30; i++)
{
printf("Testing %d\n", i);
fflush(stdout); // ADD THIS
Sleep(1000);
}
----
..and that should solve it.
Meanwhile, I'll take a look at your zip file in a bit,
time permitting.
...snip...
>
> Try instead:
>
> ----
> for (i=0; i<30; i++)
> {
> printf("Testing %d\n", i);
> fflush(stdout); // ADD THIS
> Sleep(1000);
> }
> ----
>
> ..and that should solve it.
>
This was the problem. Thanks for your help.
Jeff.
Yes, that must be the problem, because the original program
seems to work correctly.
> I'm not really sure how to get a Window program down to a page or so.
Just as an exercise, I rewrote your app in a plain text editor,
stripping out all the VS cruft, ending up with a single .cpp file
that compiles under VS Express with this command line from DOS:
cl /TP wintest.C \
/link /subsystem:windows \
wsock32.lib comctl32.lib kernel32.lib user32.lib gdi32.lib winspool.lib \
comdlg32.lib advapi32.lib shell32.lib winmm.lib ole32.lib oleaut32.lib \
uuid.lib imm32.lib /nologo /machine:I386
Here's the trimmed code, for your entertainment.
Not a page, but a 'page or so'.. a single file at least ;)
* * *
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#define OUTBUFSIZE sizeof(output_buffer)
// Global Variables:
HWND mainwin;
HANDLE hChildStdoutWr, hChildStdoutRd;
HANDLE hChildStdinWr, hChildStdinRd;
char output_buffer[4096] = "";
// POST A FAILURE MESSAGE AND EXIT
void Fail(char *fmt, ...)
{
va_list ap;
TCHAR s[120];
va_start(ap, fmt);
vsprintf_s(s, 120, fmt, ap);
MessageBox(mainwin, s, NULL, MB_OK | MB_ICONEXCLAMATION);
va_end(ap);
exit(1);
}
// PRINT STRING TO WIN32 SCREEN
void Print(const char *s)
{
strncat(output_buffer, s, OUTBUFSIZE);
output_buffer[OUTBUFSIZE-1] = 0; // truncate
InvalidateRect(mainwin, NULL, FALSE);
}
// CHILD THREAD: READ DATA FROM CHILD AND PRINT
DWORD WINAPI handle_stdout_pipe(void *thread_param)
{
DWORD bytes_read;
char read_buffer[81];
char buf2[90];
while (TRUE)
{
// Read data from child
if (!ReadFile(hChildStdoutRd, read_buffer, 80, &bytes_read, NULL))
Fail("stdout read returned error %d", GetLastError());
read_buffer[bytes_read] = 0;
// Print
sprintf_s(buf2, 90, "(%2d):%s", bytes_read, read_buffer);
Print(buf2);
}
}
// CREATE PIPES AND START CHILD PROCESS
void create_pipe_and_child()
{
SECURITY_ATTRIBUTES saAttr;
memset(&saAttr, 0, sizeof(saAttr));
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if ( ! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
Fail("Stdout pipe creation failed");
if ( ! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
Fail("Stdin pipe creation failed");
// Ensure local handles not inherited to child
SetHandleInformation(hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);
SetHandleInformation(hChildStdinWr, HANDLE_FLAG_INHERIT, 0);
// Start a processing thread for receiving stdout messages
if ( CreateThread(NULL, // default security attributes
0, // use default stack size
handle_stdout_pipe, // thread function
NULL, // argument to thread function
0, // use default creation flags
NULL // the thread identifier
) == NULL)
Fail("winrun: CreateThread(stdout) failed with error %d", GetLastError());
// START CHILD PROCESS WITH HANDLES REDIRECTED
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
// Write a test message to test the pipe, before starting child
{
DWORD bytes_written;
char *testmsg = "Parent Test\n";
if ( ! WriteFile(hChildStdoutWr, testmsg, strlen(testmsg), &bytes_written, NULL))
Fail("writefile failed with errno %d", GetLastError ());
}
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.wShowWindow = SW_MINIMIZE;
si.hStdError = hChildStdoutWr;
si.hStdOutput = hChildStdoutWr;
si.hStdInput = hChildStdinRd;
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
if (!CreateProcess(NULL, // Application Name
"test.exe", // Command
NULL, // Process Attributes
NULL, // Thread Attributes
TRUE, // Inherit Handles
0, //CREATE_NO_WINDOW, // Creation Flags
NULL, // Environment
NULL, // Current Directory
&si, // Startup Information
&pi // Process Information
))
Fail("Create Process failed with error %d", GetLastError());
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hChildStdoutWr);
CloseHandle(hChildStdinRd);
}
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
RECT rt;
switch(message)
{
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
GetClientRect(hWnd, &rt);
DrawText(hdc, output_buffer, -1, &rt, DT_WORDBREAK|DT_NOCLIP);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return(DefWindowProc(hWnd, message, wParam, lParam));
}
return 0;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int iCmdShow)
{
MSG msg;
WNDCLASSEX wndclass;
memset(&wndclass, 0, sizeof(wndclass));
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.hInstance = hInstance;
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszClassName = "test";
wndclass.lpszMenuName = NULL;
RegisterClassEx(&wndclass);
mainwin = CreateWindow("test", "Test",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
// Start pipes and child process
create_pipe_and_child();
ShowWindow(mainwin, iCmdShow);
UpdateWindow(mainwin);
// Main loop
while ( GetMessage(&msg, NULL, 0, 0) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return((int)msg.wParam);
}
Thanks a bunch for all of the help!
Jeff.