tutorials.de Buch-Aktion 05/2012
Like Tree1Danke
  • 1 Beitrag von deepthroat
ERLEDIGT
JA
ANTWORTEN
6
ZUGRIFFE
7652
EMPFEHLEN
  • An Twitter übertragen
  • An Facebook übertragen
AUF DIESES THEMA
ANTWORTEN
  1. #1
    psi81 psi81 ist offline Grünschnabel
    Registriert seit
    Apr 2007
    Beiträge
    2
    Hallo zusammen,

    ich verzweifle gerade an einer Sache, von der ich dachte, dass sie eigentlich zu den Grundfunktionen der stdio gehören:

    Ich versuche, aus meinem C/C++ Programm heraus ein weiteres Programm aufzurufen, und sowohl stdin als auch stdout auf pipes umzulenken, damit ich Zugriff sowohl auf Ein- als auch auf die Ausgabe habe.

    Unter Windows gibt es hierfür eine WinAPI Funktion, "CreateProcess", der ich meine Kanäle übergeben kann, das Problem ist nur... ich brauch das ganze für Linux (SuSE 10.1). Weiß jemand Rat?

    Gruß
    Achim
     

  2. #2
    deepthroat deepthroat ist offline Mitglied Diamant
    tutorials.de Premium-User
    Registriert seit
    Jun 2005
    Beiträge
    8.168
    Hi.
    Zitat Zitat von psi81 Beitrag anzeigen
    Hallo zusammen,

    ich verzweifle gerade an einer Sache, von der ich dachte, dass sie eigentlich zu den Grundfunktionen der stdio gehören:

    Ich versuche, aus meinem C/C++ Programm heraus ein weiteres Programm aufzurufen, und sowohl stdin als auch stdout auf pipes umzulenken, damit ich Zugriff sowohl auf Ein- als auch auf die Ausgabe habe.
    Laut POSIX Standard muß eine Pipe lediglich unidirektional sein - nicht bidirektional. Unter Linux ist das auch der Fall, so das verhindert wird das es zu einem Deadlock kommt.

    Zitat Zitat von psi81 Beitrag anzeigen
    Unter Windows gibt es hierfür eine WinAPI Funktion, "CreateProcess", der ich meine Kanäle übergeben kann, das Problem ist nur... ich brauch das ganze für Linux (SuSE 10.1). Weiß jemand Rat?
    Du kannst das ganze mit fork, exec* und mehreren Pipes unter Linux realisieren:
    Code c:
    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
    
    #include <sys/types.h>
    #include <unistd.h>
    #include <stdio.h>
     
    pid_t pid;
    int p2c[2], c2p[2];
     
    pipe(p2c);
    pipe(c2p);
     
    pid = fork();
     
    if (pid == 0) { /* child */
      dup2(p2c[0], stdin);
      dup2(p2c[1], stdout);
     
      execvp(...); /* anderes Programm starten */
    } else {
      FILE *tochild, *fromchild;
     
      close(p2c[0]);
      tochild = fdopen(p2c[1], "w");
     
      close(c2p[1]);
      fromchild = fdopen(c2p[0], "r");  
     
      /* tochild / fromchild benutzen */  
    }
    Also so ungefähr sollte das funktionieren. Vor allem Fehlerbehandlung hab ich komplett weggelassen...

    Gruß
    RyoShinzo bedankt sich. 
    If at first you don't succeed, try again. Then quit. No use being a damn fool about it.

  3. #3
    Rene Albrecht Rene Albrecht ist offline Mitglied Gold
    Registriert seit
    Oct 2003
    Ort
    Hamburg
    Beiträge
    125
    Danach suche ich gerade. Die grds. Idee ist mir klargeworden, danke für die kurze Erläuterung. Hat jemand dafür vielleicht ein komplettes Beispiel?

    Gruß
    René
     

  4. #4
    psi81 psi81 ist offline Grünschnabel
    Registriert seit
    Apr 2007
    Beiträge
    2
    Ich habe die Lösung inzwischen auch zu 90% (würd ich sagen) verstanden, ich kann mal versuchen, das in Worten wiederzugeben. Danach hänge ich mal ein Beispiel an, wie ich das gelöst habe.

    Wenn ich das richtig verstanden habe, und mein Testcode hat zumindest funktioniert, dann funktioniert ein CGI- Call wie folgt:

    Zuerst mal erzeuge ich 2 Pipes. Jede Pipe hat logischerweise 2 Handles, nämlich eins am Lese- und eins am Schreibende.

    Die eine Pipe ist quasi die Richtung "Parent -> Child", die andere ist die Richtung "Child -> Parent".

    Dann wird geforkt.

    Im Child wird zunächst STDIN geschlossen (
    Code :
    1
    
    close(0)
    ). Grund dafür ist der, dass der (spätere) CGI- Prozess ja keine Pipe öffnen soll, sondern diese Pipes auf STDIN und STDOUT gemappt werden sollen, damit der Child direkt darüber mit dem Parent kommunizieren können.


    Nachdem STDIN geschlossen wurde, kann jetzt das Lese- Handle unserer "Parent->Child" Pipe dupliziert werden:
    Code :
    1
    
    dup(p0[0])
    .

    p0 ist hierbei unsere Parent -> Child Pipe, p1 die umgekehrte Richtung.

    Damit wird das Handle auf den gerade geschlossenen Kanal (nämlich STDIN) dupliziert.

    Als nächstes machen wir dasselbe mit STDOUT:
    Code :
    1
    
    close(1); dup(p1[1])
    .

    In diesem Fall wurde das Schreibende der anderen Richtung (p1) auf den wiederum zuvor geschlossenen Kanal (STDOUT) gemappt.

    Jetzt kann der Childprozess seine nicht benötigten Handles der Pipes schließen, nämlich die Enden, die der Parent braucht.

    ------------------

    Im Parent machen wir dasselbe, nur dass wir in dem Fall nicht noch STDIN und STDOUT umbiegen müssen. Wir öffnen stattdessen einfach die Handles und schließen die überflüssigen, das wars auch schon.

    ------------------

    Die letzte Anweisung im Child- Zweig lautet dann execlp(Programmname, Argumente, NULL). Damit wird das Programm beendet und an dessen Stelle tritt dann das aufgerufene Kommando. Der somit neu erzeugte Prozess kennt weder p0 noch p1, aber STDIN und STDOUT werden automatisch vererbt. Beim Beenden des Prozesses werden diese Handles standardmäßig auch wieder von der Shell geschlossen, so dass da keine offenen Handles zurückbleiben.

    -------------------

    Beispiel:

    Meine CGIComm.h und .cpp:

    CGIComm.h

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    #ifndef CGICOMM_H
    #define CGICOMM_H
     
    #include <stdlib.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
     
    typedef struct CGI
    {
      FILE *in;
      FILE *out;
      int pid;
    };
     
    CGI *OpenCGI(char *Command, char *Args); //CGI- Prozess erzeugen
    char* ReadCGI(CGI* cgi); //Vom CGI- Prozess lesen
    void WriteCGI(CGI* cgi, char* Data); //An CGI- Prozess senden
    void CloseCGI(CGI *cgi); //CGI- Handles parentseitig schließen
     
    int CGICommDirect(char* Command, char* Data, char* Answer, int AnsLen); //Kurzbefehl für alles zusammen
     
    #endif

    --------------------------------

    CGIComm.cpp

    Code :
    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
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    
    #include "CGIComm.h"
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <errno.h>
     
    CGI *OpenCGI(char *Command, char *Args)
    {
      FILE *f = fopen(Command, "r");
      if (f == NULL)
        return NULL;
      fclose(f);
     
      CGI *cgi = (CGI*)malloc(sizeof(CGI));
      int p0[2], p1[2];
      pipe(p0);
      pipe(p1);
     
      int cid = fork();
      if (!cid)
      {
        close(0);
        dup(p0[0]);
        close(1);
        dup(p1[1]);
        close(p0[1]);
        close(p1[0]);
     
        execlp(Command, Args, NULL);
      }
      else
      {
        cgi->in = fdopen(p0[1], "w");
        close(p0[0]);
        cgi->out = fdopen(p1[0], "r");
        close(p1[1]);
        cgi->pid = cid;
      }
      return cgi;
    }
     
    char* ReadCGI(CGI* cgi)
    {
      char* Data = NULL;
      if (cgi == NULL)
        return NULL;
      int Len = 0;
      int DataLen = 0;
      char Buffer[128];
      while ((Len = fread(Buffer, 1, 128, cgi->out)))
      {
        char* NData = (char*)malloc(Len + DataLen);
        memcpy(NData, Data, DataLen);
        memcpy(&NData[DataLen], Buffer, Len);
        if (Data != NULL)
          free(Data);
        Data = NData;
        DataLen += Len;
      }
      if (DataLen == 0)
        return NULL;
      char *NData = (char*)malloc(DataLen+1);
      memcpy(NData, Data, DataLen);
      NData[DataLen] = 0;
      if (Data != NULL)
        free(Data);
      Data = NData;
      return Data;
    }
     
    void WriteCGI(CGI* cgi, char* Data)
    {
      if (cgi == NULL)
        return;
      int Len = strlen(Data)+1;
      fputs(Data, cgi->in);
      fclose(cgi->in);
      cgi->in = NULL;
    }
     
    void CloseCGI(CGI *cgi)
    {
      if (cgi == NULL)
        return;
     
      int result;
      waitpid(cgi->pid, &result, 0);
     
      if (cgi->in != NULL)
        fclose(cgi->in);
      fclose(cgi->out);
      free(cgi);
    }
     
     
    int CGICommDirect(char* Command, char* Data, char* Answer, int AnsLen)
    {
      int pfds0[2], pfds1[2];
      FILE *fp_in, *fp_out;
     
      pipe(pfds0);
      pipe(pfds1);
     
      int cid = fork();
     
      if (!cid) {
        close(0);
        dup(pfds1[0]);
        close(1);
        dup(pfds0[1]);
        close(pfds0[0]);
        close(pfds1[1]);
        execlp(Command, NULL, NULL);
      } else {
        fp_in  = fdopen(pfds0[0], "r");
        close(pfds0[1]);
        fp_out = fdopen(pfds1[1], "w");
        close(pfds1[0]);
        fputs(Data, fp_out);
        fclose(fp_out);
        fgets(Answer, AnsLen, fp_in);
        fclose(fp_in);
        wait(NULL);
      }
      return 0;
    }

    -------------------------------

    Bei Fragen fragen

    Der Code steht übrigens zur freien Verwendung zur Verfügung, ich übernehme allerdings keine Garantie auf Funktionsfähigkeit oder Zweckdienlichkeit (halt die Haftungsauschlussklausel und so...)
     

  5. #5
    RyoShinzo RyoShinzo ist offline Grünschnabel
    Registriert seit
    Nov 2011
    Beiträge
    2
    Hallo,

    tut mir leid diesen alten Thread wieder ausgraben zu müssen, aber er beinhaltet genau das, wonach ich suche.

    Ich habe allerdings noch eine Frage zum Code von deepthroat. Kann es sein, dass in deinem Code ein kleiner Fehler ist?

    Müsste dieser Code
    Code :
    1
    2
    3
    
    if (pid == 0) { /* child */
      dup2(p2c[0], stdin);
      dup2(p2c[1], stdout);
    nicht eher so
    Code :
    1
    2
    3
    
    if (pid == 0) { /* child */
      dup2(p2c[0], stdin);
      dup2(c2p[1], stdout);
    sein?

    Für was es den Integer (p2c bzw. c2p) jeweils 2 mal gibt, habe ich noch nicht verstanden. Kann das eventuell noch jemand erklären?

    Gruß

    Ryo
     

  6. #6
    deepthroat deepthroat ist offline Mitglied Diamant
    tutorials.de Premium-User
    Registriert seit
    Jun 2005
    Beiträge
    8.168
    Hi.
    Zitat Zitat von RyoShinzo Beitrag anzeigen
    Ich habe allerdings noch eine Frage zum Code von deepthroat. Kann es sein, dass in deinem Code ein kleiner Fehler ist?

    Müsste dieser Code
    Code :
    1
    2
    3
    
    if (pid == 0) { /* child */
      dup2(p2c[0], stdin);
      dup2(p2c[1], stdout);
    nicht eher so
    Code :
    1
    2
    3
    
    if (pid == 0) { /* child */
      dup2(p2c[0], stdin);
      dup2(c2p[1], stdout);
    sein?
    Ja, da hast du allerdings recht.
    Zitat Zitat von RyoShinzo Beitrag anzeigen
    Für was es den Integer (p2c bzw. c2p) jeweils 2 mal gibt, habe ich noch nicht verstanden. Kann das eventuell noch jemand erklären?
    Was genau meinst du? Warum p2c und c2p ein Array ist? Oder warum p2c und c2p notwendig sind?

    Gruß
     
    If at first you don't succeed, try again. Then quit. No use being a damn fool about it.

  7. #7
    RyoShinzo RyoShinzo ist offline Grünschnabel
    Registriert seit
    Nov 2011
    Beiträge
    2
    Habe die Funktionsweise von pipe() nun verstanden und weiß, warum du ihm ein Array übergibst.

    Nochmal vielen Dank für deinen Code, der hat mir sehr weitergeholfen!

    €: Hast du eventuell noch eine Idee, wie ich im Parent-Prozess feststellen kann, wann der Child-Prozess fertig ist? Denn ich möchte ja solange der Child-Prozess lebt im Parent-Prozess die Ausgaben mitlesen und wenn der Child-Prozess beendet ist, soll das Programm beendet werden.

    €2: Folgendes Abbruchkriterium scheint wohl zu funktionieren:
    Code :
    1
    
    while (readResult > 0);
    Geändert von RyoShinzo (02.11.11 um 16:30 Uhr)
     

Ähnliche Themen

  1. stdout in Logfile umleiten
    Von DestroyerJoe im Forum Java
    Antworten: 2
    Letzter Beitrag: 21.04.09, 11:31
  2. Ausgaben eines Prozesses in Datei umleiten
    Von Eroli im Forum .NET Café
    Antworten: 8
    Letzter Beitrag: 31.07.08, 15:50
  3. Stdout eines Prozesses ermitteln
    Von Hroudtwolf im Forum C/C++
    Antworten: 2
    Letzter Beitrag: 17.11.07, 19:43
  4. [C] Linux: stdout in pipe umleiten
    Von plonk im Forum C/C++
    Antworten: 3
    Letzter Beitrag: 25.04.04, 16:06
  5. Antworten: 1
    Letzter Beitrag: 16.06.03, 16:31