views:

1463

answers:

4

I have a form with 3 controls:

  1. A textbox for the user to enter commands to send to a console application,
  2. A button to confirm the commands to be sent and
  3. A read-only textbox to display the output from the application.

What I want is for the user to enter commands in the first textbox, press the button to enter and receive feedback via the second textbox.

I know how to use ProcessStartInfo.RedirectStandardOutput but, however, the app hangs when I use StandardOutput.ReadToEnd().

I had a look at the asynchronous Process.BeginOutputReadLine() but, even though my app does not hang, somehow I get no response in the textbox, it does absolutely nothing.

Here's my code:

public partial class MainForm : Form
{

    private void MainForm_Load(object sender, EventArgs e)
    {
        InitializeInterpreter();
    }

    private void InitializeInterpreter()
    {
        InterProc.StartInfo.UseShellExecute = false;
        InterProc.StartInfo.FileName = "app.exe";
        InterProc.StartInfo.RedirectStandardInput = true;
        InterProc.StartInfo.RedirectStandardOutput = true;
        InterProc.StartInfo.RedirectStandardError = true;
        InterProc.StartInfo.CreateNoWindow = true;
        InterProc.OutputDataReceived += new DataReceivedEventHandler(InterProcOutputHandler);

        InterProc.Start();
    }

    private static void InterProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
    {
        if (!String.IsNullOrEmpty(outLine.Data))
        {
           OutputTextBox.Append(Environment.NewLine + outLine.Data);
        }
    }

    private void Enterbutton_Click(object sender, EventArgs e)
    {
        InterProc.StandardInput.Write(CommandtextBox.Text);
        InterProc.BeginOutputReadLine();
    }
}

Is there any way I can have this run smoothly? Thanks.

+1  A: 

I've used code something like this:

    public static void Run(string fileName, string arguments, out string standardOutput, out string standardError, out int exitCode)
    {
        Process fileProcess = new Process();
        fileProcess.StartInfo = new ProcessStartInfo
        {
            FileName = fileName,
            Arguments = arguments,
            RedirectStandardError = true,
            RedirectStandardOutput = true,
            UseShellExecute = false,
            WindowStyle = ProcessWindowStyle.Hidden,
            CreateNoWindow = true,
        };

        bool started = fileProcess.Start();

        if (started)
        {
            fileProcess.WaitForExit();
        }
        else
        {
            throw new Exception("Couldn't start");
        }

        standardOutput = fileProcess.StandardOutput.ReadToEnd();
        standardError = fileProcess.StandardError.ReadToEnd();
        exitCode = fileProcess.ExitCode;

    }

But it's not interactive. But if the app is interactive, it'll take a lot more code anyway.

McKay
Yeah, I really need it to be interative...
Zach
By "Interactive" do you mean that you want to pass data to the standard in of the program? Or just that it will update as the app continues to output. The former is a bit harder than the latter.In any case, you complain that ReadToEnd is what hangs, when you're not using that in your sample code. What's actually the problem or can we see the new code?
McKay
A: 

Where are you calling StandardOutput.ReadToEnd()? I once had a similar problem because I was calling Process.WaitForExit() before StandardOutput.ReadToEnd(). I had a large amount of input, and the output buffer was full before completion and my process was blocked.

You must call StandardOutput.ReadToEnd()before Process.WaitForExit().

Dour High Arch
+1  A: 

If you want something interactive, I got this code to work (yours modified, details on modifications below)

    private void InitializeInterpreter()
    {
        InterProc.StartInfo.UseShellExecute = false;
        InterProc.StartInfo.FileName = "Echoer.exe";
        InterProc.StartInfo.RedirectStandardInput = true;
        InterProc.StartInfo.RedirectStandardOutput = true;
        InterProc.StartInfo.RedirectStandardError = true;
        InterProc.StartInfo.CreateNoWindow = true;
        InterProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        InterProc.OutputDataReceived += new DataReceivedEventHandler(InterProcOutputHandler);

        bool started = InterProc.Start();

        InterProc.BeginOutputReadLine();

    }

    private void AppendTextInBox(TextBox box, string text)
    {
        if (this.InvokeRequired)
        {
            this.Invoke((Action<TextBox, string>)AppendTextInBox, OutputTextBox, text);
        }
        else
        {
            box.Text += text;
        }
    }

    private void InterProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
    {
        AppendTextInBox(OutputTextBox, outLine.Data + Environment.NewLine);
    }

    private void Enterbutton_Click(object sender, EventArgs e)
    {
        InterProc.StandardInput.WriteLine(CommandTextBox.Text);
    }

So, I moved the BeginOutputReadLine to just after the process is started. That ensures it's really only called once. I also did an invoke required to clean up thread calls. Hopefully this should work for you.

McKay
Thanks a lot, but I hooked it up properly, but I still get no output in the output text box when I enter a command and press enter. The executable I'm trying to call is "lua.exe" from http://luaforwindows.luaforge.net/Entering something like print("Hello")should output:Hello>and the user should be able to use it like that. Anyway, I hooked up some Debug.WriteLine()'s which showed that the last statement executed may have beenInterProc.StandardInput.WriteLine(CommandTextBox.Text);so that may be where something goes wrong.
Zach
So, I used this code, and it worked flawlessly for me.I did write a custom executable, that echoed everything back to me. The interactivity worked fine.
McKay
If the executable isn't ready to receive a message, it won't get received yet. So, maybe the problem is in the lua.exe? Try hooking it up to a different app?
McKay
It seems as though something's wrong with lua.exe, even with StdError. It works great with other exe's but not lua.exe. Oh well...
Zach
It doesn't work with octave.exe neither
Jader Dias
@McKay @Zach The problem with this event is that it only is called when a the output receives a newline. My solution in another answer in this thread works also when the output has no carriage returns nor newlines
Jader Dias
+2  A: 

The best solution I have found is:

private void Redirect(StreamReader input, TextBox output)
{
    new Thread(a =>
    {
        var buffer = new char[1];
        while (input.Read(buffer, 0, 1) > 0)
        {
            output.Dispatcher.Invoke(new Action(delegate
            {
                output.Text += new string(buffer);
            }));
        };
    }).Start();
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    process = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            CreateNoWindow = true,
            FileName = "app.exe",
            RedirectStandardError = true,
            RedirectStandardOutput = true,
            UseShellExecute = false,
        }
    };
    if (process.Start())
    {
        Redirect(process.StandardError, textBox1);
        Redirect(process.StandardOutput, textBox1);
    }
}
Jader Dias