Question
I am trying to start Python as a System.Diagnostics.Process
and redirect its standard output, input and error to a RichTextBox
on a Windows form, so that I can have a python REPL on my form.
The process starts correctly (Start()
returns true
) however no output or error data is ever sent from the process:
- If I try to
Read
from theStandardOutput
orStandardError
the read just hangs. - If I instead subscribe to
OutputDataReceived
andErrorDataReceived
they never fire.
started with ProcessStartInfo("Python.exe")
I only see output from python once I kill it by sending Ctrl + C
(sent after pressing Enter
at the end of the line asd
, hence the 3rd line).
To clarify, I want to see "2" sent from python (either from Reading the StandardOutput stream, or in the data event), after I pressed enter after entering "1 + 1".
Why is no data being sent to the Standard outputs and no events raised whilst python is executing? Is it possible to have this work as I want it (so that response from python is given immediately).
Additional
If instead I start cmd.exe
then everything is fine and I can use the form as I would a regular command prompt. So to some extent my application is working.
However if I try to start python using this form, then it hangs until I kill the process by sending Ctrl + C
as before:
started with ProcessStartInfo("cmd.exe")
Evidently the standard input is getting to python (since the line number reported by the python error message changes depending on how many lines before it)
I am using:
Python 3.5.2 |Anaconda 4.2.0 (64-bit)| (default, Jul 5 2016, 11:41:13) [MSC v.1900 64 bit (AMD64)] on win32
Again: Why does python not send any data to standard out or cause any events to be raised until it dies? How can I make it?
Code
Some of this follows the pattern of www.codeproject.com/Articles/335909/Embedding-a-Console-in-a-C-Application
Setup
var processStartInfo = new ProcessStartInfo("python.exe");
processStartInfo.UseShellExecute = false;
processStartInfo.ErrorDialog = false;
processStartInfo.RedirectStandardError = true;
processStartInfo.RedirectStandardInput = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.CreateNoWindow = true;
process = new Process();
process.StartInfo = processStartInfo;
bool processStarted = process.Start();
outputReader = process.StandardOutput;
errorReader = process.StandardError;
inputWriter = process.StandardInput;
// start reading from standard output
outputWatchWorker.WorkerSupportsCancellation = true;
outputWatchWorker.DoWork += (o, e) =>
{
while (!outputWatchWorker.CancellationPending)
{
int nChars = 0;
string str;
char[] buffer = new char[1024];
nChars = outputReader.Read(buffer, 0, buffer.Length);
if (nChars > 0)
{
str = new string(buffer, 0, nChars);
Invoke((MethodInvoker)(() =>
{
WriteOutput(str, Color.Black);
}));
}
}
};
outputWatchWorker.RunWorkerAsync();
// I've removed an almost identical block of code for errorWatchWorker to read from the error stream
KeyDown event of RichTextBox
// some extra code here I haven't included to prevent deletion of stuff printed from standard out
// just checks the cursor position and then suppresses the event if certain keys are pressed
// I can provide if it will help
if (e.KeyCode == Keys.Return)
{
string input = textConsole.Text.Substring(inputStart, textConsole.SelectionStart - inputStart);
lastInput = input;
inputStart = textConsole.SelectionStart + 1; // inluding the new line
inputWriter.WriteLine(input);
inputWriter.Flush();
if (string.IsNullOrWhiteSpace(input)) e.SuppressKeyPress = true;
}
WriteOutput
public void WriteOutput(string output, Color color)
{
if (string.IsNullOrEmpty(lastInput) == false &&
(output == lastInput || output.Replace("\r\n", "") == lastInput))
return;
Invoke((Action)(() =>
{
// Write the output.
textConsole.SelectionColor = color;
textConsole.AppendText(output);
inputStart = textConsole.SelectionStart;
}));
}
The click listener of the "Ctrl + C" button to send Ctrl+C
just does
inputWriter.WriteLine("\x3");
based on this question How do I send ctrl+c to a process in c#?