7

I'm having a hard time deciphering the MSDN doc about Process.StandardOutpout as to if the Read(Char[], Int32, Int32) method blocks or not. My understanding is that it shouldn't block, but it seems like it does when I set RedirectStandardInput to true.

Does anybody have experience with this; or some explanation for the issue I'm having?

The context here is that I don't want to wait for a full line (ie with a line terminator), or for the process to exit before reading standard output. Also I don't want to use callbacks. I want to read StdOut synchronously as the process writes to it.

Here is a simplified version of my code:

string command = @"C:\flex_sdks\flex_sdk_4.5.1.21328\bin\fcsh.exe";
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = false; # <-- if I set this to true, then 
                                           # the program hangs on
                                           # p.StandardOutput.Read later on
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.FileName = command;
p.Start();

StringBuilder sb_stdout = new StringBuilder(1024);
char[] buffer = new char[64];
int nb_bytes_read;
while (true) {
    do {
        nb_bytes_read = p.StandardOutput.Read(buffer, 0, buffer.Length);
        sb_stdout.Append(new string(buffer, 0, nb_bytes_read));
    } while (nb_bytes_read > 0);
    if (sb_stdout.ToString().EndsWith("\n(fcsh) "))
        break;
    Thread.Sleep(20);
}

Update

Based on my (probably bad) assumption that Process.StandardOutput is broken when used:

  • with stdin redirected; and,
  • while reading something else than terminated lines from stdout or stderr,

I decided to try using Windows' API directly. I added an answer with such code; it works fine (at least for now).

Another update

I created a blog entry with the code I now use.

  • It seems like your real problem is buffering of the child's stdout, and no amount of stdin redirection is going to help that. See http://stackoverflow.com/questions/3844267/how-to-disable-output-buffering-in-process-standardoutput – Ben Voigt Jul 11 '11 at 20:36
  • Why? Where is it a problem in the code I posted? Reading stdout works perfectly well if I disable redirecting stdin. –  Jul 11 '11 at 20:56
  • You said you don't want to write for a full line, etc. That's a buffering issue (separate from the deadlock issue you mentioned in this question). – Ben Voigt Jul 11 '11 at 21:19
  • I don't want to wait for a full line to _read_ from stdout; for example, if I run this same .exe in cmd, cmd presents me with the non terminated line that is meant to be a prompt, eg "(fcsh) "; so how does cmd manage to present me that prompt? –  Jul 11 '11 at 21:23
  • Well, `cmd` can flush its output buffers whenever it wants. Most programs don't bother to flush, so they just get the default behavior implemented by their compiler's runtime library. Most of those libraries check whether stdout is a console or file, console output has no buffering, file output enables buffering. – Ben Voigt Jul 11 '11 at 21:30
  • Now, if you're simply talking about `Read` vs `ReadLine`, that's buffering in your program that you control. – Ben Voigt Jul 11 '11 at 21:30
  • Well, I don't care for ReadLine because I am not expecting a line; see it as if I'm trying to read individual characters, not lines. My question kinda still stands: why does Read block at all to begin with? –  Jul 11 '11 at 21:33
  • It has to block until at least one byte arrives. Are you seeing it block until the array is full? – Ben Voigt Jul 11 '11 at 21:37
  • You seem to be saying it _has_ to block until the array is full, yet when redirectstdin is off, it doesn't and nb_bytes_read tells me how many bytes were retrieved (and in case you're wondering, the process is till running at that point). With the above code with redirectstdin on, the array fills twice and all is fine, then it seems to block when read needs to put <64 bytes in the array (once again, it does _not_ block with redirectstdin off). –  Jul 11 '11 at 21:39
  • Does `p.StandardInput.Close()` make any difference? And no, it shouldn't have to block until the array is full, only until at least one character is read. There may be buffering inside the child process, though. – Ben Voigt Jul 11 '11 at 21:45
  • Yes, closing stdin makes a difference (ie read doesn't block). Also, from screwing around some more I just realized that using a buffer big enough to read the 133 bytes in one shot works (eg char[>=133]). –  Jul 11 '11 at 22:00
  • I guess this is probably one of those places where the .NET classes don't give you enough control. Partial read with the Win32 API would be pretty straightforward, guess the `Process` class less so. – Ben Voigt Jul 11 '11 at 22:13
  • Yeah, screw all this,I'm now trying CreateProcess from Kernel32. Thanks for our help! –  Jul 12 '11 at 07:28

4 Answers4

5

i fought and fought with this just last week actually... for some reason anything other than the Read() call (ReadToEnd() was not what i needed) seemed to block and never return. here's what i did to finally get it to "work":

snip 1:

    private bool ThreadExited = true;
    private bool ExitThread = false;
    private void ReadThread()
    {
        while (!ExitThread)
        {
            string Output = "";

            int CharacterInt = myProcess.StandardOutput.Read();

            while (CharacterInt > 0)
            {
                char Character = (char)CharacterInt;
                Output += Character;

                var MyDelegate = new delegateUpdateText(UpdateText);
                Invoke(MyDelegate, Output);

                Output = "";
                CharacterInt = myProcess.StandardOutput.Read();
            }

            System.Threading.Thread.Yield();
        }

        ThreadExited = true;
    }

snip 2:

    private void InitializeProcess()
    {
        ThreadExited = true;
        ExitThread = true;

        while (!ThreadExited)
            System.Threading.Thread.Sleep(1000);

        ThreadExited = false;
        ExitThread = false;

        myProcess = new Process();

        ProcessStartInfo PSI = myProcess.StartInfo;

        PSI.FileName = @"cmd.exe";
        PSI.UseShellExecute = false;
        PSI.RedirectStandardError = false;
        PSI.RedirectStandardInput = true;
        PSI.RedirectStandardOutput = true;
        PSI.CreateNoWindow = false;
        PSI.ErrorDialog = true;


        myProcess.StartInfo = PSI;

        myProcess.Exited += new EventHandler(myProcess_Exited);

        myProcess.EnableRaisingEvents = false;


        myProcess.Start();

        ReadThreadThread = new System.Threading.Thread(ReadThread);
        ReadThreadThread.Start();
    }
    private System.Threading.Thread ReadThreadThread;

that finally ended up working for me. in my case i was writing text to to a textbox but that should be easy enough to modify to something else. but anything else i did caused issues due to the blocks; for some reason even if i used reflection to get the number of bytes that were available, calling the ReadBlock() function would block. never did figure it out to my satisfaction.

shelleybutterfly
  • 3,216
  • 15
  • 32
  • this actually was from the code i used to test originally; i uploaded an entire project which has a "working" command prompt if you're interested: http://www.mediafire.com/file/ejuv7j7ta5rr7i1/WindowsFormsApplication1.zip (sorry for the default naming ;)) – shelleybutterfly Jul 11 '11 at 21:26
  • I would avoid using Thread.Yield() in code like this. You will likely find that the CPU is spinning, because while Yield will ensure other threads/processes may take the CPU if needed, it will return to executing your code as often as the CPU isn't needed elsewhere. To avoid spinning, check if you need to spin first (is there more work?), if not then Thread.Sleep(1). – John Thoits Jan 27 '22 at 18:06
  • Oh, wow. I mean it's been a very long time since I wrote this code, but I just went and looked at the docs on Thread.Yield() and yikes! If I read the description correctly, not only will it be (almost) spinning, it will keep things from any other thread in your app from running. I'll fix the code. (And just to mention, you should always feel free to just edit important changes into an answer, in case someone reads the answer but misses your comment.) Anyhoo, thanks for the note! – shelleybutterfly Jun 04 '22 at 05:44
3

After discussing this a bit with Ben Voigt, I decided to implement communicating with the process without using System.Diagnostics.Process. This is what I came up with for now and it works great, ie it works consistently every time, and nothing blocks or hangs.

I'm posting this as this might help anyone needing to read from stdout/stderr and to write to stdin of some created process while doing without System.Diagnostics.Process.

const UInt32 STARTF_USESTDHANDLES = 0x00000100;
const int HANDLE_FLAG_INHERIT = 1;

struct PROCESS_INFORMATION
{
    public IntPtr hProcess;
    public IntPtr hThread;
    public uint dwProcessId;
    public uint dwThreadId;
}

struct STARTUPINFO
{
    public uint cb;
    public string lpReserved;
    public string lpDesktop;
    public string lpTitle;
    public uint dwX;
    public uint dwY;
    public uint dwXSize;
    public uint dwYSize;
    public uint dwXCountChars;
    public uint dwYCountChars;
    public uint dwFillAttribute;
    public uint dwFlags;
    public short wShowWindow;
    public short cbReserved2;
    public IntPtr lpReserved2;
    public IntPtr hStdInput;
    public IntPtr hStdOutput;
    public IntPtr hStdError;
}

struct SECURITY_ATTRIBUTES
{
    public int length;
    public IntPtr lpSecurityDescriptor;
    [MarshalAs(UnmanagedType.Bool)]
    public bool bInheritHandle;
}

[DllImport("kernel32.dll")]
static extern bool CreateProcess(string lpApplicationName,
                                 string lpCommandLine,
                                 IntPtr lpProcessAttributes,
                                 IntPtr lpThreadAttributes,
                                 bool bInheritHandles,
                                 uint dwCreationFlags,
                                 IntPtr lpEnvironment,
                                 string lpCurrentDirectory,
                                 ref STARTUPINFO lpStartupInfo,
                                 out PROCESS_INFORMATION lpProcessInformation);

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CreatePipe(out IntPtr hReadPipe,
                              out IntPtr hWritePipe,
                              ref SECURITY_ATTRIBUTES lpPipeAttributes,
                              uint nSize);

[DllImport("kernel32", SetLastError = true)]
static extern unsafe bool ReadFile(IntPtr hFile,
                                   void* pBuffer,
                                   int NumberOfBytesToRead,
                                   int* pNumberOfBytesRead,
                                   IntPtr lpOverlapped);

[DllImport("kernel32.dll")]
static extern unsafe bool WriteFile(IntPtr hFile,
                                    void* pBuffer,
                                    int nNumberOfBytesToWrite,
                                    int* lpNumberOfBytesWritten,
                                    IntPtr lpOverlapped);

[DllImport("kernel32.dll")]
static extern bool SetHandleInformation(IntPtr hObject, int dwMask, uint dwFlags);

void OpenAndCloseFcsh()
{
    STARTUPINFO si = new STARTUPINFO();
    SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
    PROCESS_INFORMATION pi = new PROCESS_INFORMATION();

    sa.bInheritHandle = true;
    sa.lpSecurityDescriptor = IntPtr.Zero;
    sa.length = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
    sa.lpSecurityDescriptor = IntPtr.Zero;

    IntPtr h_stdout_r, h_stdout_w;
    if (!CreatePipe(out h_stdout_r, out h_stdout_w, ref sa, 0))
        throw new Exception("bad");
    if (!SetHandleInformation(h_stdout_r, HANDLE_FLAG_INHERIT, 0))
        throw new Exception("bad");

    IntPtr h_stdin_r, h_stdin_w;
    if (!CreatePipe(out h_stdin_r, out h_stdin_w, ref sa, 0))
        throw new Exception("bad");
    if (!SetHandleInformation(h_stdin_w, HANDLE_FLAG_INHERIT, 0))
        throw new Exception("bad");

    si.wShowWindow = 0;
    si.cb = (uint)Marshal.SizeOf(si);
    si.dwFlags |= STARTF_USESTDHANDLES;
    si.hStdOutput = h_stdout_w;
    si.hStdError = h_stdout_w;
    si.hStdInput = h_stdin_r;

    string command = @"C:\flex_sdks\flex_sdk_4.5.1.21328_trimmed\bin\fcsh.exe";

    if (!CreateProcess(command, null, IntPtr.Zero, IntPtr.Zero, true, 0, IntPtr.Zero, null, ref si, out pi))
        throw new Exception("bad");

    Console.WriteLine("Process ID (PID): " + pi.dwProcessId);
    Console.WriteLine("Process Handle : " + pi.hProcess);

    // ****************************************************
    // let's interact with our process

    // first read to the prompt
    Console.WriteLine("read this from fcsh.exe:\r\n" + ReadTillPrompt(h_stdout_r));

    // write "help" to stdin
    byte[] bytes_to_write = Encoding.UTF8.GetBytes("help\r\n");
    Write(h_stdin_w, bytes_to_write, 0, bytes_to_write.Length);

    // then read to the prompt again
    Console.WriteLine("read this from fcsh.exe:\r\n" + ReadTillPrompt(h_stdout_r));

    // write "quit" to stdin
    bytes_to_write = Encoding.UTF8.GetBytes("quit\r\n");
    Write(h_stdin_w, bytes_to_write, 0, bytes_to_write.Length);

    // ****************************************************

    if (!CloseHandle(pi.hProcess))
        throw new Exception("bad");
    if (!CloseHandle(pi.hThread))
        throw new Exception("bad");
    if (!CloseHandle(h_stdout_w))
        throw new Exception("bad");
    if (!CloseHandle(h_stdin_w))
        throw new Exception("bad");
}

public string ReadTillPrompt(IntPtr h_stdout_r)
{
    StringBuilder sb = new StringBuilder(1024);
    byte[] buffer = new byte[128];

    int nb_bytes_read;
    while (true) {
        nb_bytes_read = Read(h_stdout_r, buffer, 0, buffer.Length);
        sb.Append(Encoding.UTF8.GetString(buffer, 0, nb_bytes_read));
        if (sb.ToString().EndsWith("\n(fcsh) "))
            break;
        Thread.Sleep(20);
    }
    return sb.ToString();
}

public unsafe int Read(IntPtr h, byte[] buffer, int index, int count)
{
    int n = 0;
    fixed (byte* p = buffer) {
        if (!ReadFile(h, p + index, count, &n, IntPtr.Zero))
            throw new Exception("bad");
    }
    return n;
}

public unsafe int Write(IntPtr h, byte[] buffer, int index, int count)
{
    int n = 0;
    fixed (byte* p = buffer) {
        if (!WriteFile(h, p + index, count, &n, IntPtr.Zero))
            throw new Exception("bad");
    }
    return n;
}
  • This does not work for me. It buffers all of the stdout until the process completes. I have not found the correct solution yet for .NET. – Mark Lakata Aug 16 '12 at 23:54
  • 1
    @MarkLakata: the above code is stale so I created a blog entry with my latest code. Feel free to give it a try and let me know how it goes. Link is http://sixfeetsix.blogspot.ch/2012/08/interacting-with-sub-processed-shell-in.html –  Aug 30 '12 at 15:50
2

Raymond Chen recently covered this on his blog:

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • @sixfeetsix: Sounds like the child program is reading stdin with a blocking read. When stdin isn't redirected, it gets "read succeeded, zero bytes, end of file". When stdin is redirected, the blocking read never completes. – Ben Voigt Jul 11 '11 at 20:35
  • Don't think, honestly, it's case to choose IPC mechanism here, at least looking on problem description. What he needs is just read and interpret output. IPC implies that the processes "talk" with each other, which will lead, in this case, a need to change also calling process code, assuming that he has access to that code. – Tigran Jul 11 '11 at 20:36
  • @Tigran, once again, my interest as to how I could be doing thing differently is very limitited. If you can't address my _actual_ question, please, please don't add noise with your opinions. –  Jul 11 '11 at 21:04
  • @Ben, your last comment about how the sub-process hangs while waiting for something in stdin is very interesting; I guess I need to read more about how stdin works in general, and with windows in particular –  Jul 11 '11 at 21:06
  • @sixfeetsix: It depends on how the subprocess is designed. I'm working based on a combination of your description of observed behavior, and knowing how a blocking read works. – Ben Voigt Jul 11 '11 at 21:18
1

It's not very clear from your post what you mean by "I need to read it synchronously immediately after the process writes to it." If you need immediate feedback, you need asynchronous management.

Pseudocode:

Synchronous management:

string sOutput = process.StandardOutput.ReadToEnd();
process.WaitToExit();

Asynchronous management:

/*subscribe to events in order to receive notification*/    
p.StartInfo.RedirectStandardInput = true;
p.OutputDataReceived += Subscription

Afterwards if you need p.WaitForExit();, if you don't care when it finished but just want data from it, you can even avoid that line.

Hope this helps.

Adaline Simonian
  • 4,596
  • 2
  • 24
  • 35
Tigran
  • 61,654
  • 8
  • 86
  • 123
  • Yes sorry for the confusion, I re-worked my question; see in bold "I want to read StdOut synchronously as the process writes to it." Also, please note how my question asks about the change in behaviour with RedirectStandardInput=true, not about how I can do things differently because other people feel like I should do them that way. –  Jul 11 '11 at 20:02
  • In this case try the code I posted, could be useful to you. Regards. – Tigran Jul 11 '11 at 20:04