Problem mit dem Umleiten vom STD_OUTPUT einer 3rd-Party Consolenanwendung



  • Hallo zusammen,

    ich bin grade so ein bisschen am Verzweifeln. Seid Tagen versuche ich mich daran, aus einer 3rd-Party Consolenanwendung den STD_OUTPUT an meine Consolenanwendung umzuleiten, um dessen Ausgabetext auswerten zu können.
    Das habe ich zuerst mit einer Pipe versucht, was nach etlichen Fehlversuchen auch endlich geklappt hat:

    #include <windows.h>
    #include <stdio.h>
    #include <iostream>
    
    using namespace std;
    
    int main(int argc, char *argv[])
    {
    
        #define BUFSIZE 4096
    
        STARTUPINFO si;
        memset(&si, NULL, sizeof(si));
    
        PROCESS_INFORMATION pi;
        SECURITY_ATTRIBUTES sa;
        HANDLE hPipeRead,hPipeWrite;
        char cPipeResult[BUFSIZE];
        DWORD dwBytes;
    
        sa.nLength=sizeof(sa);
        sa.bInheritHandle=TRUE;
        sa.lpSecurityDescriptor=NULL;
        CreatePipe(&hPipeRead,&hPipeWrite, &sa, 0);
    
        si.cb=sizeof(STARTUPINFO);
        si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
        si.wShowWindow=SW_HIDE;
        si.hStdInput=GetStdHandle(STD_INPUT_HANDLE);
        si.hStdOutput=hPipeWrite;
        si.hStdError=GetStdHandle(STD_ERROR_HANDLE);
        CreateProcess("D:\\CINEMA 4D R11.5\\CINEMA 4D 64 Bit.exe", "-render -render \"D:\\rendertest.c4d\" -oimage \"D:\\rendertest.jpg\"  -nogui", NULL, NULL, TRUE, CREATE_DEFAULT_ERROR_MODE|NORMAL_PRIORITY_CLASS, NULL, "D:\\CINEMA 4D R11.5", &si, &pi);
        CloseHandle(pi.hThread);
    
        WaitForSingleObject(pi.hProcess, 100);
        memset(cPipeResult,0,BUFSIZE);
        ReadFile(hPipeRead,cPipeResult,sizeof(cPipeResult),&dwBytes,NULL);
    
        cout<<cPipeResult<<endl;
    
        char a[100];
        cin>>a;
    
    }
    

    Es gibt dabei allerdings ein Problem:
    Normalerweise erscheint in der 3rd-Party-Consolenanwendung ("CINEMA 4D 64 Bit.exe") der Text in der Console im Abstand von ein paar Sekunden. Also da kommt Stückchenweise immer neuer Text hinzu.
    Mit dem Codebeispiel von mir oben erscheint der Text aber am Stück und nicht nach und nach, sondern erst wenn aller Text in der anderen Console fertig geschrieben wurde.
    Ich hab da mal nach gegoogelt und scheinbar liegt es daran, dass der Buffer nicht geflushed wird und die ReadFile-Operation wartet bis der STDOUT-Buffer geschlossen wird. So jedenfalls habe ich das verstanden.

    Daraufhin bin ich auf folgende Seite gestoßen:
    http://www.codeproject.com/KB/threads/RTconsole.aspx

    Dort wird erklärt, wie man den Inhalt einer Console in Echtzeit mittels eines ConsoleScreenBuffers auslesen kann.
    Ich habe meinen Code also entsprechend angepasst, sodass ein ConsoleScreenBuffer angelegt wird, in den die 3rd-Party-Console schreibt, und den ich dann auslese und - zur Kontrolle, ob etwas ankommt - in eine Textdatei schreibe. Leider erscheint in der Textdatei nur leerer Inhalt, also als würde der Buffer leer sein.

    Das hier ist der Code, wie ich ihn angepasst habe - nun mit ScreenBuffer statt Pipe:

    #include <windows.h>
    #include <stdio.h>
    #include <iostream>
    #include <fstream>
    
    using namespace std;
    
    int main(int argc, char *argv[])
    {
    
        #define BUFSIZE 100
    
        STARTUPINFO si;
        PROCESS_INFORMATION pi;
        DWORD dwBytes;
        HANDLE hConsole;
    
        sa.nLength=sizeof(sa);
        sa.bInheritHandle=TRUE;
        sa.lpSecurityDescriptor=NULL; 
    
        memset(&si, '\0', sizeof(si));
    
        si.cb=sizeof(STARTUPINFO);
        si.dwFlags=STARTF_FORCEOFFFEEDBACK;
    
        COORD origin = {0,0};
        DWORD dwDummy;
    
        HANDLE stdOut;
        hConsole = CreateConsoleScreenBuffer(GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,&sa,CONSOLE_TEXTMODE_BUFFER,NULL);
        FillConsoleOutputCharacter(hConsole, '\0', MAXLONG, origin, &dwDummy);
        SetStdHandle(STD_OUTPUT_HANDLE, hConsole); // to be inherited by child process
    
        CreateProcess("D:\\CINEMA 4D R11.5\\CINEMA 4D 64 Bit.exe", "-render -render \"D:\\rendertest.c4d\" -oimage \"D:\\rendertest.jpg\"  -nogui", NULL, NULL, TRUE, CREATE_DEFAULT_ERROR_MODE|NORMAL_PRIORITY_CLASS, NULL, "D:\\CINEMA 4D R11.5", &si, &pi);
        CloseHandle(pi.hThread);
    
        char* buffer = new char[BUFSIZE];
        DWORD count;
    
        ofstream datei;
        datei.open("testdatei.txt");
    
        while (1)
        {
             WaitForSingleObject(pi.hProcess, 2000);
             memset(buffer,NULL,BUFSIZE);
    
             ReadConsoleOutputCharacter(hConsole, buffer, 100, origin, &count);
             datei.write(buffer, 100);
    
            }
    
            char a[100];
            cin>>a;
    }
    

    (Mir ist klar, dass der Code sehr unsauber ist und ein While(1) ein Verbrechen ist, aber zum Testen auf Funktionalität sollte es reichen.)

    Wie gesagt, in der Textdatei, welche geschrieben wird, befinden sich nur leere Zeichen, es wird also scheinbar kein Text aus dem Buffer empfangen.

    Kann mir jemand weiterhelfen? Was mache ich falsch oder wie ließe sich die Problematik umgehen, dass die Pipe immer erst nach Beenden des Child-Prozesses ausgelesen wird?

    Viele Grüße,

    ynnus



  • Also wenn ich testweise mal eine eigene kleine Consolenanwendung heran nehme, und diese auslesen möchte, dann klappt mein zweiter Code...

    Dies ist meine kleine Testconsole (kopiert von der Seite, die ich oben verlinkt habe):

    #include <stdio.h>
    #include <windows.h>
    
    int main(int argc, char* argv[])
    {
    	for (int i = 0; i < 100; i++)
    	{
    		for (int j = 0; j < i; j++)
    			printf(".");
    		printf("%d\n", i);
    		Sleep(50);
    	}
    	return 0;
    }
    

    Diesen Code als Exe gespeichert und mit meiner CreateProcess-Funktion aufgerufen funktioniert soweit, dass ich die Ausgabe (Zahlen von 0 - 99) in die Textdatei geschrieben bekomme.

    Meine andere 3rd-Party Consolenanwendung (Die Cinema 4D.exe) scheint also ein Spezialfall zu sein - leider will ich genau dieses Programm auslesen bzw an dessen Consolen-Output heran... Was könnte es sein, dass der Code hier nicht funktioniert? Gibt es andere Methoden, Text in der Console auszugeben, welcher hier womöglich angewendet wird, sodass mein Code diesen nicht berücksichtigt?

    Ich bin langsam echt am Verzweifeln... 😞



  • Hallo Ynnus,

    Warum nicht gleich:

    TCHAR szStdOutput[MAX_PATH];
    TCHAR szName[MAX_PATH];
    
    _sntprintf(&szName, MAX_PATH, TEXT("%%TEMP%%\\StdOutput%.8X.txt"), GetTickCount());
    ExpandEnvironmentStrings(szName, szStdOutput, MAX_PATH);
    
    si.hStdInput  = 0;
    si.hStdOutput = CreateFile(szStdOutput, GENERIC_READ|GENERIC_WRITE, 0, &sa, CREATE_ALWAYS, 0, 0);
    si.hStdError  = si.hStdOutput;
    ...
    CreateProcess ...
    WaitForSingleObject(pi.hProcess, INFINITE); // really INFINITE ?
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    
    // read stdout?
    DWORD size = GetFileSize(si.hStdOutput, 0); // oder while fgets
    if (size)
    {
    	char *txt = new char[size+1];
    	txt[size] = 0;
    	SetFilePointer(si.hStdOutput, 0, 0, FILE_BEGIN);
    	ReadFile(si.hStdOutput, txt, size, &size, 0);
    ...
    	delete txt;
    }
    
    CloseHandle(si.hStdOutput);
    DeleteFile(szStdOutput);
    

    STD_OUTPUT an meine Consolenanwendung umzuleiten

    Dann brauchst Du keine Pipe:

    si.hStdOutput=GetStdHandle(STD_OUTPUT_HANDLE);
    si.hStdError=GetStdHandle(STD_ERROR_HANDLE);
    si.hStdInput=0;
    CreateProcess + WaitForSingleObject INFINITE, oder einfacher mit popen.
    
    WaitForSingleObject(pi.hProcess, 100); // warum 100? (nicht raten)
    //wozu? memset(cPipeResult,0,BUFSIZE);
    //bug at std::cout ReadFile(hPipeRead,cPipeResult,sizeof(cPipeResult),&dwBytes,NULL);
    /*IF fehlt*/ReadFile(hPipeRead,cPipeResult,sizeof(cPipeResult)-1 /*eins weniger*/,&dwBytes,NULL);
    cPipeResult[dwBytes]=0;
    

    MSDN schrieb:

    If the pipe buffer is full before all bytes are written, WriteFile does not return until another process or thread uses ReadFile to make more buffer space available.

    So die andere Anwendung kann hung. Du muß die Pipe leeren:

    // WaitForSingleObject -> bitte nicht!
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    while (ReadFile(hPipeRead,cPipeResult,sizeof(cPipeResult),&dwBytes,NULL) && dwBytes)
    {
    	cPipeResult[dwBytes]=0;
    	cout<<cPipeResult;
    }
    // process terminated
    cout<<"\n";
    CloseHandle(hPipeRead);
    


  • Hallo sapero,

    danke für deine Antwort. Ich habe mal versucht deine Codebeispiele an meine Anwendung anzupassen, leider funktioniert es noch nicht so wie gewünscht.

    Wenn ich es direkt mittels folgendem Code:

    si.hStdOutput=GetStdHandle(STD_OUTPUT_HANDLE)
    

    also ohne File und ohne Pipe umleiten will dann erscheint garnichts auf meinem Fenster, dafür erscheint in der 3rd-Party Console nach wie vor der Text ganz normal, ohne umgeleitet zu werden. Aus irgend einem Grund funktioniert das Zuweisen des eigenen STD_OUTPUTs an diese Console leider nicht.
    Ich habe aber auch keinen Zugriff auf den Quellcode dieser anderen Console und weiß daher nicht, wie der Text dort ausgegeben wird. Normales Umleiten so ohne Pipe klappt jedenfalls nicht.

    Und dein erstes Codebeispiel kann ich nicht genau testen weil mein Compiler die Funktion "_sntprintf()" nicht kennt (hab kein Visual Studio sondern den mingw Compiler).
    Und wenn man diese Zeile auskommentiert und das Programm startet dann hat "size" nach dem "GetFileSize" den Wert 4294967296 (also 32 Bit) aber ReadFile ließt keine Zeichen aus - also die Funktion gibt 0 als Rückgabewert - dass sie fehlgeschlagen ist... 😕

    Und jede andere Kombination deines Codes wie dieser hier:

    // WaitForSingleObject -> bitte nicht!
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    while (ReadFile(hPipeRead,cPipeResult,sizeof(cPipeResult),&dwBytes,NULL) && dwBytes)
    {
        cPipeResult[dwBytes]=0;
        cout<<cPipeResult;
    }
    // process terminated
    cout<<"\n";
    CloseHandle(hPipeRead);
    

    hatte zur Folge, dass der Text zwar ankommt, genau wie mit meinem 1. Beispiel mit der Pipe, aber leider eben erst sobald der Child-Prozess beendet wurde. Der Text aus der anderen Console kommt also in einem kompletten Stück an und nicht so wie er ursprünglich geschrieben wurde.



  • _sntprintf ist definiert in tchar.h, aber da brauchst Du stdio.h zuerst (in C::B).

    Mit der Pipe ist so, dass muß man den "write-end" nach CreateProcess schließen

    #include <windows.h>
    #include <stdio.h>
    
    int main()
    {
    	STARTUPINFO si;
    	PROCESS_INFORMATION pi;
    	SECURITY_ATTRIBUTES sa;
    	char szName[MAX_PATH];
    
    	HANDLE hPipeRead, hPipeWrite;
    
    	sa.nLength=sizeof(sa); 
    	sa.bInheritHandle=TRUE; 
    	sa.lpSecurityDescriptor=NULL;
    
    	ZeroMemory(&si, sizeof(si));
    	si.cb         = sizeof(STARTUPINFO);
    	si.dwFlags    = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
    
    	CreatePipe(&hPipeRead,&hPipeWrite, &sa, 0);
    	si.hStdOutput = hPipeWrite;
    	si.hStdError  = si.hStdOutput;
    
    	CreateProcess(0, "cmd.exe /c dir %SYSTEMROOT%\\system32", NULL, NULL, TRUE, 0, NULL, 0, &si, &pi);
    
    	CloseHandle(hPipeWrite); // wichtig !!!
    
    	CloseHandle(pi.hProcess);
    	CloseHandle(pi.hThread);
    
    	char buff[1024];
    	while (ReadFile(hPipeRead,buff,sizeof(buff)-1,&si.dwX,NULL) && si.dwX) // borrow si.dwX
    	{ 
    		buff[si.dwX]=0; // oder einfach writefile hStdOut
    		printf("%s", buff);
    	}
    	CloseHandle(hPipeRead);
    }
    

    Und die direkte Methode mit STD umgeleitet: (my fault)

    si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); 
    	si.hStdError  = si.hStdOutput;
    	SetHandleInformation(si.hStdOutput, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
    

Anmelden zum Antworten