Laden...

Altes Consolen C++ Prog -> C# Prog, No Flush Problem

Erstellt von charlkef vor 19 Jahren Letzter Beitrag vor 19 Jahren 2.442 Views
C
charlkef Themenstarter:in
9 Beiträge seit 2005
vor 19 Jahren
Altes Consolen C++ Prog -> C# Prog, No Flush Problem

Hi All,

ich habe das Problem, dass ich ein altes Consolen-Programm (Messprogramm) habe, das mit VC++ 6.0 geschrieben wurde und von dem kein Code mehr existiert, von dem ich die STDOUT Ausgaben einlesen möchte, in "Echtzeit" 😉.

Ich bekomme die Daten aber erst wenn das Consolen-Programm terminiert ist,.
Wie bekomme ich die STDOUT genau so schnell wie wenn ich das Programm in einer
Consolen-DOS-Box laufen lasse ?

Als C# Code verwende ich ausschnittsweise folgende Sequenz:

process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
streamReaderCOUT = process.StandardOutput;

if (( streamReaderCOUT != null ) &&
(( cout_line = streamReaderCOUT.ReadLine ()) != null ))
{
textBox_target.AppendText ( cout_line + "\n" );
}

Aber das ReadLine gibt erst dann Daten raus wenn die C-Consolen-Exe terminiert ist, und das ist dann leider zu spät. Wenn ich eine DummyCConsolen-Exe schreibe mit printf und flush funktioniert es, ohne flush nicht.

[ Ansatz 1 ]
Also dachte ich einen Disassember zu nehmen und von printf ( * auf fprintf ( sdterr, * zu patchen, den laut Doku sollte stderr sofort nach \n geflush werden aber in DummyDummyC++Consolen-Exe hat das nicht funktioniert.

[ Ansatz 2 ]
unter
http://support.microsoft.com/default.aspx?scid=kb;en-us;190351
steht leider :
NOTE: Child processes that use such C run-time functions as printf() and fprintf() can behave poorly when redirected. The C run-time functions maintain separate IO buffers. When redirected, these buffers might not be flushed immediately after each IO call. As a result, the output to the redirection pipe of a printf() call or the input from a getch() call is not flushed immediately and delays, sometimes-infinite delays occur. This problem is avoided if the child process flushes the IO buffers after each call to a C run-time IO function. Only the child process can flush its C run-time IO buffers. A process can flush its C run-time IO buffers by calling the fflush() function.

Also ist scheinbar ? von EXTERN der Buffer nicht explizit zu flushen.

[ Ansatz 3 ]
Es muss aber gehen, denn wenn ich es in einer Consolen-DOS-Box laufen lasse, wird auch ohne explizites internes flush die STDOUT sofort in "Echtzeit" ausgegeben.

Aber wie ??

Grüsse
Charlkef

49.485 Beiträge seit 2005
vor 19 Jahren

Hallo charlkef,

ich kann dir leider wenig Hoffnung machen. Ich denke auf die Aussagen von Knowledgebase-Artikeln kann man sich ziemlich verlassen.

Auch [Ansatz 3] ist kein Argument, weil ja die C-Bibliothek weiß, ob sie auf die Console ausgibt oder in eine Pipe schreibt (sowas kann man sogar selbst abfragen). Ich denke das Pufferverhalten wird eben genau drauf "angepasst". Wenn du in deinem Testprogramm mal sehr viel Text ausgibst, wirst du sehen, dass die Daten in die Pipe geschrieben werden, sobald der Puffer voll ist - und nicht erst beim Terminieren des Testprogramms.

Was [Ansatz 1] angeht, kann ich zumindest vermelden, dass bei mir (Win2000 SP4) das Verhalten so ist, wie in der Knoledgebase beschrieben, also flush nach jeder Zeile. Ich denke das ist also deine einzige Chance.

HTH

herbivore

C
charlkef Themenstarter:in
9 Beiträge seit 2005
vor 19 Jahren

Wenn die Consolen-Anwendung merkt, dass ihr Ausgabeziel eine Konsole ist, kann mann dann nicht ein ProxyConsolen-Programm schreiben, das für die primäre Consolen-Anwendung wie eine richtige Console aussieht, und das die daten dann direkt weitergibt via flush.

[ Frage 1 ] woran "erkennt" die primäre Consolen-App das ihr Aufrufer eine Console ist ?

[ Frage 2 ] kann mann eine Pipe mit der Bufferlänge 0 erstellen und sie in einem Consolenfenster mit der primären Consolen-App verketten, das Consolenfenster würde ich dann außerhalb des sichbaren Screen positionieren. ?

Es muss irgendwie !! gehen sonst fällt die gesamte Aufgabe, jedere noch so wilde Lösung ist willkommen.

49.485 Beiträge seit 2005
vor 19 Jahren

Hallo charlkef,

zu [Frage 1]

Ob einen Programm auf Console oder Datei ausgibt, kann man mit z.B. mit der Win32-Funktion GetFileType ermitteln. In der Doku dieser Funktion steht zu den Rückgabewerten zu lesen:

FILE_TYPE_UNKNOWN: The type of the specified file is unknown.
FILE_TYPE_DISK: The specified file is a disk file.
FILE_TYPE_CHAR: The specified file is a character file, typically an LPT device or a console.
FILE_TYPE_PIPE: The specified file is either a named or anonymous pipe.

Das würde ich mal so interpretieren, dass ein Programm bei FILE_TYPE_CHAR nicht unterscheiden kann, ob es auf Console oder Drucker ausgibt. Wenn man also eine Drucker-Port (LPTx) einrichtet und (per Druckertreiber?) an dessen Daten lauscht ... vielleicht geht ja was in dieser Richtung.

zu [Frage 2]:

Die Knowledgebase sagt doch gerade, dass die C-Bibliothek ihre eigene Pufferverwaltung hat. Hier sehe ich erstmal keinen Ansatz, schon gar nicht von außen, lasse mich aber gerne vom Gegenteil überzeugen.

HTH

herbivore

4.506 Beiträge seit 2004
vor 19 Jahren

Hallo!

Also ich bin auch der Meinung, dass von aussen es sehr schlecht aussieht.

Ist es denn nicht so, dass das alte C++ Programm eine Konsole aufmacht, um darin dann Ausgabewerte zu schreiben?

Also müsste man irgendwie verhindern (oder ändern), dass das C++ Programm die Konsole öffnet, und ohne Sourcecode bleibt da wohl nur AssemblerHacking 😉
[Das würd ich nicht machen, ansonsten viel Spaß 😉))]

Egal ob z.B. Druckerport oder sonstwie es Möglich sein kann, wie gesagt, Du musst verhindern, dass die Applikation die Konsole öffnet.

Wenn Du das geschafft hast, dann schreib uns wie, das würd mich zumindest stark interessieren...

Ciao
Norman-Timo

A: “Wie ist denn das Wetter bei euch?”
B: “Caps Lock.”
A: “Hä?”
B: “Na ja, Shift ohne Ende!”

49.485 Beiträge seit 2005
vor 19 Jahren

Hallo charlkef,

war gerade am Einschlafen, da sind mir noch weitere Ideen zu deinem Problem gekommen; leider blieben diese ohne durchschlagenden Erfolg. Ich schreib trotzdem mal, was alles nicht geht 🙂 Doch eine Hoffnung bleibt noch (s.u.).

Zuerst habe ich folgendes kleines Programm pipe2.exe geschrieben, um leichter experimentieren zu können:


using System;
abstract class App
{
   public static int Main (string [] astrArg)
   {
      String strLine;
      while ((strLine  = Console.ReadLine ()) != null) {
         Console.WriteLine ("pipe2: " + strLine);
      }
      return 0;
   }
}

source.exe hatte ich schon:


#include <windows.h>
#include <stdio.h>
INT main (INT iArgC, PSZ apszArgV [])
{
   int i;
   for (i = 1; i <= 30; ++i) {
      fprintf (stdout, "stdout: line %4d\n", i);
   }
   Sleep (3000);
   fflush (stdout);
   Sleep (3000);
   return 0;
}
  1. Überlegung:

Man kann mit 'source 2>&1 | pipe2' stdout und stderr zusammen in dieselbe Pipe umlenken. Meine Hoffnung war, dass dadurch das zeilenweise Flush von stderr auf stdout durchschlägt. Leider tut es das nicht!

  1. Überlegung:

Ich bin davon ausgegangen, dass es more.com schafft, sofort an die Daten zu kommen. Also habe ich 'source | more | pipe2' probiert. Aber leider hat schon 'source | more' gezeigt, dass more nicht besser dasteht als pipe2.

  1. Überlegung:

Bei den Druckertreibern habe leider ein negatives Resultat erzielt. Ich habe die Verzögerung (Sleep) in source.c verzehnfacht und danach einfach 'source > lpt1' aufgerufen. Ein Auftrag erscheint erst in der Drucker-Queue, wenn das flush erfolgt ist. Und als ich dann den FILE_TYPE_* mit

HANDLE hStdout;
DWORD dwType;
hStdout = GetStdHandle (STD_OUTPUT_HANDLE);
dwType = GetFileType (hStdout);

ermittelt habe, wurde auch klar warum: Er ist in diesem Fall FILE_TYPE_PIPE.

  1. Überlegung:

Dann habe ich deinen Bufferlänge-0-Vorschlag doch noch mal aufgegriffen und dazu die clib-Funktion 'void setbuf(FILE *stream, char *buffer);' gefunden. Beschreibung: "setbuf controls buffering for the specified stream. [...] If the buffer argument is NULL, the stream is unbuffered."

Und siehe da, wenn ich in source.c an den Anfang ein 'setbuf (stdout, NULL);' schreibe, werden die Daten sofort in die Pipe geschrieben.

Wenn du es also schaffst, dein bestehendes Programm so zu patchen, dass dieser Aufruf ausgeführt wird, kommt noch alles zu einem glücklichen Ende.

HTH

herbivore

C
charlkef Themenstarter:in
9 Beiträge seit 2005
vor 19 Jahren

Hallo All,

vielen Dank !! für Eure Bemühungen und besonderer Dank gilt Herbivore der um 3:00 noch sich mit dem Problem rumschlug und auch auf der richtigen Spur war, danke !!.

Also die Lösung ist folgende:

[ STEP 1 ]
Zuerst eine ProxyConsonen-App schreiben die von STDIN liest und nach STDOUT mit FLUSH !! schreibt ( in C++ ).

Dann ein ConsolenFenster aufmachen und folgendes eingeben, als TEST:

__oldConsoleApp | __ProxyConsoleApp.exe

Also eine ganz banale Pipe, NICHT mit > oder ähnlichem.

[ STEP 2 ]
Dann, jetzt wirds wild !!, noch eine C++ ConsolenApp schreiben die einfach nur System verwendet


void main(int argc, char* argv[])
{
	system ( "__oldConsoleApp.exe | __ProxyConsoleApp.exe" );
}


Das Programm nenne ich
__eXperimental_START_UP.exe

[ STEP 3 ]


C# Targer Application:

	process = new Process ();
	process.StartInfo.FileName= "__eXperimental_START_UP.exe";
	process.StartInfo.WorkingDirectory= pathProcess;
	process.StartInfo.Arguments= argsProcess;

	process.StartInfo.WindowStyle= ProcessWindowStyle.Hidden;

	process.StartInfo.CreateNoWindow= true;
	
	process.StartInfo.UseShellExecute= false;
	process.StartInfo.RedirectStandardOutput= true;
	process.StartInfo.RedirectStandardError	= false;
			
	process.Start();
				
	streamReaderCOUT = process.StandardOutput;

und dann nur noch fleissig lesen, ab besten in einem separatem Thread,
asynchon lesen wenn ich den Stream/Reader nicht erzeugt habe ist mir noch nicht klar, aber explizit geht es jedenfalls

Netter weise werden alle verwendeten Prozesse terminiert wenn "__eXperimental_START_UP.exe" von C#-App terminiert wird.

BTW: Die __ Underlines habe ich verwendet um mit dem TaskMgr die Prozesse besser zu beobachten zu können.

Ein wenig mit dem Pfeil von hinten durch die Brust durchs in Auge, ABER es geht !!

Fall jemand weis, wie man einen fertig übergebenen Reader das "asynchron" sein beibringt dann immer her damit.

Vielen Dank
Charlkef