1

I've looked everywhere, but I just can't find the right answer.

I use VS2013 > C# > Windows Forms-Application

Below you see a working version of my process. But I have two little problems, which I don't know how to fix.

  1. The *.exe is an optimization algorithm, that displays each iteration it does and the current best solution it has found. -> but because I have 'useshellexecute = false' I don't see anything in the command shell

  2. The user can interupt the algorithm at any time by pressing 'Ctrl+C' and the algorithm will stop and return the current best solution -> but because I have 'useshellexecute = false' I can't imput any key-commands

How can I fix this ?? - I need to see the interations and to be able to press 'Ctrl+C'. - It doesn't has to be in the command shell, I would be ok with an alternativ "interface". - If I set 'useshellexecute = true' how can I input commands and read all lines.

Please note:

P.StartInfo.Arguments

to input the commands does not work. The *.exe will thow an "invalite input" error.

Code that works:

private void btn_Optimize_Start_Click(object sender, EventArgs e)
    {
        Process P = new Process();

        P.StartInfo.FileName = @Application.StartupPath + @"\Algorithm.exe";
        P.StartInfo.UseShellExecute = false;
        P.StartInfo.RedirectStandardInput = true;
        P.StartInfo.RedirectStandardOutput = true;
        P.StartInfo.RedirectStandardError = true;

        P.Start();
        //sets timelimit 30 min
        P.StandardInput.WriteLine("set lim tim 1800"); 
        //reads the modell for which an optimal solution has to be found 
        P.StandardInput.WriteLine("read modell.zpl");
        //command that starts the optimization algorithm
        P.StandardInput.WriteLine("optimize");         //this part can take hours
        //command that displays the solution
        P.StandardInput.WriteLine("display solution");
        //ends the *.exe
        P.StandardInput.WriteLine("quit");

        //saves all information in a log-file with which I can work
        string[] log = P.StandardOutput.ReadToEnd().Split(new string[] { Environment.NewLine }, StringSplitOptions.None);

        //opens a function, that formats the solution
        this.result_create(log);
    }

edit 11.11.2014 / Threaded Process / Output in RichTextBox:

private void btn_Optimize_Start_Click(object sender, EventArgs e)
    {
        Process P = new Process();

        P.StartInfo.FileName = @Application.StartupPath + @"\Algorithm.exe";
        P.StartInfo.UseShellExecute = false;
        P.StartInfo.RedirectStandardInput = true;
        P.StartInfo.RedirectStandardOutput = true;
        P.StartInfo.RedirectStandardError = true;
        //*** NEW *** Event Handler for the asynchron Output-Process
        P.OutputDataReceived += new DataReceivedEventHandler(this.Asyn_Process);

        P.Start();

        //*** NEW *** Starts asynchron Output-Process
        P.BeginOutputReadLine();

        //sets timelimit 30 min
        P.StandardInput.WriteLine("set lim tim 1800"); 
        //reads the modell for which an optimal solution has to be found 
        P.StandardInput.WriteLine("read modell.zpl");
        //command that starts the optimization algorithm
        P.StandardInput.WriteLine("optimize");         //this part can take hours
        //command that displays the solution
        P.StandardInput.WriteLine("display solution");
        //ends the *.exe
        P.StandardInput.WriteLine("quit");

        //*** DELETED ***
        //saves all information in a log-file with which I can work
        //string[] log = P.StandardOutput.ReadToEnd().Split(new string[] { Environment.NewLine }, StringSplitOptions.None); 

        //opens a function, that formats the solution
        //this.result_create(log);
    }

    //*** NEW *** The asynchronous Process
    private void Asyn_Process(object sender, DataReceivedEventArgs e)
    {
        if (this.rTB_Log.InvokeRequired && e.Data != null)
        {
            //Anonym Invoke Function
            this.rTB_Log.Invoke(new MethodInvoker(delegate()
            {
                //Writes Output continuously in the RichTextBox
                this.rTB_Log.Text += e.Data + Environment.NewLine;
                //Scroll to End of RichTextBox continuously
                this.rTB_Log.SelectionStart = this.rTB_Log.Text.Length;
                this.rTB_Log.ScrollToCaret();                          
            }));
        }
        //When the process has finished (e.Data == null)
        else
        {
            //Anonym Invoke Function
            this.rTB_Log.Invoke(new MethodInvoker(delegate()
            {
                //Saves the RichTextBox-Content in a Text-File
                this.rTB_Log.SaveFile(Algorithm.log", RichTextBoxStreamType.PlainText);
            }));
        }
    }
Beckz
  • 13
  • 5
  • Get rid of the `InvokeRequired`. It's not an error to call `Invoke` when you don't need to, and it would be an error to skip that logic. – Ben Voigt Nov 11 '14 at 20:08

1 Answers1

1

First, you're not getting any benefit from UseShellExecute = true. It's just the side effects -- ShellExecute is incompatible with redirection, and so you're seeing what happens without redirection.

Your core issue is that you need output from the program to go two places. On Unix, you'd use the tee to send the output to both the console and a file (or pseudo-file) of your choice, your application would read that file to get the results.

Windows doesn't have ready-made tools that do that, but all the necessary parts are there. The approach you want is:

  1. Use redirection, not ShellExecute
  2. Read the application output continuously, not with ReadToEnd()
  3. Everything you read from the child application, also show to the user. Sending it to your own console window is fine, but it is equally valid to dump it into a textbox, or parse it and make a plot.
  4. When the user wants to interrupt the search, they'll use your UI to do it, because the child process is no longer reading its input from the console. And you'll have to relay that to the child process, as if CTRL+C had been pressed. There's a Win32 function to do that, GenerateConsoleCtrlEvent. I don't think there's any .NET replacement, so you'll probably have to use p/invoke.
  5. Worse, as noted in this answer, GenerateConsoleCtrlEvent acts on an entire a process group, and Process.Start doesn't use the flag that creates a new group, so you may need to also p/invoke CreateProcess. Which means you also have to use p/invoke to set up the stream redirections, which basically means creating pipes and sticking them in the STARTUPINFO structure passed to CreateProcess. That's a lot of work. Maybe you'll get lucky sharing the process group, and nothing else in the group responds badly. The docs also say "Only those processes in the group that share the same console as the calling process receive the signal. In other words, if a process in the group creates a new console, that process does not receive the signal, nor do its descendants." So you'll need to have a console in your application too, either use the a console subsystem or use AllocConsole.
Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thanks for the info - I feared as much.- I edited my code to read continuously and asynchronous. - I haven't tried the GenerateConsoleCtrlEvent yet and since this isn't a priority, it has to wait until I have a the time. – Beckz Nov 11 '14 at 15:29