Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Parent send EOF to child's StdIn

Status
Not open for further replies.
Apr 13, 2001
4,475
0
0
US
I tried searching here and didn't find a good answer... heck, I've tried searching lots of places. I wish I had Appleman's API book to look at too but I don't have a copy.

I have a process that initiates a child process using [tt]CreateProcess()[/tt], redirecting the standard I/O streams in a pretty conventional manner via anonymous pipes. The problem is that I have some old code I need to run as the "child" and it needs to receive an EOF status on its StdIn to signal "end of input."

Of course running the program manually via CMD.EXE it works fine. Pressing CTRL-Z causes the application to see EOF and it sends a response and finishes.

From my VB program acting as a parent (in place of the CMD.EXE shell) this doesn't seem to be working though.

How do you "send" an EOF on an anonymous (or named, for that matter) pipe?


More background:


The documentation seems a little vague, and I believe I have tried everything obvious. Yes, I have made made sure that the child process does not inherit the handle to the write end of the StdIn pipe (nor the read end of my StdOut/StdErr pipe) using [tt]SetHandleInformation()[/tt] too.

Thing #1:

In the parent process, signal EOF by closing the write end of the StdIn pipe to the child. Make sure that the child process does not inherit the handle to the write end of the StdIn pipe.

Thing #2:

In the parent process, signal EOF by doing a "null write" (zero bytes length) through the write end of the StdIn pipe to the child.

Thing #3:

Null write followed by close of the write end of the child's StdIn pipe.


None of those "things" seems to work.

Even stranger, one small program I found and that I have the source code to only wants to read "lines" (waits for input up to a CR/LF). It is so agressive about wanting "lines" that it doesn't even detect EOF on it's StdIn input properly unless the EOF is followed by an input CR/LF!

This seems odd, but it works fine from CMD.EXE: run this program, type in:
[tt]
C:\>littletest.exe
text<enter>
type more text<enter>
^Z<enter>
I saw 2 lines of input.
C:\>
[/tt]
... and it ends just fine. BUT not until after the enter key is pressed following the CTRL-Z.


So... this seems to indicate that EOF on a StdIn stream redirected via an anonymous pipe is not a closure at the other end of the pipe. Unless... does CMD.EXE close the redirected StdIn pipe and then immediately re-open it???


Weirdly enough I have the CTRL-C and CTRL-Break interrupts working fine. When the parent process requests termination in this manner the child finishes up and the read end of the child's StdOut pipe gets a "broken pipe" error as expected.
 
Ok...

Modifying that odd little command line program so that it detects EOF on StdIn immediately (NOT waiting for a subsequent CR/LF) tells me something.

Now simply having the parent process close its write handle to the child's StdIn causes an EOF to be seen there.


But...

There are still programs out there that do require more input (like a CR/LF) to detect the EOF. These don't work properly with my parent process, but they do work with CMD.EXE after a ^Z followed by a CR/LF.


So...

I'm back to my theory that CMD.EXE re-opens the pipe to StdIn after closing it (when the user types a ^Z). Does this make sense?


Furthermore...

If I want to be able to handle "random" programs as my child process, should I pump the ^Z character itself through? Some old programs may still require (look for) this character, right?

If so, do I send out the ^Z character before or after the EOF (handle close)?


Answers?

Maybe I'll need to write a test program to try to capture and report precisely what CMD.EXE does I suppose. The documentation sure seems slim on the subject.
 
:) And a number of mine do too ...

The point is, however, that we probably need to understand better how you have implemented the standard pipes for your VB application before commenting on how or why they might be malfunctioning ...
 
Sorry, didn't mean to be a smart-aleck. Here's a trivial example, w/o using API calls:

echo.bas
Code:
Sub Main()
    Dim FSO As New Scripting.FileSystemObject
    
    FSO.GetStandardStream(StdOut).WriteLine Command$()
End Sub

Of course for it to work you need to edit the compiled object VB generates. I usually do this with a short WSH script:

LinkConsole.vbs
Code:
Option Explicit
'LinkConsole.vbs
'
'This is a WSH script used to make it easier to edit
'a compiled VB6 EXE using LINK.EXE to create a console
'mode program.
'
'Drag the EXE's icon onto the icon for this file, or
'execute it from a command prompt as in:
'
'        LinkConsole.vbs <EXEpath&file>
'
'Be sure to set up strLINK to match your VB6 installation.

Dim strLINK, strEXE, WSHShell

strLINK = _
  """C:\Program Files\Microsoft Visual Studio\VB98\LINK.EXE"""
strEXE = """" & WScript.Arguments(0) & """"
Set WSHShell = CreateObject("WScript.Shell")

WSHShell.Run _
  strLINK & " /EDIT /SUBSYSTEM:CONSOLE " & strEXE

Set WSHShell = Nothing
WScript.Echo "Complete!"
 
And to follow up on your followup...

Yes, I agree. As usual without seeing the code there is no end to the number of ways I may have gone wrong.

But the pipes are working fine. The trouble is with those anomolous console programs that insist in eading "until end of line" i.e. until a CR/LF comes through.


But you're right again. At this point I don't think I have an API issue. Instead it is more of a "how exactly is CMD.EXE doing what it does?" issue.

So the point of the original post I made may be dead now.
 
Just to avoid looking too darned smug to post my code (which is pretty generic stuff):
Code:
Public Function Run(ByVal CommandLine As String, _
                    Optional ByVal CurrentDir As String = vbNullString) _
                    As SP_RESULTS
    With saPipe
        .nLength = Len(saPipe)
        .lpSecurityDescriptor = 0
        .bInheritHandle = 1
    End With
    
    If CreatePipe(hChildOutPipeRd, hChildOutPipeWr, saPipe, 0&) = 0 Then
        Run = SP_CREATEPIPEFAILED
        Exit Function
    End If
    SetHandleInformation hChildOutPipeRd, HANDLE_FLAG_INHERIT, 0&
    
    If CreatePipe(hChildInPipeRd, hChildInPipeWr, saPipe, 0&) = 0 Then
        Run = SP_CREATEPIPEFAILED
        Exit Function
    End If
    SetHandleInformation hChildInPipeWr, HANDLE_FLAG_INHERIT, 0&
    
    'Set or clear everything because we may be used more than once.
    With siStart
        .cb = Len(siStart)
        .lpReserved = vbNullString
        .lpDesktop = vbNullString
        .lpTitle = vbNullString
        .dwX = 0
        .dwY = 0
        .dwXSize = 0
        .dwYSize = 0
        .dwXCountChars = 0
        .dwYCountChars = 0
        .dwFillAttribute = 0
        .dwFlags = STARTF_USESTDHANDLES Or STARTF_USESHOWWINDOW
        .wShowWindow = SW_HIDE
        .cbReserved2 = 0
        .lpReserved2 = 0
        'Process will write both StdOut and StdErr to OutPipe.
        .hStdOutput = hChildOutPipeWr
        .hStdError = hChildOutPipeWr
        'Process will read its StdIn from InPipe.
        .hStdInput = hChildInPipeRd
    End With
    With piProc
        .hProcess = 0
        .hThread = 0
        .dwProcessID = 0
        .dwThreadID = 0
    End With
    
    If CreateProcessA(vbNullString, CommandLine, 0&, 0&, 1&, _
                      NORMAL_PRIORITY_CLASS, _
                      0&, CurrentDir, siStart, piProc) = 0 Then
        m_blnProcessActive = False
        Run = SP_CREATEPROCFAILED
    Else
        CloseHandle piProc.hThread
        CloseHandle piProc.hProcess
        CloseHandle hChildOutPipeWr
        CloseHandle hChildInPipeRd
        m_blnProcessActive = True
        m_blnInPipeOpen = True
        tmrCheck.Enabled = True
        Run = SP_SUCCESS
    End If
End Function

The reason I hesitated is that clearly I might still have gone wrong a million ways. Any of those items may be defined improperly, from the UDTs and constants to the API Declares.

I didn't want to post the 400 lines or so (sans large comment header) of this thing (which happens to be a UserControl, of all things) though.


If I haven't put you off by this point however, do you happen to have a good, clean way to zero out a UDT without using an API call? I seem to be blocking on this one - thus the bulky zeroing above.
 
Well, in case anyone else stumbles over this thread seeking similar information, here's what I've found.


It can't be done.


Reading the actual console device via StdIn, reading a disk file redirected to StdIn, etc. does indeed result in an EOF condition on StdIn. For the console this is ^Z, for a disk file it is hitting the EOF pointer of the file.

When redirecting via an anonymous pipe however, the pipe writer can't signal EOF by any means I can locate!

The best thing the writer can do is to close the write handle on the pipe. This causes the next read done on StdIn to encounter a "broken pipe" error.

Now, some programs have been written to consider any system error (VB programs call GetLastError() in Kernel32 for this) or an EOF to be an EOF. Those will work fine when a broken pipe error pops up.

The problem is with programs trying to process an input EOF on StdIn (as it is documented). They'll variously ignore the error entirely, or fault and terminate, or do some other random thing.


Microsoft's example of how to properly have a StdIn reader (program) work with redirected input uses the kludge I described: either any error or EOF = EOF.

Of course they don't state this anywhere. Indeed the only documentation I can locate suggests that only EOF = EOF.

Sad, but true.


Any information to the contrary would be greatly appreciated.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top