Capturing binary output from Process.StandardOutput
Solution 1:
Using StandardOutput.BaseStream
is the correct approach, but you must not use any other property or method of cmdProcess.StandardOutput
. For example, accessing cmdProcess.StandardOutput.EndOfStream
will cause the StreamReader
for StandardOutput
to read part of the stream, removing the data you want to access.
Instead, simply read and parse the data from br
(assuming you know how to parse the data, and won't read past the end of stream, or are willing to catch an EndOfStreamException
). Alternatively, if you don't know how big the data is, use Stream.CopyTo
to copy the entire standard output stream to a new file or memory stream.
Solution 2:
Since you explicitly specified running on Suse linux and mono, you can work around the problem by using native unix calls to create the redirection and read from the stream. Such as:
using System;
using System.Diagnostics;
using System.IO;
using Mono.Unix;
class Test
{
public static void Main()
{
int reading, writing;
Mono.Unix.Native.Syscall.pipe(out reading, out writing);
int stdout = Mono.Unix.Native.Syscall.dup(1);
Mono.Unix.Native.Syscall.dup2(writing, 1);
Mono.Unix.Native.Syscall.close(writing);
Process cmdProcess = new Process();
ProcessStartInfo cmdStartInfo = new ProcessStartInfo();
cmdStartInfo.FileName = "cat";
cmdStartInfo.CreateNoWindow = true;
cmdStartInfo.Arguments = "test.exe";
cmdProcess.StartInfo = cmdStartInfo;
cmdProcess.Start();
Mono.Unix.Native.Syscall.dup2(stdout, 1);
Mono.Unix.Native.Syscall.close(stdout);
Stream s = new UnixStream(reading);
byte[] buf = new byte[1024];
int bytes = 0;
int current;
while((current = s.Read(buf, 0, buf.Length)) > 0)
{
bytes += current;
}
Mono.Unix.Native.Syscall.close(reading);
Console.WriteLine("{0} bytes read", bytes);
}
}
Under unix, file descriptors are inherited by child processes unless marked otherwise (close on exec). So, to redirect stdout
of a child, all you need to do is change the file descriptor #1 in the parent process before calling exec
. Unix also provides a handy thing called a pipe which is a unidirectional communication channel, with two file descriptors representing the two endpoints. For duplicating file descriptors, you can use dup
or dup2
both of which create an equivalent copy of a descriptor, but dup
returns a new descriptor allocated by the system and dup2
places the copy in a specific target (closing it if necessary). What the above code does, then:
- Creates a pipe with endpoints
reading
andwriting
- Saves a copy of the current
stdout
descriptor - Assigns the pipe's write endpoint to
stdout
and closes the original - Starts the child process so it inherits
stdout
connected to the write endpoint of the pipe - Restores the saved
stdout
- Reads from the
reading
endpoint of the pipe by wrapping it in aUnixStream
Note, in native code, a process is usually started by a fork
+exec
pair, so the file descriptors can be modified in the child process itself, but before the new program is loaded. This managed version is not thread-safe as it has to temporarily modify the stdout
of the parent process.
Since the code starts the child process without managed redirection, the .NET runtime does not change any descriptors or create any streams. So, the only reader of the child's output will be the user code, which uses a UnixStream
to work around the StreamReader
's encoding issue,