core3.1: Große Datenmengen in StdIn und StdOut mit Process

  • C#

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von Bluespide.

    core3.1: Große Datenmengen in StdIn und StdOut mit Process

    Guten Tag,

    Ich versuche, mittels Process ein Programm (namentlich Inkscape) zu starten, und via StandardInput und StandardOutput Daten damit auszutauschen.

    Mit folgendem Befehl (unter Linux (Ubuntu) mit bash) funktioniert das:
    cat assets/resources/svg/input.svg | inkscape --export-type="png" --pipe --export-filename=- 2> /dev/null >out/out.png.
    (Ist nach unter einer halben Sekunde fertig)

    Kurz aufgeschlüsselt:
    cat liest die Datei assets/resources/svg/input.svg und schickt sie an den StdIn von inkscape, welches auf seinem StdOut wiederrum Daten ausgibt, die dann in out/out.png geschrieben werden. StdErr wird hierbei einfach verworfen.

    Ich habe (mit meinen doch sehr eingeschränkten Kentnissen zum Thema Process) dann folgenden Test-Code aufgebaut,
    die Betonung liegt auf Test

    C#-Quellcode

    1. using (var reader = new StreamReader(@"assets/resources/svg/input.svg"))
    2. using (var memory = new MemoryStream())
    3. using (var memoryWriter = new StreamWriter(memory))
    4. using (var proc = new Process
    5. {
    6. StartInfo = new ProcessStartInfo("inkscape", "--export-type=\"png\" --export-filename=- --pipe")
    7. {
    8. RedirectStandardOutput = true,
    9. RedirectStandardInput = true
    10. }
    11. })
    12. {
    13. proc.Start();
    14. proc.BeginOutputReadLine();
    15. //Datei einlesen und an Programm übergeben
    16. proc.StandardOutput.DiscardBufferedData();
    17. await proc.StandardInput.WriteAsync(await reader.ReadToEndAsync());
    18. await proc.StandardInput.FlushAsync();
    19. proc.WaitForExit(5_000); //Max 5 Sekunden
    20. //Bis hierher kommen wir noch
    21. await memoryWriter.WriteAsync(await proc.StandardOutput.ReadToEndAsync());
    22. //Hierher kommen wir nicht....
    23. }
    24. }


    Das Programm scheint jedoch nie zu beenden, und gibt keine Daten im StdOut aus.
    Nach einiger Zeit auf StackOverflow Recherche, mit dem Zwischenergebnis "Wenn ich den Stream nicht lese gibt es einen Deadlock" bin ich dann bei folgendem Code gelandet (Auch wenn ich nicht verstehe, was das AutoResetEvent hier helfen soll - werde mal Nachforschen):
    Spoiler anzeigen

    C#-Quellcode

    1. using (var reader = new StreamReader(@"assets/resources/svg/input.svg"))
    2. using (var memory = new MemoryStream())
    3. using (var memoryWriter = new StreamWriter(memory))
    4. using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
    5. {
    6. using (Process process = new Process
    7. {
    8. StartInfo = new ProcessStartInfo("inkscape", "--export-type=\"png\" --export-filename=- --pipe")
    9. {
    10. RedirectStandardOutput = true,
    11. RedirectStandardInput = true
    12. }
    13. })
    14. {
    15. try
    16. {
    17. process.OutputDataReceived += (sender, e) =>
    18. {
    19. if (e.Data == null)
    20. {
    21. outputWaitHandle.Set();
    22. }
    23. else
    24. {
    25. memoryWriter.WriteLine(e.Data);
    26. }
    27. };
    28. process.Start();
    29. process.StandardInput.Write(reader.ReadToEnd());
    30. process.StandardInput.Flush();
    31. process.BeginOutputReadLine();
    32. if (process.WaitForExit(5_0000))
    33. {
    34. // Kein Timeout
    35. logger.Info("LÄUFT");
    36. }
    37. else
    38. {
    39. // Timeout
    40. logger.Info("TIMEOUT");
    41. }
    42. }
    43. finally
    44. {
    45. outputWaitHandle.WaitOne(100);
    46. }
    47. }
    48. memoryWriter.Flush();
    49. memory.Position = 0;
    50. //Hier stream lesen
    51. }

    Hier passiert jedoch genau das gleiche... das Programm eendet nicht und gibt nichts in StOut.

    Ich habe mich nun entschieden, diesen gesammten Code einfach in die Tonne zu hauen, und es neu zu machen.

    Kann/Möchte mir jemand erläutern, wie ich dies umsetzen kann, oder mir sagen, was genau ich falsch Mache :D

    Grüße
    こんにちわ
    Achte beim stellen von Fragen auf eine genaue Fragestellung, mir passiert das selbst häufig, andere können dir dann nicht so gut helfen.
    @LuaX Sieh Dir folgendes Beispiel an:
    TextBox, RichTextBox, 3 Button (Connect, Send, Clear).
    Es wird hidden cmd.exe gestartet, der Text der TextBox wird an cmd übertragen,
    die Antwort von cmd wird in der RichTextBox ausgegeben.
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Diagnostics;
    3. using System.Globalization;
    4. using System.Text;
    5. using System.Windows.Forms;
    6. namespace WindowsFormsApplication1
    7. {
    8. public partial class Form1 : Form
    9. {
    10. Process cmd;
    11. public Form1()
    12. {
    13. this.InitializeComponent();
    14. }
    15. private void btnConnect_Click(object sender, EventArgs e)
    16. {
    17. this.StartProcess();
    18. }
    19. private void btnSend_Click(object sender, EventArgs e)
    20. {
    21. this.cmd.StandardInput.WriteLine(this.textBox1.Text);
    22. }
    23. private void StartProcess()
    24. {
    25. Encoding enc = Encoding.GetEncoding(CultureInfo.CurrentUICulture.TextInfo.OEMCodePage);
    26. this.cmd = new Process();
    27. this.cmd.StartInfo.FileName = "cmd.exe";
    28. this.cmd.StartInfo.WorkingDirectory = "";
    29. this.cmd.StartInfo.CreateNoWindow = true;
    30. this.cmd.StartInfo.UseShellExecute = false;
    31. this.cmd.StartInfo.RedirectStandardOutput = true;
    32. this.cmd.StartInfo.RedirectStandardInput = true;
    33. this.cmd.StartInfo.RedirectStandardError = true;
    34. this.cmd.StartInfo.StandardOutputEncoding = enc;
    35. this.cmd.StartInfo.StandardErrorEncoding = enc;
    36. this.cmd.OutputDataReceived += new DataReceivedEventHandler(this.WriteProcessOutput);
    37. this.cmd.Start();
    38. this.cmd.BeginOutputReadLine();
    39. }
    40. void WriteProcessOutput(object sender, DataReceivedEventArgs e)
    41. {
    42. if (e.Data != null)
    43. {
    44. Action<string> action = new Action<string>(this.AppendText);
    45. this.BeginInvoke(action, e.Data);
    46. }
    47. }
    48. private void AppendText(string txt)
    49. {
    50. this.richTextBox1.AppendText(txt + Environment.NewLine);
    51. }
    52. private void btnClear_Click(object sender, EventArgs e)
    53. {
    54. this.richTextBox1.Clear();
    55. }
    56. }
    57. }

    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Guten Tag,

    Dein Beispielcode funktioniert zwar, funktioniert jedoch mit Inkscape genauso wenig.
    Wie ich jetzt allerdings erkannt habe, funktioniert es, wenn ich nachdem ich die Daten geschrieben habe den InputStream schließe.

    Jetzt stehe ich allerdings vor einem neuen Problem: Die Ausgabe in StdOut ist ein PNG-Bild. wenn ich dieses herauslese und in eine Datei schreibe, bekomme ich jedoch eine kaputte Datei, die offensichtlich nicht richtig formattiert ist.

    Die Datei startet mit ï¿œPNG, sollte jedoch einfach nur mit PNG starten, ich bin am grübeln wo diese zusätzlichen Daten herkommen. Das führt sich fort durch die ganze Datei.
    こんにちわ
    Achte beim stellen von Fragen auf eine genaue Fragestellung, mir passiert das selbst häufig, andere können dir dann nicht so gut helfen.

    LuaX schrieb:

    Die Datei startet mit
    Sieht aus, als stüne da ein BOM oder so was (Kennung für Unicode-Reihenfolge).
    Spiele mal ein wenig mit dem Parameter Encoding (Variable enc).
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ja tatsächlich, durch ändern des Encoding funktioniert das ganze, jedoch ist die PNG immernoch nicht zu nutzen.

    Meine Recherchen haben mich dazu gebracht, das \r\n und \n von OutputDataReceived gleich behandelt wird, was auch der Fehler zu seien scheint. Da es keine Möglichkeit gibt, das zu prüfen, muss ich wohl auf den BaseStream ausweichen, wo ich jedoch wieder das Problem haben das dieser kein Ereignis bereitstellt, weswegen ich in einem Deadlock landen würde. Hier wäre, meiner Ansicht nach, eine "unsaubere" while mit Thread.Sleep() die einzige möglichkeit.
    こんにちわ
    Achte beim stellen von Fragen auf eine genaue Fragestellung, mir passiert das selbst häufig, andere können dir dann nicht so gut helfen.
    Hi

    Schaut so aus als wolltest Du ein SVG-Bild in ein PNG-Bild konvertieren. Das kannst Du auch mit Direct2D und dem Interface ID2D1DeviceContext5::CreateSvgDocument und dann weiter über die Windows Imaging Component (WIC) oder auch per GDI+ direkt konvertieren. Da brauchst kein externes Programm für. Das ganze funktioniert aber auch erst ab dem Windows 10 Creators Update.
    Mfg -Franky-
    @LuaX Das Problem besteht darin, dass der Stream denkt, es sei ein Text. Das wird so nicht funktionieren.
    Du musst zurück auf die Byte-Ebene, dann soll es gehen. Encoding ist auf Bytes nicht anwendbar.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @-Franky-
    Vielen Dank für einen Hinweis, aber ich habe vergessen zu erwähnen, das das ganze unter Linux laufen soll, nicht unter Windows :S .
    GDI+ wäre zwar unter Linux auch möglich, jedoch nur mit Umwegen, und ich habe mir sagen lassen, dass GDI eher zur langsameren Sorte gehört (nicht das ich hier Rekorde aufstellen wöllte).

    @RodFromGermany
    Ja das habe ich nun auch bemerkt, mit *putStream.BaseStream komme ich auch auf diese Ebene, jedoch zerbreche ich mir den Kopf darüber, wie ich das Elegant lösen kann. Der Stream verfügt über keine Ereignisse, was bedeutet das er regelmäßig geleert werden muss. Am simpelsten erscheint mir dies in einer Schleife.

    Jedoch glaube ich, das das "besser" geht, hier wird ja doch sehr oft der Stream abgefragt, auch wenn eventuell garkeine Daten da sind.

    Ich gebe offen zu das ich, wenn es um solche Sachen geht, kein Experte bin, bin also doch sehr erfreut wenn mir ein kleiner Hinweis in die richtige Richtung gegeben werden könnte.

    Pseudo-Code:

    Quellcode

    1. while(!process.HasExited())
    2. {
    3. buffer.Write(process.StandardInput.BaseStream.Read());
    4. }
    こんにちわ
    Achte beim stellen von Fragen auf eine genaue Fragestellung, mir passiert das selbst häufig, andere können dir dann nicht so gut helfen.
    Also mir kommt das gePipe ganz un-netmässig vor.
    Imo kannst du dem Ink-Ding einfach als Argumente das Input-File und das Output-File übergeben und gut - vlt. so:

    C#-Quellcode

    1. private static void CallInkscape() {
    2. using (var proc = new Process()) {
    3. var args = "--export-type=\"png\"";
    4. args += " --import-filename=\"assets/resources/svg/input.svg\"";
    5. args += " --export-filename=\"assets/resources/svg/output.png\"";
    6. proc.StartInfo = new ProcessStartInfo("inkscape") {
    7. Arguments = args
    8. };
    9. proc.Start();
    10. }
    11. }
    Wobei ich nicht weiss, ob --import-filename tatsächlich ein zulässiger KommandozeilenParameter von inkscape ist. Aber ich denke, da wird es einen geben, und dass man dessen richtige Bezeichnung herausfinden kann.
    Hallo,
    In der Tat gibt es diesen Paramter, doch möchte ich ihn nicht unbedingt nutzen, da die Daten nicht auf meinem Festspeicher gespeichert werden sollen. Für "normale" Anwendungen wäre das wahrscheinlich die beste Variante ^^
    こんにちわ
    Achte beim stellen von Fragen auf eine genaue Fragestellung, mir passiert das selbst häufig, andere können dir dann nicht so gut helfen.
    Also hat das jetzt funktioniert? Ich habe das auch mal kurz ausprobiert. Bei mir klappt das so:

    C#-Quellcode

    1. using (Process proc = new Process()) {
    2. proc.StartInfo = new ProcessStartInfo(@".\..\..\..\..\inkscape\bin\inkscape.exe", "--pipe --export-type=png --export-filename=-");
    3. proc.StartInfo.RedirectStandardInput = true;
    4. proc.StartInfo.RedirectStandardOutput = true;
    5. proc.StartInfo.UseShellExecute = false;
    6. proc.Start();
    7. using (FileStream fs = new FileStream(@".\Test.svg", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
    8. fs.CopyTo(proc.StandardInput.BaseStream);
    9. }
    10. proc.StandardInput.Close();
    11. using (FileStream fs = new FileStream(@".\Test.png", FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) {
    12. proc.StandardOutput.BaseStream.CopyTo(fs);
    13. }
    14. }