Опитвам се да направя просто приложение, което комуникира с конзолна програма, която работи по начин, подобен на класическия cmd.exe на Windows.
Очаква се последователността на изпълнение да бъде следната:
- Настройте фонов работник и го стартирайте, за да прочете изхода.
- Стартирайте процеса и изчакайте за кратко.
- Подайте команда към Process.StandardInput
- Изчакайте няколко секунди
- Излезте от фоновата нишка и убийте процеса
Но това, което се случва, не е според очакванията, защото:
- Не целият изход на cmd.exe се чете. Дори когато се изключи стъпката, която записва в StandardInput
- Низът не е предаден на командата от StandardInput, въпреки че има AutoFlush = true.
Опитах също Process.StandardInput.Flush(), който не прави нищо. Опитах се също да му предам низ, подплатен с интервали с дължина, по-голяма от 4096, което е размерът на буфера без резултати. Нищо не се случва!! Защо?
Опитах това на dot net 4.5.2 и 4.7.1
Подобни въпроси съществуват тук, тук и тук, но нито един от отговорите не работи. Други са внедрени на друг език. например Java, Delphi и др
Това е опростена версия на моя код:
BackgroundWorker _outputWorker;
Process _process;
StreamWriter _inputWriter;
TextReader _outputReader;
void Main()
{
_outputWorker = new BackgroundWorker { WorkerSupportsCancellation = true };
_outputWorker.DoWork += OnOutputWorkerDoWork;
_outputWorker.RunWorkerAsync();
_process = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = string.Empty,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Directory.GetCurrentDirectory(),
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
Console.WriteLine("Starting...");
if (!_process.Start()) return;
_inputWriter = _process.StandardInput;
_inputWriter.AutoFlush = true; // does nothing
_outputReader = TextReader.Synchronized(_process.StandardOutput);
// You can exclude this step too and still not get the expected output
Thread.Sleep(500);
_inputWriter.WriteLine("dir");
_inputWriter.Flush(); // does nothing, private field carpos = 0
_inputWriter.BaseStream.Flush(); // does nothing, private field carpos = 5 which is equal to length of "dir" command + 2 characters (NewLine \r\n)
//_inputWriter.WriteLine("dir".PadLeft(4096)); // does nothing
// also closing the stream does nothing and does something that I can't afford which is closing the exe
// _inputWriter.Close();
//
_process.WaitForExit(5000);
_outputWorker.CancelAsync();
_process.Kill();
Console.WriteLine("Done");
}
void OnOutput(string data)
{
// never mind thread safety for now. It's just a single line static call
Console.WriteLine(data);
}
void OnOutputWorkerDoWork(object sender, DoWorkEventArgs e)
{
const int BUFFER_SIZE = 4096;
StringBuilder builder = new StringBuilder(BUFFER_SIZE);
while (!_outputWorker.CancellationPending)
{
/*
* It'll keep on running untill it's canceled to reduce thread costs
* because the program will run different executables sequentially which
* all are similar to cmd.exe.
*/
try
{
// Simplified version without locking
if (_outputReader == null) continue;
TextReader reader = _outputReader;
if (reader.Peek() < 1) continue;
char[] buffer = new char[BUFFER_SIZE];
do
{
int count = reader.Read(buffer, 0, buffer.Length);
if (count > 0) builder.Append(buffer, 0, count);
}
while (reader.Peek() > 0);
}
catch (Exception ex)
{
// handle exception in debug mode
Console.WriteLine(ex.Message); // no exception generated!
continue;
}
if (builder.Length == 0) continue;
OnOutput(builder.ToString());
builder.Length = 0;
}
if (!IsWaitable(_process)) return;
try
{
if (_outputReader == null) return;
TextReader reader = _outputReader;
if (reader.Peek() < 1) return;
char[] buffer = new char[BUFFER_SIZE];
do
{
int count = reader.Read(buffer, 0, buffer.Length);
if (count > 0) builder.Append(buffer, 0, count);
}
while (reader.Peek() > 0);
}
catch (Exception ex)
{
// handle exception in debug mode
Console.WriteLine(ex.Message); // no exception generated!
return;
}
if (builder.Length > 0) OnOutput(builder.ToString());
}
bool IsWaitable(Process thisValue)
{
return thisValue != null && !thisValue.HasExited;
}
Не мога да използвам Process.BeginOutputReadLine, защото чака NewLine да присъства в потока, което не се случва за последния ред на изхода. Вижте това и това за подробности
Получавам Microsoft Windows [Версия xxxxx]
(c) линия за авторски права
И програмата не показва подканата на командния ред, освен ако не идва повече резултат от процеса, който съдържа NewLine
Точките на интерес са:
1. Защо този код не чете целия изход както трябва до края?
След като опитах много неща, изглежда, че буферът не съдържа повече текст за четене и в потока изглежда липсват някои данни от оригиналния изход на изпълнимия файл. Странното е, че се случва случайно. Понякога получавам втория ред, а понякога не. Във всеки случай никога не получих резултата, който трябва да е резултат от подаването на вътрешни команди към cmd процеса.
2. StandardInput (StreamWriter) всъщност изчиства буфера (или си мисли, че го прави) и настройва своите charpos на нула, докато BaseStream все още има своето charpos поле = [дължина на буфера]. Защо се случва това?
Всякакви идеи за това какво може да е проблемът или заобиколно решение ще бъдат много оценени.