# Win32: Ausgabe einer Konsolenanwendung "einfangen" - WIE?!



## Rene Albrecht (12. Dezember 2003)

Hi @all,

ich habe folgende Frage:

wie fange ich die Ausgabe z.B. eines "ipconfig /all" über meine Anwendung ein? 

Ich hatte vor, über ein CreateProcess das ipconfig zu starten und das Ergebnis von meinem Programm auszuwerten. Dabei fiel mir auf, dass ich in dem Struct STARTUPINFO für das CreateProcess zwei Handles angeben kann, die sich interessant anhören: 
   HANDLE  hStdOutput 
   HANDLE  hStdError

Habt Ihr vielleich einen Source rumliegen, bei dem Ihr das nutzt? Oder wie kann ich es ggf. besser lösen?

Gruß
René


----------



## chibisuke (12. Dezember 2003)

Du hast einen möglichen weg schon richtig erkannt, diese HANDLE einträge sind für Pipehandles.

Du erstellt mit CreatePipe() ein pipe dessen Schreibhandle du als hStdOutput übergibst, und aus dem lesehandle kannst du dann die daten lesen, is nicht schwer. Hatte sowas selbst schon mal gemacht.

Diese methode empfehle ich aber nur wenn du bidirektionale kommunikation brauchst, das heißt du dem programm eingaben senden und die ausgaben verarbeiten willst. (hStdInput und hStdOutput belegt).


Alternativ und in dem fall wesentlich einfach ist die verwendung von _popen() 
Dabei handelt es sich im eines der "alten" datei komandos. _popen öffnet ein pipe zu einem programm, aber nur unidirektion verwendbar

Anwendung:
FILE* returndata = _popen("ipconfig /all", "r");

returndata enthällt nun alle daten die ipconfig nach stdout schreibt, du brauchst sie jetzt lediglich noch mit fscanf oder fread oder fgets oder ähnlichem auszulesen.


----------



## Rene Albrecht (12. Dezember 2003)

Danke für die Info:

```
#define BUFSIZE 4096

STARTUPINFO	si;
PROCESS_INFORMATION	pi;
SECURITY_ATTRIBUTES sa;
HANDLE hPipeRead,hPipeWrite;
char cPipeResult[BUFSIZE];
DWORD dwBytes;

//
// Erstellen der Pipe
sa.nLength=sizeof(sa);
sa.bInheritHandle=TRUE;
sa.lpSecurityDescriptor=NULL;
CreatePipe(&hPipeRead,&hPipeWrite, &sa, 0);
//
// Erstellen des Prozesses
si.cb=sizeof(STARTUPINFO);
si.lpReserved=NULL;
si.lpDesktop=NULL;
si.lpTitle=NULL;
si.dwX=0;
si.dwY=0;
si.dwXSize=640;
si.dwYSize=400;
si.dwXCountChars=0;
si.dwYCountChars=0;
si.dwFillAttribute=FOREGROUND_BLUE;
si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
si.wShowWindow=SW_HIDE;
si.cbReserved2=0;
si.lpReserved2=NULL;
si.hStdInput=GetStdHandle(STD_INPUT_HANDLE);
si.hStdOutput=hPipeWrite;
si.hStdError=GetStdHandle(STD_ERROR_HANDLE);
CreateProcess(NULL, "ipconfig /all", NULL, NULL, TRUE, CREATE_DEFAULT_ERROR_MODE|NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
CloseHandle(pi.hThread);
WaitForSingleObject(pi.hProcess, 5000);
memset(cPipeResult,0,BUFSIZE);
ReadFile(hPipeRead,cPipeResult,sizeof(cPipeResult),&dwBytes,NULL);
```
...funktioniert! (Das nur für alle die eine ähnliche Frage haben)

Wo Du schon bei dem Thema bist: Wie bekomme ich jetzt noch eine bidirektionale Kommunikation hin? Nehmen wir an, ich setze anstelle von 'ipconfig /all' ein 'ftp'. Um flexibel zu sein, müßte ich dann in der Lage sein, über 'open' z.B. einen Server anzusprechen o.ä.

Wie bekomme ich das hin?

Danke nochmal


----------



## chibisuke (12. Dezember 2003)

> Wo Du schon bei dem Thema bist: Wie bekomme ich jetzt noch eine bidirektionale Kommunikation hin? Nehmen wir an, ich setze anstelle von 'ipconfig /all' ein 'ftp'. Um flexibel zu sein, müßte ich dann in der Lage sein, über 'open' z.B. einen Server anzusprechen o.ä.
> 
> Wie bekomme ich das hin?
> 
> Danke nochmal



Ich erlaube mir mal deinen beispielcode anzuwenden dafür


```
...
HANDLE hPipeOutRead,hPipeOutWrite;
HANDLE hPipeInRead, hPipeInWrite;
...
CreatePipe(&hPipeOutRead,&hPipeOutWrite, &sa, 0);
CreatePipe(&hPipeInRead,&hPipeInWrite, &sa, 0);
....
si.hStdInput=hPipeInRead;
si.hStdOutput=hPipeOutWrite;
si.hStdError=GetStdHandle(STD_ERROR_HANDLE);
CreateProcess(NULL, "ipconfig /all", NULL, NULL, TRUE, CREATE_DEFAULT_ERROR_MODE|NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
...
```

Nun kannst du aus hPipeOutRead die ausgegeben daten lesen und nach hPipeInWrite die einzugebenden daten schreiben und alles is um lot.


----------



## Rene Albrecht (12. Dezember 2003)

Das hab ich schon probiert... Mag auch gut funktionieren  - leider bei mir nicht!  Ich habe da wohl ein eher grundsätzliches Problem: Wie bekomme ich - bzw. meine Anwendung - mit, ob sich der Inhalt der Pipe geändert hat und es die Pipe neu auslesen muß?

Habe vorhin mal ein 'open xyz' mit WriteFile(hPipeInWrite...) in die Pipe geschickt, aber da hat sich nicht viel getan...


----------



## chibisuke (12. Dezember 2003)

also zuerstmal is es so... wenn du die daten aus dem pipe abgerufen hast kannst du sie kein 2. mal bekommen. 
Um zu sehen ob neue daten da sind brauchst du einfach nur von dem HANDLE lesen versuchen 


> If the return value is nonzero and the number of bytes read is zero, the file pointer was beyond the current end of the file at the time of the read operation.


Das heißt so viel wie du machst n ReadFile syscall und wenn die anzahl der gelesenen byte 0 ist dann sind keine daten hinzu gekommen, is sie größer 0 sind welche hinzu gekommen, und werden dir zurückgeben...

Bedenke das ein PIPE nicht seekable is.



Naja was soll sich auch viel tun, du bekommst ne rückgabe von dem programm die du erstmal abfragen musst bevor irgendwas anderes is oder etwa nich?

das hatt jedenfalls super fuktioniert damals... hatte ne Terminal server klasse programmiert der man einen programmnamen und ein socket objekt übergeben hatt, und dann hatt man das programm z.B. über telnet gesteuert.


----------



## Rene Albrecht (14. Dezember 2003)

Muß ich direkt mal ausprobieren, wenn ich wieder an MEINEN PC komme... werde das Ergebnis (den Source) bei Erfolg dann hier veröffentlichen. So haben dann Leute - die ähnliche Fragen haben - wenigstens die Möglichkeit, sich mal ein Beispiel dafür anzuschauen (und nicht nur die MSDN-Beiträge dazu durchzulesen)...  

Sollte es nicht funktionieren, folgt der nächste "Request" auf dem Fuße...


----------



## Rene Albrecht (15. Dezember 2003)

*Zusatzfrage!*

Ist es möglich, ein Event in meinem Programm zu erzeugen, wenn der durch CreateProcess erzeugte Kindprozess Daten in die Pipe geschrieben hat? (Ich möchte die Pipe nicht sequentiell nach X Sekunden auslesen/beschreiben, sondern vom Programm nur Daten aus der Pipe lesen lassen, wenn der aufgerufene Kindprozess dort auch wirklich welche hinterlegt hat)

Ich denke da an eine Methodik ähnlich den WinSocks. Wenn irgendwas auf dem erzeugten Socket passiert erzeugt Windows ein Event und gibt dieses an meine Anwendung weiter...

Sorry, dass ich das jetzt doppelt und dreifach versucht habe zu erklären... Brauche allerdings dringend Unterstützung und deshalb sollte die Fragestellung (grundsätzlich) eindeutig sein. :-(

Gruß


----------



## chibisuke (15. Dezember 2003)

du meinst sowas wie assynchronmode bei sockets?

nein gibtes nicht soweit ich weiß. aber es gibt eine einfache möglichkeit das manuell zu realisieren.

erstell dir einen 2. thread der eine endlosschleife macht wo er abfragt ob schon was da is, und das pipe im synchronen blokierenden modus anspricht, das müsste wenn ich richtig informiert bin nämlich gehen, dann kann dir der thread ne nachricht geben.


----------



## Rene Albrecht (16. Dezember 2003)

ich bin noch ein wenig ungeschickt im umgang mit threads! habe erstmal folgendes problem:


```
switch (msg) {
case WM_INITDIALOG:
	SetWindowText(hwndDlg,"CreateProcess-Test");
	/*
	   Erstellen der Pipe
	*/
	sa.nLength=sizeof(sa);
	sa.lpSecurityDescriptor=NULL;
	sa.bInheritHandle=TRUE;
	CreatePipe(&hPipeOutRead,&hPipeOutWrite, &sa, 0);
	return TRUE;
case WM_COMMAND:
	switch (LOWORD(wParam)) {
	case IDOK:
		/*
		   Erstellen des Prozesses
		*/
		si.cb=sizeof(STARTUPINFO);
		si.lpReserved=NULL;
		si.lpDesktop=NULL;
		si.lpTitle=NULL;
		si.dwX=0;
		si.dwY=0;
		si.dwXSize=640;
		si.dwYSize=400;
		si.dwXCountChars=0;
		si.dwYCountChars=0;
		si.dwFillAttribute=FOREGROUND_BLUE;
		si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
		si.wShowWindow=SW_HIDE;
		si.cbReserved2=0;
		si.lpReserved2=NULL;
		si.hStdInput=GetStdHandle(STD_INPUT_HANDLE);
		si.hStdOutput=hPipeOutWrite;
		si.hStdError=GetStdHandle(STD_ERROR_HANDLE);
		CreateProcess(NULL, "ipconfig /all", NULL, NULL, TRUE, CREATE_DEFAULT_ERROR_MODE|NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
		/*
		   Auslesen der Pipe
		*/
		CloseHandle(pi.hThread);
		WaitForSingleObject(pi.hProcess, 5000);
		memset(cPipeResult,0,BUFSIZE);
		ReadFile(hPipeOutRead, cPipeResult, sizeof(cPipeResult), &dwBytes, NULL);
		SetDlgItemText(hwndDlg,IDTEXT,cPipeResult);
		break;
	case IDCANCEL:
		EndDialog(hwndDlg,0);
		return 1;
	}
	break;
case WM_CLOSE:
	EndDialog(hwndDlg,0);
	return TRUE;
}
```

ich war der Meinung, dass ich beim WM_INITDIALOG bereits die pipe erstellen kann. allerdings scheint das nicht zu funktionieren, denn das programm gibt nach Klick auf OK (IDOK) nichts aus.

erstelle ich die pipe direkt vor dem prozess (also mit im eventhandler für IDOK) funktioniert es...

hat jemand einen schimmer?


----------



## chibisuke (16. Dezember 2003)

Hast du die handle dafür vieleicht in der funktion definiert?

wenn ja kann das net gut funktionieren.. versuch mal die HANDLE als static zu definieren...

also nicht mehr
HANDLE hPipeRead;

sondern versuch mal

static HANDLE hPipeRead;


----------



## Rene Albrecht (16. Dezember 2003)

Danke für den Tipp!    


```
static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   static STARTUPINFO	si;
   SECURITY_ATTRIBUTES sa;
   static HANDLE hPipeOutRead=INVALID_HANDLE_VALUE;
   static HANDLE hPipeOutWrite=INVALID_HANDLE_VALUE;
   PROCESS_INFORMATION	pi;
   char cPipeResult[BUFSIZE];
   DWORD dwBytesRead;

   switch (msg) {
      case WM_INITDIALOG:
         SetWindowText(hwndDlg,"CreateProcess-Test");
         //
         // Erstellen der Pipe
         sa.nLength=sizeof(sa);
         sa.bInheritHandle=TRUE;
         sa.lpSecurityDescriptor=NULL;
         CreatePipe(&hPipeOutRead,&hPipeOutWrite, &sa, 0);
         //
         // Definieren der StartupInfos von Kindprozessen
         ZeroMemory(&si, sizeof(STARTUPINFO));
         si.cb=sizeof(STARTUPINFO);
         si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
         si.wShowWindow=SW_HIDE;
         si.hStdInput=GetStdHandle(STD_INPUT_HANDLE);
         si.hStdOutput=hPipeOutWrite;
         si.hStdError=GetStdHandle(STD_ERROR_HANDLE);
         return TRUE;
      case WM_COMMAND:
         switch (LOWORD(wParam)) {
            case IDOK:
               //
               // Starten eines Kindprozesses
               ZeroMemory(&pi,sizeof(PROCESS_INFORMATION));
               CreateProcess(NULL, "ipconfig /all", NULL, NULL, TRUE, CREATE_DEFAULT_ERROR_MODE|NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
               WaitForSingleObject(pi.hProcess, INFINITE);
               CloseHandle(pi.hThread);
               CloseHandle(pi.hProcess);
               memset(cPipeResult,0,BUFSIZE);
               ReadFile(hPipeOutRead,cPipeResult,sizeof(cPipeResult),&dwBytesRead,NULL);
               SetDlgItemText(hwndDlg,IDTEXT,cPipeResult);
               break;
            case IDCANCEL:
               EndDialog(hwndDlg,0);
               return 1;
            }
            return TRUE;
         case WM_CLOSE:
            EndDialog(hwndDlg,0);
            return 0;
   }
   return FALSE;
}
```
*FUNKTIONIERT!* Jetzt muß ich nur noch die oben beschriebene bidirektionale Kommunikation zum erstellten Prozess (natürlich nicht zum "ipconfig /all", sondern zu einem "ftp" o.ä.) hinbekommen - dann bin ich glücklich...


----------



## com (16. Dezember 2003)

Wieso machst du es dir s kompliziert?
Du willst so wie ich das sehen einfach nur FTP (oder what ever) über deine selbstgeschriebenes programm machen oder?

Benutze doch die Funktionen die C++ dir da bietet!
Ist doch wesentlich komfortabler...

Schau dir dazu doch einfach mal 

"CInternetSession"
und
"CFtpConnection"
an

Das dürfte dir auch helfen...

Auch interessant ist der Beitrag uas der MSDN 
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wininet/wininet/ftp_sessions.asp


----------



## chibisuke (16. Dezember 2003)

es geht noch nicht unbedingt um FTP hier, sondern es geht um bidirektionale kommunikation.


und ich hab kein problem damit gehabt als ich es versucht hab, hab das progy aber leider net mehr, sonst hätt ich dir die klasse gezeigt.


----------



## Rene Albrecht (16. Dezember 2003)

> _Original geschrieben von chibisuke _
> *es geht noch nicht unbedingt um FTP hier, sondern es geht um bidirektionale kommunikation.*


Genau - FTP war nur ein Beispiel. 

Konkreter: Bei uns in der Fa. haben wir Legato Networker als Datensicherungstool im Einsatz. Über installierte Networker Clients hat man die Möglichkeit, z.B. Datendurchsätze der im SAN angeschlossenen Bandlaufwerke auszulesen. Ich möchte nun (über einen installierten Networker Client) periodisch Informationen vom Server holen, da ich nachts nicht unbedingt ständig in's System schauen möchte, um Optimierungsmöglichkeiten bei der Verteilung der Ressourcen zu finden. Da der Client jedoch über RPC mit dem Server kommuniziert und ich weder Lust habe, mir die Netzkommunikation über RPC mit dem Sniffer anzuschauen und mit einem eigenen Programm einen Networker Client zu simulieren (mal davon abgesehen, dass das sicher nicht ganz legal ist), noch mich mit RPC generell zu beschäftigen (ich hatte mal einen Post dazu - ohne viele Antworten), wollte ich die mitgelieferte Konsolenanwendung über ein selbstgeschriebenes Proggi dazu animieren, mir Infos auszuspucken, die ich dann ggf. in eine Datenbank für spätere Auswertung schreiben kann. Dazu benötige ich allerdings eine bidirektionale Kommunikation, da ich die Konsolenanwendung:
1. aufrufen
2. mit Daten füttern und
3. die Antworten auswerten muß.

Sind jetzt alle Fragen hinsichtlich Sinn und Verstand meiner Nerverei geklärt? 

*@chibisuke*: Wir sind schon so weit - jetzt nur nicht aufgeben ... Danke bis dahin!

Gruß
René


----------



## com (16. Dezember 2003)

alles klar, ich hab nix gesagt  dachte blos du wolltest auf sachen wie FTP / Telnet etc. hinaus, weil du das so ausdrücklich sagtest (nicht ipconfig sondern ftp etc.) wie man sich täuschen kann


----------

