fork() und stdout über eine pipe in einen C++ string einlesen, in stdin des Childs schreiben



  • Hallo Forum,

    ich habe vor einer Woche begonnen, mich mit C++ auseinanderzusetzen, bin also noch ziemlich grün, zumal meine Vorkenntnisse aus anderen Sprachen ebenfalls eher bescheiden sind.
    Nichts desto trotz bin ich sehr motiviert und habe zunächst einmal angefangen, mir Grundlagen duch die Lektüre des Buches "C++ Programmierung" von J.R. Hubbard anzueignen.
    Aus Motivationsgründen habe ich dann beschlossen, mir ein konkretes Projekt vorzunehmen und so langsam an den momentan noch übermächtigen Anforderungen zu wachsen. Also habe ich mich entschieden, ein Gtk++ Frontend für mplayer zu schreiben, mit dem ich eine DVD auf Platte rippen und nach Xvid umwandeln kann.

    Nach ein wenig Herumspielerei mit Gtk wollte ich zunächst eine Klasse "Fork" schreiben, die mir das gewünschte Programm mit möglichen Parametern aufruft und auch gleich entsprechende Pipes erzeugt und Funktionen zum Lesen und Schreiben in Richtung Childprozess bereitstellt.

    Aus dem Internet habe ich mir das folgende zusammengegoogelt und (vermutlich stümperhaft) in eine C++-Klasse verpackt:

    #include "Fork.h"
    #ifndef FORK_CPP
    #define FORK_CPP
    int Fork::fork_now(char* program, char* child_argv[])
    {
    	// create pipes for stdin, stdout and stderr
    	pipe(input);
    	pipe(output);
    	pipe(error);
    
    	int child_pid = fork();
    	switch (child_pid)
    	{
    		case -1: //error
    			std::cerr << "fork() error - exiting now";
    			exit(1);
    		case 0: // child
    			// connect stdin to pipe
    			dup2(input[0], 0);  
    			// connect stdout to pipe
    			dup2(output[1], 1); 
    			// connect stderr
    			// dup2(error[1], 2);
    			// call program with child_argv[] parameters 
    			execv(program, child_argv);
    			// this should never happen
    			exit(0);
    		default: // parent
    			return child_pid;
    	}
    }
    
    void Fork::write_to_child(std::string message)
    {
    	char c_message[message.length()+1];
    	strcpy(c_message, message.c_str()); strcat(c_message, "\n"); 
    	write(input[1], c_message, strlen(c_message));
    }
    
    std::string Fork::read_from_child()
    {
    	static char buf[2048];	
    	memset(buf, 0, 2048);
        read(output[0], &buf, 2048);
    	std::string reply = buf;
    	return reply;
    }
    #endif
    

    Desweiteren habe ich ein kleines Programm namens "hallo" geschrieben, das über 'cin >> input' von stdin einliest und dann 'cout << "Hallo sagt " << input' wieder ausgibt, bis ich ein einzelnes "q" übergebe, dann beendet sich "hallo" mit exit(0).

    Soweit so gut, mit folgendem Testprogramm kann ich nun also "hallo" aufrufen und über die Pipe kommunizieren:

    #include "Fork.h"
    using namespace std;
    #ifndef TESTOPTS_CPP
    #define TESTOPTS_CPP
    int main()
    {
    	Fork* pa = new Fork();
    
    	char* program = "hallo";
    	char* argv[2];
    	argv[0] = "halli";
    	argv[1] = (char)NULL;
    
    	pid_t child_pid = pa->fork_now(program, argv);
    
    	while (waitpid(child_pid, NULL, WNOHANG) == 0)	
    	{
    		string tstring;
    		cout << "Message eingeben: " << endl;
    		cin >> tstring;
    		pa->write_to_child(tstring);
    		if (waitpid(child_pid, NULL, WNOHANG) == 0)
    		{
    			string reply = pa->read_from_child();
    			cout << reply << endl;
    		}
    	}
    	delete pa;
    	exit(0);
    }
    #endif
    

    Ebenso kann ich jetzt mithilfe meiner Klasse Fork auch andere Programme, wie beispielsweise "ls" aufrufen und da kommt auch schon das Problem.

    1. Wie kann ich erreichen, daß mein Parentprozess solange die stdout des Childs einliest, bis dieser nichts mehr zu sagen hat? Wenn ich read_from_child() auf gut Glück aufrufe und der Childprozess hat sich schon beendet, dann hängt mein Programm.

    2. Von der Pipe lese ich umständlich über read(output[0], &buf, 2048) und wandle dann in ein Stringobjekt um, da alle Beispiele, die ich finden konnte, in C geschrieben sind. Geht das nicht eleganter direkt in C++ in einen String zu lesen? Das selbe gilt in der Funktion write_to_child() für den C-Befehl write.

    3. Wie kann ich Zeile für Zeile über die Pipe lesen?

    4. Manchmal klappt das Beenden von "hallo" mittels "q" problemlos, manchmal wird aus "hallo" aber auch ein Zombie und mein Parentprozess muß mit Strg+C beendet werden. Woran kann das liegen?

    5. Gibt es eine Möglichkeit, stderr des Childs abzufangen und darauf zu reagieren, wenn der seltene Fall eintritt, daß Child einen Fehler ausgibt. Wenn ich Stderr und Stdout des Childs zusammenfasse und mittels read_from_child() einlese, kann ich ja nicht mehr unterscheiden, was Programmausgabe und was Fehlermeldung war.

    Ich weiß, ein endlos langes Posting. Ich gelobe Besserung, aber mangels Erfahrung weiß ich noch nicht genau einzuschätzen, welche Bereiche relevant sind. Ich würde mich jedenfalls über Antworten auf meine Fragen sehr freuen und bin auch für allgemeine Hinweise zu meinem Code sehr dankbar. Wenn möglich möchte ich mir von Anfang an einen einigermaßen passablen Stil angewöhnen.

    Von den Buchtips hier auf der Seite habe ich mir jetzt übrigens "Linux-Unix-Programmierung Das umfassende Handbuch" von Jürgen Wolf bestellt, auch wenn es für mich als Einsteiger vermutlich noch viel zu starker Tobak sein dürfte.

    Gruß
    puntarenas

    Edit: Beinahe hätte ich es vergessen, vielleicht habe ich ja auch etwas in der Fork.h vermasselt, der Vollständigkeit halber:

    #include <iostream>
    #include <sys/wait.h>
    
    #ifndef FORK_H
    #define FORK_H
    class Fork
    {
    	public:
    		int input[2], output[2], error[2];
    		Fork(){}
    		~Fork(){close(input[1]); close(output[0]); close(error[0]);};
    		int fork_now (char*, char* []);
    		void write_to_child (std::string);
    		std::string read_from_child();
    };
    #endif
    

Anmelden zum Antworten