Dakuan
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Ich habe den Titel absichtlich etwas kurz gehalten, da es sonst ein ganzer Absatz geworden wäre. Ich möchte von einem C++ Programm aus ein anderes Programm starten. Bisher hatte ich das immer mit popen() gemacht. Damit kann man aber immer nur in eine Richtung Daten austauschen. Jetzt benötige ich aber einen Datenaustausch in beide Richtungen. Alles was ich bisher zu diesem Thema gelesen hatte, weist auf Fifos hin und klingt sehr kompliziert. Brauchbare Beispiele für bidirektionale Kommunikation habe ich da aber auch noch nicht gefunden. Bevor ich da jetzt tiefer einsteige, wollte ich daher die Experten hier mal Fragen, ob ich überhaupt auf dem richtigen Weg bin oder ob es einfachere Alternativen gibt. Das Scenario: Ich habe ein GUI-Programm das Metadaten (Kommentare) bearbeiten soll. Diese werden oft per Copy/Paste eingefügt, wobei aber nicht alles interessant ist oder neu formatiert bzw. gefiltert werden muss. Die Anforderungen sind dabei je nach Benutzer und Anwendungsfall unterschiedlich, weshalb ich diesen Textfilter nicht fest in das Programm einbauen will. Stattdessen soll der Benutzer die Möglichkeit bekommen, ein externes Programm dafür anzugeben. Das könnte notfalls ein Bash Script oder sed sein. D.h. die Kommunikation soll über stdin/stdout stattfinden. Geht das so überhaupt?
|
MPW
Anmeldungsdatum: 4. Januar 2009
Beiträge: 3729
|
Hallo, in Anbetracht der Möglichkeit, dass der Nutzer den Server- und Client-Teil auf verschiedenen Rechnern nutzen möchte, solltest du das wohl direkt über Sockets implementieren. Das ist auch der gängige Weg, wenn du beide Teile das Programms schreibst. Aufrufen von Drittprogrammen und auslesen der Rückgabewerte ist eine Funktion, die zum Einbinden von binären Programmen von dritten gedacht ist. Das trifft in deinem Fall nicht zu, soweit ich das verstanden habe. Grüße
MPW
|
Lysander
Anmeldungsdatum: 30. Juli 2008
Beiträge: 2669
Wohnort: Hamburg
|
Dakuan schrieb: Das Scenario: Ich habe ein GUI-Programm das Metadaten (Kommentare) bearbeiten soll. Diese werden oft per Copy/Paste eingefügt, wobei aber nicht alles interessant ist oder neu formatiert bzw. gefiltert werden muss. Die Anforderungen sind dabei je nach Benutzer und Anwendungsfall unterschiedlich, weshalb ich diesen Textfilter nicht fest in das Programm einbauen will. Stattdessen soll der Benutzer die Möglichkeit bekommen, ein externes Programm dafür anzugeben.
Nur weil die Anforderungen unterschiedlich sind, muss der Benutzer doch nicht unbedingt ein externes Programm dafür aufrufen! Du kannst doch verschiedenen Strategien implementieren und ihm eine Auswahl ermöglichen? Oder willst Du im Grunde eine Art Plugin-Schnittstelle, bei der der Benutzer selber komplett frei ist in der Wahl des Werkzeugs? Das wird dann natürlich immer ein wenig schwieriger... Wenn es reicht, dass das unter Linux läuft, wäre dbus mein erster Ratschlag. Ansonsten gäbe es mit Boost.Interprocess eine spezielle C++-Lösung. Imho angenehmer - dafür mit einem Mehraufwand verbunden - wäre eine Message Queue a la RabbitMQ ☺
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Du kannst doch verschiedenen Strategien implementieren und ihm eine Auswahl ermöglichen?
Das mit den verschiedenen Strategien ist so eine Sache. Ich habe da noch keine wirklich gute Idee, mit der man alles abdecken könnte. Im wesentlichen geht es ja auch darum, das bei einer Änderung der zugelieferten Daten nicht jedesmal das eigentliche Programm geändert werden muss. Aber Du bringst mich da gerade auf eine neue Idee. Da es sich bei dem Filter im wesentlichen um Texttabellen handelt, könnte man diese auslagern, sodass der Benutzer sich für jeden Anwendungsfall eine spezielle Tabelle erstellen / anpassen kann.
Oder willst Du im Grunde eine Art Plugin-Schnittstelle, bei der der Benutzer selber komplett frei ist in der Wahl des Werkzeugs?
Das trifft meine ursprüngliche Idee eigentlich am besten. Ich werde jetzt erstmal eine Runde mit dem Rad duchs Grüne machen, damit mein Kopf wieder klar wird.
|
microft
Anmeldungsdatum: 6. August 2009
Beiträge: 454
Wohnort: Norddeutschland
|
Hallo Schreib deine Eingansdaten einfach in eine temporäre Datei und übergebe diesen File dann, als Parameter, an ein Programm das du mit popen aufrufst und seinen Output einließt. Auf diese weise kann dein Filterprogramm auch ein Script oder ein awk oder sonstwas sein ohne das du für den Filter das Rad neu erfinden mußt. cu
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12832
|
microft schrieb:
Schreib deine Eingansdaten einfach in eine temporäre Datei und übergebe diesen File dann, als Parameter, an ein Programm das du mit popen aufrufst und seinen Output einließt. Auf diese weise kann dein Filterprogramm auch ein Script oder ein awk oder sonstwas sein ohne das du für den Filter das Rad neu erfinden mußt.
Ich halte das aus verschiedenen Gründen für eine schlechte Idee: zum einen sind die Inhalte dann öffentlich sichtbar. Zum anderen muss man die Datei sauber wieder abräumen (gut, das bekommt man durch tempfile() hin). Desweiteren verbraucht man ggf. deutlich mehr Platz als nötig, denn mit einer temporären Datei muss man den kompletten Output erzeugen, bevor der andere Prozess ihn lesen kann - das gleiche gilt für den Rückweg. Außerdem ist Dateisystem IO teurer als IO im Hauptspeicher. Nein, FIFOs sind schon der richtige Mechanismus. Man muss allerdings aufpassen, dass man beim parallelen Schreiben und Lesen kein Deadlock erzeugt. Da muss man dann Threads oder nonblocking IO verwenden. Zutaten:
Ich habe auf die Schnelle das gefunden. Das kommt Deinem Anwendungsfall schon recht nahe, soweit ich das sehe.
|
microft
Anmeldungsdatum: 6. August 2009
Beiträge: 454
Wohnort: Norddeutschland
|
rklm schrieb:
Ich halte das aus verschiedenen Gründen für eine schlechte Idee: zum einen sind die Inhalte dann öffentlich sichtbar. Zum anderen muss man die Datei sauber wieder abräumen (gut, das bekommt man durch tempfile() hin). Desweiteren verbraucht man ggf. deutlich mehr Platz als nötig, denn mit einer temporären Datei muss man den kompletten Output erzeugen, bevor der andere Prozess ihn lesen kann - das gleiche gilt für den Rückweg. Außerdem ist Dateisystem IO teurer als IO im Hauptspeicher.
Er zieht seine Daten per cut/past rein. Bevor der Anwender die Taste losgelassen hat ist die Tempdatei längst geschrieben. Einen File zu löschen der als Parameter übergeben wurde, ist in der Tat ein größes Problem 😉 Auf heutigen Systemen mit Gigs an Hauptspeicher und Teras an Plattenplatz spielt der Platzverbrauch keine Rolle. Das entscheidende Argument für meine Lösung ist aber die Flexibilität. Nochmal das Filterprogramm kann alles mögliche sein sogar ein Script ein AWK oder sonst was sein. Das Rad muss nicht immer neu erfunden werden.
Nein, FIFOs sind schon der richtige Mechanismus. Man muss allerdings aufpassen, dass man beim parallelen Schreiben und Lesen kein Deadlock erzeugt. Da muss man dann Threads oder nonblocking IO verwenden. Zutaten:
Ich habe auf die Schnelle das gefunden. Das kommt Deinem Anwendungsfall schon recht nahe, soweit ich das sehe.
Irgend wie hab ich den eindruck das hier immer nach der kompliziertesten Lösung gesucht wird. cu
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Sorry, ich musste meine Antwort komplett neu schreiben, weil das System diese mehrfach dargestellt hatte (in der Vorschau) und jetzt habe ich festgestellt das bereits weitere Antworten eingetroffen sind. Ich kopiere meine ursprüngliche Antwort daher nochmal. Ich habe jetzt etwas nachgedacht, und die Sache mit den ausgelagerten Tabellen eigentlich erstmal wieder verworfen. Da müsste ich ja auch zumindest eine Plausibilitätsprüfung veranstalten.
Schreib deine Eingansdaten einfach in eine temporäre Datei und übergebe diesen File dann, als Parameter, an ein Programm das du mit popen aufrufst und seinen Output einließt.
Wenn ich nichts besseres finde, wird es wohl darauf hinauslaufen. Aber da man mir an anderer Stelle empfohlen hat mich weiter zu entwickeln, überlege ich ob das nicht ein Anlass ist, mich näher mit Interprozesskommunikation zu beschäftigen. Ich hatte bisher nicht erwähnt, das ich für die GUI FLTK verwende. Auf einer Webseite dazu habe ich folgende interessante Links gefunden (alles die gleiche lange Seite):
Die Mathode Fl::add_fd() in den Beispielen scheint irgendwie auf select() zu basieren. Ob und wieweit man damit das Problem der Blockade bei bidirektionalen Pipe Verbindungen lösen kann, weiss ich aber (noch) nicht. Ich muss da nochmal drauf nachdenken. Bisher habe ich noch keine Beispiele für bidirektionale Kommunikation gefunden, nur Hinweise das sowas prinzipiell geht. Die weiteren Posts lassen mich jedenfalls vermuten das es hier auch um Grundsatzfragen geht, so das es durchaus sinnvoll erscheint die Möglichkeiten (im Test) mal auszuloten.
Er zieht seine Daten per cut/past rein. Bevor der Anwender die Taste losgelassen hat ist die Tempdatei längst geschrieben.
Das ist in der Tat richtig. Selbst bei "suboptimaler" Programmierung (Funktionalität vorausgesetzt) ist der Zeitgewinn in Zehnerpotenzen auszudrücken. Alles was ein Programm macht ist schneller als der User die Tasten drücken kann.
Einen File zu löschen der als Parameter übergeben wurde, ist in der Tat ein größes Problem
Eigentlich auch nicht wirklich, denn das sollte ja der "Auftraggeber" machen wenn der Auftrag abgearbeitet wurde (und nichts blockiert, darüber habe ich aber noch nicht nachgedacht). Vielen Dank erstmal für die bisherigen Tips. Ich muss darüber nochmal nachdenken. Mal sehen wie das morgen Nachmittag aussieht (der Vormittag ist ausgebucht ...). p.s. Für die betreffende Datenmenge würde ich 4kB als Obergrenze ansehen. Tatsächlich lag die größte Datenmenge bisher nur knapp über 1024 Bytes. Jedenfalls weiss der "Auftraggeber" wie groß die tatsächliche Input Menge ist und kann für die Rückgabe eine entsprechende Reserve vorsehen. Allerdings kann das zugehörige Editor Objekt das auch selber handlen sofern das externe Programm nicht alles in einem riesigen MB-Stück zurückliefert, also Zeilenweise arbeitet.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12832
|
microft schrieb: rklm schrieb:
Einen File zu löschen der als Parameter übergeben wurde, ist in der Tat ein größes Problem 😉
Das nicht, aber man möchte es ja so haben, dass die Datei auf jeden Fall verschwindet, wenn der Prozess terminiert. Das sollte tempfile() leisten.
Auf heutigen Systemen mit Gigs an Hauptspeicher und Teras an Plattenplatz spielt der Platzverbrauch keine Rolle.
Ja, aber Platten-IO ist immer noch teurer als Hauptspeicher-IO.
Das entscheidende Argument für meine Lösung ist aber die Flexibilität. Nochmal das Filterprogramm kann alles mögliche sein sogar ein Script ein AWK oder sonst was sein. Das Rad muss nicht immer neu erfunden werden.
Ja und? awk und Co. lesen doch auch von der Standardeingabe und schreiben auf die Standardausgabe. Das ist normal. tr z.B. list sogar nur von Stdin und schreibt auf Stdout. Das ist doch der Kern der Unix-Philosophie.
Irgend wie hab ich den eindruck das hier immer nach der kompliziertesten Lösung gesucht wird.
fork() und exec() bzw. clone() brauchst Du auf jeden Fall, wenn Du einen Prozess starten willst. Dakuan schrieb:
p.s. Für die betreffende Datenmenge würde ich 4kB als Obergrenze ansehen. Tatsächlich lag die größte Datenmenge bisher nur knapp über 1024 Bytes. Jedenfalls weiss der "Auftraggeber" wie groß die tatsächliche Input Menge ist und kann für die Rückgabe eine entsprechende Reserve vorsehen. Allerdings kann das zugehörige Editor Objekt das auch selber handlen sofern das externe Programm nicht alles in einem riesigen MB-Stück zurückliefert, also Zeilenweise arbeitet.
Da das Programm nicht kontrollieren kann, was der andere Prozess tut, musst es auf jeden Fall damit umgehen können, dass das Volumen deutlich höher liegt - und wenn es dann nur den FIFO schließt und nicht mehr weiter liest. Alles andere wäre instabil.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Zu der Variante "Datei als Parameter übergeben" habe ich noch folgendes gefunden:
mkfifo ("telnet.out", 0600);
out = popen ("/bin/telnet 10.0.0.3 1>telnet.out 2>/dev/null", "w");
Da wurde dann allerdings noch etwas mit Timeout und Puffergrößen rumgetrickst.
Ja, aber Platten-IO ist immer noch teurer als Hauptspeicher-IO.
Das bestärkt mich in der Annahme, das ich mich doch weiter mit dem Thema unnamed pipes beschäftigen sollte. Nach dem was ich bisher gelesen habe, muss ich dafür 2 Pipes einrichten.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12832
|
Dakuan schrieb: Zu der Variante "Datei als Parameter übergeben" habe ich noch folgendes gefunden:
mkfifo ("telnet.out", 0600);
out = popen ("/bin/telnet 10.0.0.3 1>telnet.out 2>/dev/null", "w");
Da wurde dann allerdings noch etwas mit Timeout und Puffergrößen rumgetrickst.
Ja, aber das ist nicht wirklich ein Vorteil gegenüber unbenannten FIFOs: Du musst immer noch die Eingabe füttern können (was popen() nicht leistet, wenn ich Dein früheres Posting richtig erinnere) und Du "sparst" lediglich, ein Umbiegen, nämlich von Stdin des Kindes auf das Leseende der Pipe. Dir bleibt aber erhalten, dass Du Schreiben und Lesen gleichzeitig erledigen musst.
Das bestärkt mich in der Annahme, das ich mich doch weiter mit dem Thema unnamed pipes beschäftigen sollte. Nach dem was ich bisher gelesen habe, muss ich dafür 2 Pipes einrichten.
Genau: eine, die den Prozess füttert, und eine andere, die das Ergebnis abholt.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Du musst immer noch die Eingabe füttern können (was popen() nicht leistet, wenn ich Dein früheres Posting richtig erinnere)
Das Problem mit popen() ist, das man entweder nur lesen oder nur schreiben kann. Die Idee in der Fundstelle war wohl die fehlende Richtung durch ein FIFO zu ersetzen und dieses im Aufruf per Umleitung anzusprechen. Aber das geht dann natürlich auch über die Platte. Momentan kämpfe ich mit den gefundenen Beispielen, die ich auf meinen Anwendungsfall angepasst habe (momentan nur eine Richtung). ddd sagt etwas von Buffer-Underrun und dann verschwindet meine Anwendung im Nirwana (ddd sagt: normaly terminated). Ich vermute daher mehrere Fehler und muss das erstmal weiter eingrenzen. An meinem Filter liegt es jedenfalls nicht, denn mit /usr/bin/less passiert genau das gleiche.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
So, nach gefühlten 1000 Tipp- und Denkfehlern und einem entfernten Brett habe ich jetzt eine funktionierende Version. Da sind aber bestimmt noch einige Haken und Ösen drinn, die ich noch nicht erkannt habe. Und für die Kosmetik habe ich auch noch nichts getan (ausser das die Debug Meldungen raus sind).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 | int MainWindow::ext_filter( char * s, char * d )
{
int fd_pa2ch[2];
int fd_ch2pa[2];
int c;
FILE *fp_pa2ch, *fp_ch2pa;
pid_t pid;
if( pipe( fd_pa2ch ) < 0 ) { /* create parent->child pipe */
perror( "pipe1" );
return -1;
}
if( pipe( fd_ch2pa ) < 0 ) { /* create child->parent pipe */
perror( "pipe2" );
return -1;
}
if( (pid = fork()) < 0 ) {
perror( "fork" );
return -1;
}
if( pid > 0 ) { /* parent --------------------------------------------- */
close( fd_pa2ch[0] );
close( fd_ch2pa[1] );
if( (fp_pa2ch = fdopen( fd_pa2ch[1], "w" )) == NULL ) {
perror( "fdopen1" );
return -1;
}
if( (fp_ch2pa = fdopen( fd_ch2pa[0], "r" )) == NULL ) {
perror( "fdopen2" );
return -1;
}
while( 1 ) {
fputc( *s, fp_pa2ch );
if( *s++ == '\0' )
break;
}
fclose( fp_pa2ch );
while( !feof( fp_ch2pa ) ) {
c = fgetc( fp_ch2pa );
if( c > 0 )
*d++ = (char)c;
}
*d = '\0';
fclose( fp_ch2pa );
if( waitpid( pid, NULL, 0 ) == -1 ) {
exit( EXIT_FAILURE );
}
} else { /* child --------------------------------- */
close( fd_pa2ch[1] ); /* disable write direction */
close( fd_ch2pa[0] );
if( fd_pa2ch[0] != STDIN_FILENO ) {
if( dup2( fd_pa2ch[0], STDIN_FILENO ) != STDIN_FILENO )
exit( EXIT_FAILURE );
close( fd_pa2ch[0] );
}
dup2( fd_ch2pa[1], STDOUT_FILENO );
close( fd_ch2pa[1] );
if( execl( "./filter", "filter", NULL ) != 0 ) {
return -1;
}
exit( 0 );
}
return 0;
}
|
Was ich u.A. noch nicht verstanden habe sind die Zeilen 51 bis 55. In einem meiner Bücher steht das man es so machen muss, also das prüfen auf STDIN_FILENO. Im Internet finde ich aber meist eine vereinfachte Version, wie ich sie in den Zeilen 56 und 57 für die Gegenrichtung verwendet habe. Vielleicht kann mir jemand sagen, was es damit auf sich hat. Das funktioniert also erstmal, solange keine Fehler passieren. Ich habe aber noch keine Ahnung, was ich im Fehlerfall mit den Filedisciptoren machen muss. Jedenfall soll im Fehlerfall das eigentliche Programm weiterlaufen, nur das der Text dann nicht modifiziert wird. Bei den gefundenen Beispielen wird immer gleich das ganze Programm beendet, was bei einer echten Anwendung nicht sein darf. Und ja, die Pufferung der empfangenen Zeichen wird noch geändert.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12832
|
Dakuan schrieb:
Was ich u.A. noch nicht verstanden habe sind die Zeilen 51 bis 55. In einem meiner Bücher steht das man es so machen muss, also das prüfen auf STDIN_FILENO. Im Internet finde ich aber meist eine vereinfachte Version, wie ich sie in den Zeilen 56 und 57 für die Gegenrichtung verwendet habe. Vielleicht kann mir jemand sagen, was es damit auf sich hat.
Wenn ich das auf die Schnelle richtig verstehe, dient der Test dazu, dass Du nicht einen Dateideskriptor auf sich selbst klonst, was einerseits keinen Sinn ergibt und andererseits ggf. schaden kann.
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6345
Wohnort: Hamburg
|
Ich dachte immer, das so etwas nicht passieren kann. Aber in diesem Zusammenhang fällt mir auf, dass die Beispiele mit Überprüfung Konsolen Anwendungen sind und die ohne mit GUI. Ich kämpfe aber noch mit der Fehlerbehandlung. Wenn der Anwender z.B. einen falschen Programmnamen eingibt, oder das Filterprogramm während der Laufzeit des Hauptprogramms "abhanden kommt", bleibt das Programm hängen. Es scheint so, das execl() dann mit Ergebnis 0 oder gar nicht terminiert. Debuggen kann ich das nicht richtig, da gdb/ddd immer im Parent Prozess bleibt. In der Konsole kommt dann folgendes:
pdb: ../../src/xcb_io.c:182: process_responses: Assertion `((int) (((dpy->last_request_read)) - ((dpy->request))) <= 0)' failed.
Aborted
womit ich nichts anfangen kann. (ja, der Programmname ist unglücklich gewählt, es ist nicht der Python Debugger).
Das Hauptprogramm lässt sich aber noch bedienen und korrekt beenden. Aber der Thread im Hauptprogramm bleibt wohl irgendwo hängen. Bei jedem Aufruf des Filters entsteht im Fehlerfall ein neuer Prozess der nicht beendet wird. Diese verschwinden erst, wenn das Hauptprogramm beendet wird.
@qube:~$ ps -a
PID TTY TIME CMD
6508 pts/2 00:02:33 htop
7394 pts/0 00:00:00 pdb
7395 pts/0 00:00:00 pdb
7408 pts/0 00:00:00 pdb
7409 pts/3 00:00:00 ps
@qube:~$ ps -a
PID TTY TIME CMD
6508 pts/2 00:02:37 htop
7416 pts/3 00:00:00 ps
@qube:~$
Irgendwie benötige ich eine andere Fehlerbehandlung. Am besten währe eine Zeitüberwachung. Gibt es dazu "übliche" Vorgehensweisen?
|