From: IlyaK on
Hello:

Strange hang problem with ReadFile.
Here is the algorithm:

The idea is to launch a python script from one Win32 GUI application,
then read its std output and store it in a file. Do this until the
script is done.
1. I create a child process using CreateProcess with the code pretty
much copied from http://support.microsoft.com/default.aspx?scid=kb;en-us;190351.
However, instead of creating some special child process I just launch
a python script.
2. Then start reading from the hChildStdOut using ReadFile.
3. The problem is that ReadFile at the end hangs indefinitely waiting
for more data from the child process.

Is there any way to know if the child process is done (or dead) and
stop reading. I tried several methods to do that, however, they
weren't reliable so far.
1. Check if the ReadFile lpNumberOfBytesRead is smaller than
nNumberOfBytesToRead (http://msdn.microsoft.com/en-us/library/
aa365467%28VS.85%29.aspx). This didn't work because at the beginning
sometimes the Python script returns lpNumberOfBytesRead <
nNumberOfBytesToRead. It does some init and it takes time, I suppose.
2. Use GetExitCodeProcess (http://msdn.microsoft.com/en-us/library/
ms683189%28VS.85%29.aspx) on the child process handle. This method
didn't work either. For some reason the GetExitCodeProcess would
indicate that the process is still active.

Please help.
-Ilya.
From: Hector Santos on
You should show code because that first link with redirection logic
should work for you. However, there are ways do this without using pipes.

This is pretty standard practice coding stuff. So show code to see
where you went wrong.

--

IlyaK wrote:

> Hello:
>
> Strange hang problem with ReadFile.
> Here is the algorithm:
>
> The idea is to launch a python script from one Win32 GUI application,
> then read its std output and store it in a file. Do this until the
> script is done.
> 1. I create a child process using CreateProcess with the code pretty
> much copied from http://support.microsoft.com/default.aspx?scid=kb;en-us;190351.
> However, instead of creating some special child process I just launch
> a python script.
> 2. Then start reading from the hChildStdOut using ReadFile.
> 3. The problem is that ReadFile at the end hangs indefinitely waiting
> for more data from the child process.
>
> Is there any way to know if the child process is done (or dead) and
> stop reading. I tried several methods to do that, however, they
> weren't reliable so far.
> 1. Check if the ReadFile lpNumberOfBytesRead is smaller than
> nNumberOfBytesToRead (http://msdn.microsoft.com/en-us/library/
> aa365467%28VS.85%29.aspx). This didn't work because at the beginning
> sometimes the Python script returns lpNumberOfBytesRead <
> nNumberOfBytesToRead. It does some init and it takes time, I suppose.
> 2. Use GetExitCodeProcess (http://msdn.microsoft.com/en-us/library/
> ms683189%28VS.85%29.aspx) on the child process handle. This method
> didn't work either. For some reason the GetExitCodeProcess would
> indicate that the process is still active.
>
> Please help.
> -Ilya.



--
HLS
From: IlyaK on
Fair enough.
Here is the code:

{
// start the child process
HANDLE hChildStdOut;
HANDLE hChildStdIn;
HANDLE hChildStdErr;
HANDLE hChildProc;
if (!launch_redirected_child(m_testCmdLine.c_str(), &hChildStdOut,
&hChildStdIn, &hChildStdErr, &hChildProc)){
printf("Error creating child\n");
return -1;
}


// Wait for the script to finish its busyness,
// getting the output and checking the cancel
// flag
const int iBufferSize = 256;
char * readBuffer = new char[iBufferSize];
memset(readBuffer, 0, iBufferSize);
int numRead = 0;
unsigned long lErrors = 0;
long isEof = 0;

printf("BEGIN Log:\n");
if (m_bDoScriptLog == true) {
while (get_cancel_test() != true) {
if ( (numRead = pipe_read( hChildStdOut, readBuffer,
iBufferSize )) > 0 ) {
printf("Log: %d\n", numRead);
printf("Log: %s\n", string(readBuffer,
numRead).c_str());

GetExitCodeProcess(hChildProc, &lErrors);
printf("Log: ", lErrors);

if (lErrors != STILL_ACTIVE && numRead < iBufferSize)
{
printf("Log: Exit");
break;
}
} else {
// Check if the pipe is broken, then exit,
// Otherwise report error, and aslo exit
printf("Exit on Error\n");
break;
}
//memset(readBuffer, 0, iBufferSize);
}
}
printf("END Log.\n");

delete [] readBuffer;

status = CloseHandle(hChildStdOut);
status = CloseHandle(hChildStdIn);
status = CloseHandle(hChildStdErr);
status = CloseHandle(hChildProc);
}

/**
* The function will set up STARTUPINFO structure, and launch
redirected child.
* It will return the handles to talk to this child process.
* Make sure that the handles are properly closed after use.
* Lifted from http://support.microsoft.com/default.aspx?scid=kb;en-us;190351
*
* @param command
* @param hChildStdOut
* @param hChildStdIn
* @param hChildStdErr
*
* @return 0 - error occured. Use GetLastError to figure out what
happened.
*/
int
launch_redirected_child(const char * command,
HANDLE * hChildStdOut,
HANDLE * hChildStdIn,
HANDLE * hChildStdErr,
HANDLE * hChildProcess)
{
HANDLE hOutputReadTmp,hOutputRead,hOutputWrite;
HANDLE hInputWriteTmp,hInputRead,hInputWrite;
SECURITY_ATTRIBUTES sa;
PROCESS_INFORMATION pi;
STARTUPINFO si;

// Set up the security attributes struct.
sa.nLength= sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;

// Create the child output pipe - child sends.
if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,0)) {
return 0;
}

// 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(),
hChildStdErr,
0,TRUE,
DUPLICATE_SAME_ACCESS)) {
return 0;
}

// Create the child input pipe - child receives.
if (!CreatePipe(&hInputRead,&hInputWriteTmp,&sa,0)) {
return 0;
}

// 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(),
hChildStdOut, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS)) {
return 0;
}

if (!DuplicateHandle(GetCurrentProcess(),hInputWriteTmp,
GetCurrentProcess(),
hChildStdIn, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS)) {
return 0;
}

// Close inheritable copies of the handles you do not want to be
// inherited.
if (!CloseHandle(hOutputReadTmp)) {
return 0;
}
if (!CloseHandle(hInputWriteTmp)) {
return 0;
}

// Set up the start up info struct.
ZeroMemory(&si,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdOutput = hOutputWrite;
si.hStdInput = hInputRead;
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,(LPCWSTR)(command),
NULL,NULL,TRUE,
0,NULL,NULL,&si,&pi)) {
return 0;
}

// Close any unnecessary handles.
if (!CloseHandle(pi.hThread)) {
return 0;
}
if (hChildProcess != NULL) {
*hChildProcess = pi.hProcess;
} else {
if (!CloseHandle(pi.hProcess)) {
return 0;
}
}
if (!CloseHandle(hOutputWrite)) {
return 0;
}
if (!CloseHandle(hInputRead)) {
return 0;
}
return 1;
}

int
pipe_read (HANDLE fd, void *buffer, size_t count) {
DWORD numRead;
if (!ReadFile (fd, buffer, (DWORD)count, &numRead, NULL))
{ //non overlapped
return -1;
}
return (int) numRead;
}

On Mar 2, 12:40 pm, Hector Santos <sant9...(a)nospam.gmail.com> wrote:
> You should show code because that first link with redirection logic
> should work for you.  However, there are ways do this without using pipes.
>
> This is pretty standard practice coding stuff.  So show code to see
> where you went wrong.
>
> --
>
>
>
> IlyaK wrote:
> > Hello:
>
> > Strange hang problem with ReadFile.
> > Here is the algorithm:
>
> > The idea is to launch a python script from one Win32 GUI application,
> > then read its std output and store it in a file. Do this until the
> > script is done.
> > 1. I create a child process using CreateProcess with the code pretty
> > much copied fromhttp://support.microsoft.com/default.aspx?scid=kb;en-us;190351.
> > However, instead of creating some special child process I just launch
> > a python script.
> > 2. Then start reading from the hChildStdOut using ReadFile.
> > 3. The problem is that ReadFile at the end hangs indefinitely waiting
> > for more data from the child process.
>
> > Is there any way to know if the child process is done (or dead) and
> > stop reading. I tried several methods to do that, however, they
> > weren't reliable so far.
> > 1. Check if the ReadFile lpNumberOfBytesRead is smaller than
> > nNumberOfBytesToRead (http://msdn.microsoft.com/en-us/library/
> > aa365467%28VS.85%29.aspx). This didn't work because at the beginning
> > sometimes the Python script returns lpNumberOfBytesRead <
> > nNumberOfBytesToRead. It does some init and it takes time, I suppose.
> > 2. Use GetExitCodeProcess (http://msdn.microsoft.com/en-us/library/
> > ms683189%28VS.85%29.aspx) on the child process handle. This method
> > didn't work either. For some reason the GetExitCodeProcess would
> > indicate that the process is still active.
>
> > Please help.
> > -Ilya.
>
> --
> HLS

From: Hector Santos on
You will probably get much input here, but it seems like you are
prematurely closing the child process handle. You didn't show whats in
the get_cancel_text() but that should be a kernel wait w/ timeout as
you detect some cancel idea. You might be blocking here.

If you don't want interactive I/O and just want to capture output, you
really don't need piping. But it will work the same. The loop should
be something like this example console program:

------------- CUT HERE ------------
// File: d:\wc5beta\testspawn4.cpp
// Author: Hector Santos, Santronics Software, Inc.
// Compile: cl testspawn4.cpp /MT

#include <stdio.h>
#include <afx.h>

/******************************************************************
* SendToParent() is called by the redirection logic to that you
* send the data as a message event to your parent window. In this
* simple example we are just displaying it to the screen.
******************************************************************/

void SendToParent(const char *szBuf, const DWORD size)
{
printf("%s",szBuf);
}

/******************************************************************
* BOOL Run(UseDos, szCmd)
*
* Spawn a child process and capture the redirect output.
*
* UseDos = Set TRUE to use OS command interpreter
* cmd = command to run (if DOS command, set UseDos=TRUE)
*
* return TRUE if successful. Otherwise FALSE, read extended error.
******************************************************************/

BOOL Run(const char *szCmd, const BOOL UseDos = FALSE)
{

SECURITY_ATTRIBUTES sa;
ZeroMemory(&sa, sizeof(sa));
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;

HANDLE hOut = INVALID_HANDLE_VALUE;
HANDLE hRedir = INVALID_HANDLE_VALUE;

// Create Temp File for redirection

char szTempPath[MAX_PATH];
char szOutput[MAX_PATH];
GetTempPath(sizeof(szTempPath),szTempPath);
GetTempFileName(szTempPath, "tmp", 0, szOutput);

// setup redirection handles
// output handle must be WRITE mode, share READ
// redirect handle must be READ mode, share WRITE

hOut = CreateFile(szOutput,
GENERIC_WRITE,
FILE_SHARE_READ,
&sa,
CREATE_ALWAYS,
FILE_ATTRIBUTE_TEMPORARY,
0);

if (hOut == INVALID_HANDLE_VALUE) {
return FALSE;
}

hRedir = CreateFile(szOutput,
GENERIC_READ,
FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
0,
0);

if (hRedir == INVALID_HANDLE_VALUE) {
CloseHandle(hOut);
return FALSE;
}

// setup startup info, set std out/err handles
// hide window

STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
if (hOut != INVALID_HANDLE_VALUE) {
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = hOut;
si.hStdError = hOut;
si.wShowWindow = SW_HIDE;
}

// use the current OS comspec for DOS commands, i.e., DIR

char cmd[MAX_PATH] = {0};
if (UseDos) {
strcpy(cmd,getenv("comspec"));
strcat(cmd," /c ");
}
strcat(cmd,szCmd);
printf("-Process: %s\n",cmd);

PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));

DWORD dwFlags = CREATE_NO_WINDOW; // NT/2000 only

if (!CreateProcess(NULL, (char *)cmd,
NULL, NULL, TRUE,
dwFlags,
NULL, NULL, &si, &pi)) {

int err = GetLastError(); // preserve the CreateProcess error

if (hOut != INVALID_HANDLE_VALUE) {
CloseHandle(hOut);
CloseHandle(hRedir);
}

SetLastError(err);
return FALSE;
}

// let go of the child thread
printf("-Waiting\n");

CloseHandle(pi.hThread);

// wait for process to finish, capture redirection and
// send it to the parent window/console

DWORD dw;
char buf[256];
do {
ZeroMemory(&buf,sizeof(buf));
while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL)) {
if (dw == 0) break;
SendToParent(buf,dw);
ZeroMemory(&buf,sizeof(buf));
}
} while (WaitForSingleObject(pi.hProcess, 0) != WAIT_OBJECT_0);

// perform any final flushing

while (ReadFile(hRedir, &buf, sizeof(buf)-1, &dw, NULL)) {
if (dw == 0) break;
SendToParent(buf,dw);
ZeroMemory(&buf,sizeof(buf));
}

WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(hOut);
CloseHandle(hRedir);
DeleteFile(szOutput);

printf("-finished\n");

return TRUE;
}


void main(char argc, char *argv[])
{

//------------------------------------------
// example #2 - using direct
//------------------------------------------

if (!Run("dir *.cpp", TRUE)) {
printf("Error %d\n",GetLastError());
return;
}
printf("-done\n");
}
---------------- CUT HERE ----------------

You can add a sanity/cancel check in the loop. Just make sure its not
blocking.

IlyaK wrote:

> Fair enough.
> Here is the code:
>
> {
> // start the child process
> HANDLE hChildStdOut;
> HANDLE hChildStdIn;
> HANDLE hChildStdErr;
> HANDLE hChildProc;
> if (!launch_redirected_child(m_testCmdLine.c_str(), &hChildStdOut,
> &hChildStdIn, &hChildStdErr, &hChildProc)){
> printf("Error creating child\n");
> return -1;
> }
>
>
> // Wait for the script to finish its busyness,
> // getting the output and checking the cancel
> // flag
> const int iBufferSize = 256;
> char * readBuffer = new char[iBufferSize];
> memset(readBuffer, 0, iBufferSize);
> int numRead = 0;
> unsigned long lErrors = 0;
> long isEof = 0;
>
> printf("BEGIN Log:\n");
> if (m_bDoScriptLog == true) {
> while (get_cancel_test() != true) {
> if ( (numRead = pipe_read( hChildStdOut, readBuffer,
> iBufferSize )) > 0 ) {
> printf("Log: %d\n", numRead);
> printf("Log: %s\n", string(readBuffer,
> numRead).c_str());
>
> GetExitCodeProcess(hChildProc, &lErrors);
> printf("Log: ", lErrors);
>
> if (lErrors != STILL_ACTIVE && numRead < iBufferSize)
> {
> printf("Log: Exit");
> break;
> }
> } else {
> // Check if the pipe is broken, then exit,
> // Otherwise report error, and aslo exit
> printf("Exit on Error\n");
> break;
> }
> //memset(readBuffer, 0, iBufferSize);
> }
> }
> printf("END Log.\n");
>
> delete [] readBuffer;
>
> status = CloseHandle(hChildStdOut);
> status = CloseHandle(hChildStdIn);
> status = CloseHandle(hChildStdErr);
> status = CloseHandle(hChildProc);
> }
>
> /**
> * The function will set up STARTUPINFO structure, and launch
> redirected child.
> * It will return the handles to talk to this child process.
> * Make sure that the handles are properly closed after use.
> * Lifted from http://support.microsoft.com/default.aspx?scid=kb;en-us;190351
> *
> * @param command
> * @param hChildStdOut
> * @param hChildStdIn
> * @param hChildStdErr
> *
> * @return 0 - error occured. Use GetLastError to figure out what
> happened.
> */
> int
> launch_redirected_child(const char * command,
> HANDLE * hChildStdOut,
> HANDLE * hChildStdIn,
> HANDLE * hChildStdErr,
> HANDLE * hChildProcess)
> {
> HANDLE hOutputReadTmp,hOutputRead,hOutputWrite;
> HANDLE hInputWriteTmp,hInputRead,hInputWrite;
> SECURITY_ATTRIBUTES sa;
> PROCESS_INFORMATION pi;
> STARTUPINFO si;
>
> // Set up the security attributes struct.
> sa.nLength= sizeof(SECURITY_ATTRIBUTES);
> sa.lpSecurityDescriptor = NULL;
> sa.bInheritHandle = TRUE;
>
> // Create the child output pipe - child sends.
> if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,0)) {
> return 0;
> }
>
> // 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(),
> hChildStdErr,
> 0,TRUE,
> DUPLICATE_SAME_ACCESS)) {
> return 0;
> }
>
> // Create the child input pipe - child receives.
> if (!CreatePipe(&hInputRead,&hInputWriteTmp,&sa,0)) {
> return 0;
> }
>
> // 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(),
> hChildStdOut, // Address of new handle.
> 0,FALSE, // Make it uninheritable.
> DUPLICATE_SAME_ACCESS)) {
> return 0;
> }
>
> if (!DuplicateHandle(GetCurrentProcess(),hInputWriteTmp,
> GetCurrentProcess(),
> hChildStdIn, // Address of new handle.
> 0,FALSE, // Make it uninheritable.
> DUPLICATE_SAME_ACCESS)) {
> return 0;
> }
>
> // Close inheritable copies of the handles you do not want to be
> // inherited.
> if (!CloseHandle(hOutputReadTmp)) {
> return 0;
> }
> if (!CloseHandle(hInputWriteTmp)) {
> return 0;
> }
>
> // Set up the start up info struct.
> ZeroMemory(&si,sizeof(STARTUPINFO));
> si.cb = sizeof(STARTUPINFO);
> si.dwFlags = STARTF_USESTDHANDLES;
> si.hStdOutput = hOutputWrite;
> si.hStdInput = hInputRead;
> 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,(LPCWSTR)(command),
> NULL,NULL,TRUE,
> 0,NULL,NULL,&si,&pi)) {
> return 0;
> }
>
> // Close any unnecessary handles.
> if (!CloseHandle(pi.hThread)) {
> return 0;
> }
> if (hChildProcess != NULL) {
> *hChildProcess = pi.hProcess;
> } else {
> if (!CloseHandle(pi.hProcess)) {
> return 0;
> }
> }
> if (!CloseHandle(hOutputWrite)) {
> return 0;
> }
> if (!CloseHandle(hInputRead)) {
> return 0;
> }
> return 1;
> }
>
> int
> pipe_read (HANDLE fd, void *buffer, size_t count) {
> DWORD numRead;
> if (!ReadFile (fd, buffer, (DWORD)count, &numRead, NULL))
> { //non overlapped
> return -1;
> }
> return (int) numRead;
> }
>
> On Mar 2, 12:40 pm, Hector Santos <sant9...(a)nospam.gmail.com> wrote:
>> You should show code because that first link with redirection logic
>> should work for you. However, there are ways do this without using pipes.
>>
>> This is pretty standard practice coding stuff. So show code to see
>> where you went wrong.
>>
>> --
>>
>>
>>
>> IlyaK wrote:
>>> Hello:
>>> Strange hang problem with ReadFile.
>>> Here is the algorithm:
>>> The idea is to launch a python script from one Win32 GUI application,
>>> then read its std output and store it in a file. Do this until the
>>> script is done.
>>> 1. I create a child process using CreateProcess with the code pretty
>>> much copied fromhttp://support.microsoft.com/default.aspx?scid=kb;en-us;190351.
>>> However, instead of creating some special child process I just launch
>>> a python script.
>>> 2. Then start reading from the hChildStdOut using ReadFile.
>>> 3. The problem is that ReadFile at the end hangs indefinitely waiting
>>> for more data from the child process.
>>> Is there any way to know if the child process is done (or dead) and
>>> stop reading. I tried several methods to do that, however, they
>>> weren't reliable so far.
>>> 1. Check if the ReadFile lpNumberOfBytesRead is smaller than
>>> nNumberOfBytesToRead (http://msdn.microsoft.com/en-us/library/
>>> aa365467%28VS.85%29.aspx). This didn't work because at the beginning
>>> sometimes the Python script returns lpNumberOfBytesRead <
>>> nNumberOfBytesToRead. It does some init and it takes time, I suppose.
>>> 2. Use GetExitCodeProcess (http://msdn.microsoft.com/en-us/library/
>>> ms683189%28VS.85%29.aspx) on the child process handle. This method
>>> didn't work either. For some reason the GetExitCodeProcess would
>>> indicate that the process is still active.
>>> Please help.
>>> -Ilya.
>> --
>> HLS
>



--
HLS
From: IlyaK on
Thanks Hector.

1. get_cancel_text() is just an abstraction of a higher level function
that checks if the user pressed some button. I know I don't block on
it because2. I stepped through the code with the debugger.
2. If you know a better way to do this without piping, let me know. I
don't need interactive IO.
3. I don't prematurely close the processor handle, if the
hChildProcess != NULL. I do close the main thread handle. Do you think
this is a problem?
4. Your code calls ReadFile until it returns false. I tried that, but
I still get stuck.
5 Does it make any difference that I do all this in mfc application?

Thank you,
-Ilya.