Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Delphi Consolenausgaben LIVE abfangen (https://www.delphipraxis.net/186289-consolenausgaben-live-abfangen.html)

DelTurbo 20. Aug 2015 13:06

Consolenausgaben LIVE abfangen
 
Hi,
Ich versuche seit Stunden den Output eines Commandlineprogramms LIVE abzufragen.
ALLE Beispiele die ich gefunden habe, zeigen die Ausgabe erst an wenn das CommandlineProgramm beendet wird. Also Quasi fertig ist.

Was ich so gefunden habe im Netz ist so ziemlich alles auf dem Beispiel aufgebaut. Das Problem ist das er bei ReadFile sollange wartet bis das SlavePrg fertig ist.

http://delphi.wikia.com/wiki/Capture...altime_To_Memo oder http://stackoverflow.com/questions/2...009-with-vista

Hat jemand eine Idee wie man es "richtig" macht?

Vielen dank im Voraus

Uwe Raabe 20. Aug 2015 13:38

AW: Consolenausgaben LIVE abfangen
 
Zitat:

Zitat von DelTurbo (Beitrag 1312872)
Hi,
Ich versuche seit Stunden den Output eines Commandlineprogramms LIVE abzufragen.

Guckst du hier: Getting output from a shell/dos app into a Delphi app (2. Beispiel in meiner Antwort)

DelTurbo 20. Aug 2015 13:52

AW: Consolenausgaben LIVE abfangen
 
Hi,
erstmal danke für die Antwort. Allerdings habe ich das auch schon probiert. Ich habe viele Probiert. Ich wollte nur nicht alle URLs hier Posten.
Das bleibt leider auch hier stehen und wartet bis das CmdLine Programm zu ende ist.


Delphi-Quellcode:
WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);


Kurz zu meinem System:
Windows 7 x64 (Ich hoffe doch es hat nix mit dem 64bit System zu tun.)
Delphi 2007

EDIT: Ich habe es gerade auf 32Bit versucht. Klappt auch nicht.

DelTurbo 20. Aug 2015 14:11

AW: Consolenausgaben LIVE abfangen
 
Was ich auch gesehen habe ist folgender Code. Das ist damit das Programm nicht hängen bleibt, und in der CaptureBar nicht steht "Keine Rückmeldung". Ich denke mal alleine das ist doch ein hinweis darauf das ReadFile an der stelle einfach wartet.

Delphi-Quellcode:
    if CreateProcess(nil,
           PChar(DosApp),
           @Security,
           @Security,
           true,
           NORMAL_PRIORITY_CLASS,
           nil,
           nil,
           start,
           ProcessInfo)
    then
    begin
     repeat
      Apprunning := WaitForSingleObject
                   (ProcessInfo.hProcess,100) ;
      Application.ProcessMessages;
     until (Apprunning <> WAIT_TIMEOUT) ;
      Repeat
        BytesRead := 0;
        ReadFile(ReadPipe,Buffer[0], ReadBuffer,BytesRead,nil) ;
        Buffer[BytesRead]:= #0;
        OemToAnsi(Buffer,Buffer) ;
        AMemo.Text := AMemo.text + String(Buffer) ;
      until (BytesRead < ReadBuffer) ;
   end;

BUG 20. Aug 2015 14:29

AW: Consolenausgaben LIVE abfangen
 
Bist du dir sicher, dass das aufgerufene Programm nicht einfach alles puffert und am Ende komplett ausgibt.

DelTurbo 20. Aug 2015 14:38

AW: Consolenausgaben LIVE abfangen
 
Ja, 100%. Ich habe ein extra zum Testen ein kleines Consolen Programm gemacht.

Delphi-Quellcode:
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  i  :Integer;
begin
  try
  { TODO -oEntwickler -cKonsole Main : Hier Code einfügen }
    for i:=0 to 20 do begin
      Writeln(i);
      Sleep(1000);
    end;
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.
EDIT: Ich habe sogar das versucht, bis ich gemerkt habe das es für Lazarus ist :(
http://www.delphipraxis.net/1030919-post5.html

nahpets 20. Aug 2015 14:54

AW: Consolenausgaben LIVE abfangen
 
Hallo,

probiere es bitte mal mit dem anhängenden Testprojekt.

In des Unit sbPipes ist eine Komponenten, die eigentlich die von Dir gewünschte Aufgabe erledigen sollte.

Für die Ausgabe kannst Du ihr ein TMemo zuweisen, dann übernimmt die Komponente die Ausgabe, eine Stringliste, auch dann übernimmt die Komponente die Ausgabe in die Stringliste oder Du nutzt das Ereignis OnOutput, dann musst Du selbst für die Ausgabe sorgen.

Im Ereignis OnNotify kannst Du Meldungen der Komponente ausgeben, den Exitcode Deines aufzurufenden Programmes und die Anzahl der gelesenen Bytes in der Ausgabe.

Hab's Beispielprogramm gerade mal getestet, unter Windows XP mit Delphi 7 läuft es.

hathor 20. Aug 2015 15:06

AW: Consolenausgaben LIVE abfangen
 
CaptureConsoleOutput

Delphi-Quellcode:
procedure CaptureConsoleOutput(const ACommand, AParameters: String; AMemo: TMemo);
const CReadBuffer = 2400;
var
   saSecurity: TSecurityAttributes;
   hRead: THandle;
   hWrite: THandle;
   suiStartup: TStartupInfo;
   piProcess: TProcessInformation;
   pBuffer: array[0..CReadBuffer] of ANSIChar;
   dRead: DWord;
   dRunning: DWord;
begin
   saSecurity.nLength := SizeOf(TSecurityAttributes);
   saSecurity.bInheritHandle := True;
   saSecurity.lpSecurityDescriptor := nil;

   if CreatePipe(hRead, hWrite, @saSecurity, 0) then
   begin
     FillChar(suiStartup, SizeOf(TStartupInfo), #0);
     suiStartup.cb := SizeOf(TStartupInfo);
     suiStartup.hStdInput := hRead;
     suiStartup.hStdOutput := hWrite;
     suiStartup.hStdError := hWrite;
     suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
     suiStartup.wShowWindow := SW_HIDE;

     if CreateProcess(nil, PChar(ACommand + ' ' + AParameters), @saSecurity,
       @saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess)
       then
     begin
       repeat
         dRunning := WaitForSingleObject(piProcess.hProcess, 100);
         Application.ProcessMessages();
         repeat
           dRead := 0;
           ReadFile(hRead, pBuffer[0], CReadBuffer, dRead, nil);
           pBuffer[dRead] := #0;
           OemToAnsi(pBuffer, pBuffer);
           AMemo.Lines.Add(String(pBuffer));
         until (dRead < CReadBuffer);
       until (dRunning <> WAIT_TIMEOUT);
       CloseHandle(piProcess.hProcess);
       CloseHandle(piProcess.hThread);
     end;
     CloseHandle(hRead);
     CloseHandle(hWrite);
   end;
end;

procedure TForm2.Button1Click(Sender: TObject);
begin
CaptureConsoleOutput('cmd.exe', '/c dir c: /s', Memo1);
end;

DelTurbo 20. Aug 2015 15:17

AW: Consolenausgaben LIVE abfangen
 
@nahpets, leider klappt es nicht. Beim laden kommt schon das er TTriggerPipe nicht findet. Ich habe es dann in Privat reingemacht und beim Button1Click erstellt. Dann kann ich es übersetzen und starten, aber leider kommt da nichts.

Das Programm, was in edKommandoZeile steht, wird nicht gestartet. :(

@hathor, das habe ich auch gefunden und probiert. Leider bleibt es wie alle anderen bei ReadFile stehen bis das aufgerufene Programm fertig ist.

Gruß und danke schonmal

hathor 20. Aug 2015 15:28

AW: Consolenausgaben LIVE abfangen
 
Liste der Anhänge anzeigen (Anzahl: 1)
Bei mir läuft's mit XE7 und WIN8.1.

Mach doch bitte ein COPY&PASTE und teste es!

Hast Du die vertikale Scrollbar beim Memo aktiviert?

Ich habe mal die EXE angefügt.

BUG 20. Aug 2015 15:30

AW: Consolenausgaben LIVE abfangen
 
Ich glaube immer noch an einen Puffer. Ein letzter Test, dann lass ich dich damit in Ruhe :stupid:

Kannst du ein Programm probieren, das garantiert nicht puffert, indem du direkt die WinAPI benutzt und nach jeder Ausgabe flushst?
Code:
GetStdHandle(STD_OUTPUT_HANDLE)
WriteFile(...)
FlushFile(...)

nahpets 20. Aug 2015 15:46

AW: Consolenausgaben LIVE abfangen
 
Da TriggerPipe eine Komponente ist, solltest Du sie eventuell installieren, dann kann Delphi sie auch finden.

Wenn allerdings alle Routinen, die Du probiert hast, Dein Programm nicht starten, hast Du vermutlich kein Problem mit Delphi und der Verarbeitung von Pipes, sondern ein vollkommen anderes.
Wenn
Code:
cmd.exe /c dir c:\
nicht gestartet wird, erscheint mir das schon sehr seltsam.

Bei meiner Routine steht in der Caption des Formulares nach dem Aufruf auch der Exitcode. Was steht denn bei Dir dort?
Bei dem cmd von oben sollte der ExitCode 0 sein, sonst wurde der Prozess erst garnicht gefunden oder hat sich (aus welchen Gründen auch immer) verabschiedet.

Ändere bitte mal in meinem Teil die Routine TForm1.Button1Click ab:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
begin
  meOutput.Lines.Clear;
  // Das kann ein bisserl dauern...
  TriggerPipe.CommandLine := 'cmd.exe /c dir c:\ /s';
  if not TriggerPipe.Execute then begin
    ShowMessage(Format('Rückgabewert: %d',[TriggerPipe.ReturnCode]));
  end else begin
    ShowMessage('Eigentlich sollte das hier alles Ordnunggemäß funktioniert haben.');
  end;
end;
Was bekommst Du hier zu sehen?

Das über TriggerPipe gestartete Programm sollte im Taskmanager zu sehen sein, ist dem so?

Was ist das denn für ein Programm, das Du da aufrufen möchtest?

Steht es im gleichen Verzeichnis, wie das von Dir erstellte Programm?
Oder gibst Du den Programmname mit vollständigem Pfad an?
Oder ist das Programm über den Suchpfad von Windows zu erreichen?
Startet das Programm, wenn Du in dem Verzeichnis, in dem die von Dir erstellte Exe liegt, über die Konsole aufrufst?
Wenn nein, welche Meldung wird ausgegeben?
Was passiert, wenn Du eine Kommandozeile aufmachst, nach c:\ ins Hauptverzeichnis wechselst und das Programm dann dort aufrufst? Klappt das? Wenn ja, bitte Screenshot hier zur Verfügung stellen, eventuell kann man daran erkennen, ob das Programm überhaupt StdIn und StdOut von Windows nutzt. Wenn nicht, hast Du mit den hier besprochenen Routinen keine Chance.

DelTurbo 20. Aug 2015 16:02

AW: Consolenausgaben LIVE abfangen
 
Liste der Anhänge anzeigen (Anzahl: 1)
@hathor,

Copy und Past gemacht. Rufe ich "cmd.exe /c dir c:\ " auf. Dann wird es im Memo angezeigt. Das ding ist ja sehr sehr schnell durch.

Rufe ich meine Consolenanwendung auf, dann sehe ich das erst nach 21 Sekunden wenn die zu ende ist. Ich hänge die mal hier dran. Ich gehe doch recht in der annahme das ich eigentlich jede zahl sofort sehen müsste, oder?

Delphi-Quellcode:
CaptureConsoleOutput('e:\Project1.exe', '', Memo1);
@nahpets, leider finde ich nix unter TriggerPipe und Komponente. Es ist nicht so das ALLE was ich probiert habe nicht startet. Nur das mit dem TriggerPipe. Alles andere startet und zeigt dann dieses verhalten was ich beschreibe bzw. beschrieben habe.

@all, "cmd.exe /c dir c:\ " läuft immer. Rufe ich diverse andere Sachen auf, dann wie gesagt wartet ReadFile bis es fertig ist.

Mittlerweile Probiert unter Win7 64bit, Win732bit, WinXP.

Und nochmal danke für so viel Hilfe.
nahpets vielleicht kannst du mir die Komponente mal zukommen lassen wenn es Free ist.

EDIT: Ein dir d: /s kann ich nun wirklich sehen. Warum ich die anderen Programme nicht sehe verstehen ich grade nicht wirklich

nahpets 20. Aug 2015 16:05

AW: Consolenausgaben LIVE abfangen
 
Die Komponente befindet sich in der Datei sbPipes.pas, die ich oben in die Zip-Datei gepackt habe.

DelTurbo 20. Aug 2015 16:11

AW: Consolenausgaben LIVE abfangen
 
Das passiert wenn man versucht alles so schnell wie möglich zu testen um euch eine schnelle rückmeldung zu geben.

Nun passe ich besser auf.

Wie gesagt, eine irre lange dir /s Ausgabe sehe ich. Das normale "dir" war einfach zu kurz zum testen. Deswegen habe ich das kleine ConsolenProgramm gemacht.

nahpets 20. Aug 2015 16:18

AW: Consolenausgaben LIVE abfangen
 
Also, habe mit meiner Project1.exe Deine Project1.exe aufgerufen und siehe da, es funktioniert:thumb:

Aber: Die Ausgabemenge Deiner Exe ist so gering, dass sich die Ausgabe erst lohnt, wenn Dein Programm beendet ist und dann noch der Rest der Ausgabe gelesen wird.

Die ganzen Pipes geben nicht sofort jedes einzelne Zeichen aus, sondern "sammeln" immer ein bisserl.

Hier sind es
Delphi-Quellcode:
procedure CaptureConsoleOutput(const ACommand, AParameters: String; AMemo: TMemo);
const CReadBuffer = 2400;
eben 2400 Byte. Immer wenn die voll sind, gibt es eine Ausgabe, beim Programmende dann den Rest.

Bei meiner Komponente sind es
Delphi-Quellcode:
const
  cPipeSize         = 4096;
eben 4 Kilobyte.

Gib einfach mal für den Test was mehr aus oder schneller und dafür länger zählen...

Uwe Raabe 20. Aug 2015 17:05

AW: Consolenausgaben LIVE abfangen
 
Keine Ahnung, ob es das tut was du willst, aber einen Versuch ist es vielleicht wert: DOSCommand

nahpets 20. Aug 2015 17:12

AW: Consolenausgaben LIVE abfangen
 
Vermutlich eher nicht, die lesen immer 1024 Byte, das "Problemprogramm" liefert aber nur (wenn ich das richtig mitbekommen habe) 74 Byte, diese aber in 10 3-Byte-Happen und 11 4-Byte-Happen über gefühlte 5 Minuten verteilt (nein, es sind 20 bis 21 Sekunden).

DelTurbo 21. Aug 2015 07:49

AW: Consolenausgaben LIVE abfangen
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1312913)
Keine Ahnung, ob es das tut was du willst, aber einen Versuch ist es vielleicht wert: DOSCommand

Leider bekomme ich das nicht so geändert das es auf meinem 2007 läuft. Schade.

Nochmal kurz zum Hintergrund. Ich starte eine Konsolen Anwendung die etwas berechnet. Die gibt alle (ich glaube 500ms) einen . (Punkt) aus. Ich dachte es kann ja nicht so schwer sein den Punkt in einem Label oder sonst wo anzuzeigen.
Deswegen gibt mein "DemoProgramm" auch so wenig und langsam etwas aus.

Leider habe ich mich da wohl geirrt. Ich danke allen die mir geholfen haben. Find ich echt super. Nun werde ich "gemütlich" und ohne Hektik mir mal das sbPipes ansehen.

Nochmal, danke an euch!! :thumb:

DelTurbo 21. Aug 2015 08:27

AW: Consolenausgaben LIVE abfangen
 
@nahpets,
ich habe nun sbPipes unter Delphi 7 probiert. Wenn ich meine Demo oder aber das Berechnungsprogramm starte, passiert das selbe wie immer.

In der CaptureBar steht die ganze zeit Programm läuft noch... Es kommen keinerlei ausgaben. Ist das Programm zu ende dann wird sofort alles angezeigt.

HolgerX 21. Aug 2015 08:29

AW: Consolenausgaben LIVE abfangen
 
Hallo..

Es liegt tatsächlich an der Console mit Pipes.
Wenn deine Konsolenanwendung ein WriteLN macht, dann wird dieses erstmal von StdOut gepuffert und dann komplett an die Pipe geFlusht..

Würde in deiner Konsolenanwendung nach jedem
WriteLN('.');
ein
Flush(Output);
gemacht werden, dann wird die Ausgabe direkt in die Pipe geschrieben und könnte einzeln mit ReadFile gelesen werden.

Habe hier mal dein Testprogramm nachgebaut, mal 0-20 ohne Flush, mal 0-20 mit Flush..
Und siehe da, die ersten 20 kamen erst zusammen, die weiteren 20 schön einzeln.

http://www.delphibasics.co.uk/RTL.asp?Name=Flush

Hier wirds (mit einem File) beschrieben, aber eine Pipe ist für MS ja auch nur ein File.

DelTurbo 21. Aug 2015 08:44

AW: Consolenausgaben LIVE abfangen
 
Hi,
danke für die Antwort. Leider ist das Berechnungs Programm nicht von mir und es sind keine Quellen vorhanden. In meine Demo könnte ich das einbauen.

Letzte vielleicht sogar dumme frage: Es ist wohl nicht möglich dem Programm ein Flush "unterzujubeln"? Das ich während dem warten alle 300ms ein Flush dahin schicke?

Wenn nicht, naja heute Nacht habe ich mich schon eigentlich damit abgefunden das es nicht geht. Ich werde eine Sanduhr anzeigen :wink:

HolgerX 21. Aug 2015 09:18

AW: Consolenausgaben LIVE abfangen
 
Hab mal gegooglt..

Leider gibt es keine Möglichkeiten dieses Flush extern anzustoßen.
Dieses Puffern wird direkt durch die libc oder Phyton gehandelt und somit auch in Windows.

Wenn die Anwendung nicht mit deaktiviertem Buffer für stdout erstellt wurde (oder Flush()) dann wird immer gepuffert..

Selbst der Kernel von Windows puffert...

Den einzigen Workarround den ich gefunden habe, ist eine simulierte TTY-Schnittstelle stat Pipe zu verwenden.
Aber wie das geht.. K.A. ;)

DelTurbo 21. Aug 2015 09:26

AW: Consolenausgaben LIVE abfangen
 
Schade, war so eine Idee. Trotzdem vielen dank. Dann kommt nun die Sanduhr :)

nahpets 21. Aug 2015 10:46

AW: Consolenausgaben LIVE abfangen
 
Hallo,

wenn Du Dir die sbPipes anschaust, wirst Du feststellen, dass es dort ein Ereignis onNotify gibt, gib' dort doch mal in das Memo jeweils die Uhrzeit aus...
Delphi-Quellcode:
  Caption := Format('%s - Bytes Read: %d - Exit-Code: %d',[sMessage,BytesRead,ExitCode]);
  meOutput.Lines.Add(Format('%s - %s',[FormatDateTime('hh:mm:ss.zzz',now),sOutput]);
und Du wirst feststellen, dass Du mit Meldungen zugeschüttest wirst. Wenn's denn um 'nen Punkt geht, dann schreib ihn hier doch selber ;-)
Du bekommst permanent Meldungen, dass Dein Prozess noch läuft, für die Fortschrittsanzeige kannst Du doch selbst sorgen. Wären da jetzt auf der Konsole wichtige Ausgaben zu erwarten, ok wäre ein Problem, aber nur um deren Fortschrittspunkte zu sehen, da ist der Aufwand übertrieben. Punkte in ein Label malen kannst Du selbst, da brauchst Du keine Vorlage von der Konsole ;-)


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:33 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz