Kurzer Prozess mit Qt

Qt: C-Programme mit Interprozesskommunikation
Kommentare

Qt ist ein mächtiges Framework für die Programmierung plattformunabhängiger, grafischer Oberflächen in C++. Es unterstützt Internationalisierung, Webprogrammierung jeder Art, Skriptsprachen, XML, bietet Multimediasupport und stellt diverse Datenbankentreiber bereit. Last but not least können Sie aus einer Qt-Anwendung Programme starten, die in einer anderen Sprache geschrieben wurden sowie deren Ausgabe lesen und verarbeiten. Wie das geht, zeigt dieser Artikel.

Bevor es losgeht: Der Artikel richtet sich vornehmlich an Entwickler, die bereits über C++-Kenntnisse verfügen und wenigstens grundlegend mit Qt vertraut sind (Konzepte wie zum Beispiel Signals and Slots werden im Artikel nur skizziert). Mit Java-und C#-Kenntnissen ist es ebenfalls möglich, dem Text zu folgen. Und: Sämtliche Programme wurden unter Ubuntu 12.4 LTS (64 Bit) getestet.

Kurz ein Wort zu Qt: Das Framework wird vielerorts für die verschiedensten Zwecke eingesetzt, denn es bietet nahezu unbegrenzte Möglichkeiten für die plattformunabhängige GUI-Entwicklung (GUI = Graphical User Interface). Bekannte Anwendungen sind: KDE, Opera, Google Earth, Skype, Ubuntu13.10 uvm.

Um die Beispiele testen zu können, prüfen Sie bitte, ob Qt und Qt Creator an Bord sind (überdies müssen g++ und make installiert sein). Das geht so:

xxx54 @ hamlet:~$ qtcreator -version
Qt Creator 2.4.1 based on Qt 4.8.1

Falls Qt Creator nicht installiert ist, beziehen Sie die IDE (Integrated Development Environment) unter Ubuntu folgendermaßen:

xxx54 @ hamlet:~$ sudo apt-get install qtcreator

Auf anderen Linux-Distros rufen Sie einfach den passenden Package Manager auf. Benötigten Sie weiterführende Informationen über Qt Creator, rufen Sie diesen Befehl auf (unter Ubuntu):

apt-cache show qtcreator

Qt Creator startet auf der Kommandozeile so:

xxx54 @ hamlet:~$ qtcreator &

Wenn Sie jetzt schon neugierig sind, können Sie unter „Projekt öffnen“ (Willkommenansicht von Qt Creator, Abb. 1) das Projekt des Autors einsehen (es heißt qt_process und ist auf www.entwickler.de herunterladbar). Darauf wird später detailliert eingegangen.

Nun zum Thema. Es soll aus einem Qt-GUI ein C-Programm aufgerufen werden. Die Ausgabe dieses Programms wird dann in einem von uns gebauten GUI angezeigt und weiter verarbeitet (mit einer einfachen Suche). Qt stellt für solche Zwecke die Klasse QProcess zur Verfügung, die es im Grunde einfach macht, solche IPC-Mechanismen zu realisieren.

Die IDE Qt Creator – der Willkommendialog

Aufmacherbild: geometric design von iStockphoto / Urheberrecht: Silense [ header = Seite 2: C wie Client ]

C wie Client

In Listing 1 sehen Sie das C-Programm. Es handelt sich um einen simplen Netzwerkclient (Stichwort Socket-API), der die index.html vom Heise-Verlag lädt und auf der Konsole ausgibt. In der main-Funktion wird ein TCP-Socket (es gibt auch UDP-Sockets), das ist ein Kommunikationsendpunkt, erzeugt. Die Funktion memset() setzt die Struktur sockaddr_in auf 0 (für eine saubere Initialisierung). Diese struct sockaddr_in (hier für IPv4) ist für den Verbindungsaufbau zuständig und erhält im Folgenden mehrere Parameter, um die Connection zum Server herzustellen (AF_INET für IPv4). Des Weiteren wird Port 80 spezifiziert (htons(80) transformiert den Dezimalwert 80 in die Network-Byteorder).

Die IP kommt schließlich über argv[1] in das Programm und wird unter Verwendung der Funktion inet_pton() an sockaddr_in übergeben (ebenfalls in Network-Byteorder).

Danach verbindet sich connect() mittels des 3-Way-Handshakes mit dem Server. Anschließend schreibt die write()-Funktion das GET an den Server; mit read() lesen Sie das Ergebnis aus. Kompilieren Sie das Programm auf der Konsole:

xxx54 @ hamlet:~$ gcc -o cli cli.c -Wall -pedantic -std=c99

Der Aufruf (die IP ist die des Heise-Verlags):

./cli 193.99.144.85

Achtung: Dieses Programm läuft nur unter Linux.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define LEN 512
#define SA struct sockaddr

//-----------------------------------------

int main(int argc, char **argv) {

  int sockfd;
  char buf[LEN];
  const char *command = "GET /index.html HTTP/1.0rnrn";
  struct sockaddr_in servaddr;

  sockfd = socket(AF_INET, SOCK_STREAM, 0);

  memset(&servaddr, 0, sizeof(servaddr) );
  servaddr.sin_family = AF_INET;
  servaddr.sin_port   = htons(80);
  inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
  // inet_pton(AF_INET, "193.99.144.85", &servaddr.sin_addr); // www. heise.de

  connect(sockfd, (SA *) &servaddr, sizeof(servaddr) );
  write(sockfd, command, strlen(command) );

  ssize_t n;
  while ( (n = read(sockfd, buf, LEN)) ) {
    buf[n] = 0;
    fputs(buf, stdout);
  }

  close(sockfd);
  return EXIT_SUCCESS;
}

 

[ header = Seite 3: Die GUI wird gebaut ]

Die GUI wird gebaut

Falls Sie es noch nicht getan haben, öffnen Sie bitte das mitgelieferte Projekt qt_process (Sie finden es auf www.entwickler.de) und Doppelklicken Sie auf qt_process.pro. In Listing 2 sehen Sie die Header-Datei für das Hauptprogramm (mainwindow.h).

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui>
#include <QString>

class MainWindow : public QWidget {

  Q_OBJECT
public:

MainWindow(QWidget *parent = 0);
  ~MainWindow() {}

  void myFind(QString text, QString patt);

private:
  QVBoxLayout *AllBox, *VBox;
  QHBoxLayout *HBox;

  QGroupBox *VGroup, *HGroup;
  QPushButton *buttQuit, *buttSearch;
  QLineEdit *le;
  QTextEdit *editor;
  QProcess *asynProc;

public slots:
  void findItem();
  void startProc();
  void readProc();
};

#endif // MAINWINDOW_H

Das ist das Interface für die C++-Implementierungsdatei. Die Klasse MainWindow leiten wir von QWidget ab – die übliche Vorgehensweise für kleinere Fenster und Dialoge. Es folgen Konstruktor/Destruktor und die public-Methode myFind(). Im Folgenden deklarieren wir die benötigten Widgets (Layouts, Buttons, LineEdits und auch QProcess). Am Ende des Listings sehen Sie die Slots. Listing 3 zeigt die Implementierung (mainwindow.cpp).

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {

  asynProc   = new QProcess();
  buttQuit   =  new QPushButton("&Quit");
  buttSearch =  new QPushButton("&Search");
  le         = new QLineEdit;
  editor     = new QTextEdit;

  VBox = new QVBoxLayout;
  VBox->addWidget(le);
  VBox->addSpacing(25);
  VBox->addWidget(editor);
  VBox->addSpacing(20);

  HBox = new QHBoxLayout;
  HBox->addWidget(buttSearch);
  HBox->addWidget(buttQuit);
  HBox->setSizeConstraint(QLayout::SetFixedSize);

  HGroup = new QGroupBox("Action");
  HGroup->setLayout(HBox);

  VGroup = new QGroupBox("IPC");
  VGroup->setLayout(VBox);

  AllBox = new QVBoxLayout(this);
  AllBox->addWidget(VGroup);
  AllBox->addWidget(HGroup);

  connect(le, SIGNAL(returnPressed()), this, SLOT(startProc() ) );
  connect(asynProc, SIGNAL(readyRead() ), this, SLOT(readProc() ) );
  connect(buttQuit, SIGNAL(clicked()), qApp, SLOT(quit() ) );
  connect(buttSearch, SIGNAL(clicked()), this, SLOT(findItem() ) );

setLayout(AllBox);
  setFixedSize(400, 450);
  setWindowTitle("QProcess!");
}

//--- slots ---------------------------------------------

void MainWindow::startProc() {

  asynProc->start("./cli", QStringList() << le->text() );
}

void MainWindow::readProc() {

  if (asynProc->state() == QProcess::Running) {

    QByteArray s;
    while (! (s = asynProc->readLine()).isEmpty() ) {
      QString output(QString::fromLatin1(s));
      editor->insertPlainText(output);
    }
  }
}

void MainWindow::findItem() {

  bool ok;
  QString item = QInputDialog::getText
    (this, "Search", "Search", QLineEdit::Normal, "Enter item", &ok);

  if (ok && !item.isEmpty() ) {

    QTextDocument *td = editor->document();
    QString text = td->toPlainText();
    myFind(text, item);
  }
}

//--- normal methods-------------------------------------

void MainWindow::myFind(QString text, QString patt) {

  QString s_b = "<STRONG><font color="#FF0000">";
  QString s_e = "</font></STRONG>";

  int pos_beg = text.indexOf(patt);

  while (pos_beg != -1) {

    text.insert(pos_beg, s_b);
    text.insert(pos_beg + patt.size() + s_b.size(), s_e);

    pos_beg = text.indexOf(patt, pos_beg + patt.size() + s_b.size() + s_e.size() );
  }

  editor->setHtml(text);
}

Der Quelltext ist etwas länger, aber lassen Sie sich nicht beeindrucken. Es passiert gar nicht so viel. Im Konstruktor wird zuerst das Prozessobjekt erzeugt; zudem werden zwei QPushButtons, ein QLineEdit sowie ein QTextEdit angelegt. Jetzt definieren wir die Layouts: Zur Verwendung kommen QVBoxLayout (für die vertikale Ausrichtung der Widgets, also untereinander) und QHBoxLayout (für die horizontale Ausrichtung der Widgets, also nebeneinander) – beiden Layouts werden die oben erwähnten Widgets jeweils mit der Methode addWidget() übergeben. Die Methode addSpacing() sorgt selbstredend für den Abstand zwischen den Widgets.

Die Layouts (VBox, Hbox) packen wir wiederum in eine QGroupBox, die nochmals in ein QVBoxLayout gesteckt wird. Das Prinzip ist einfach: Die Layouts werden solange ineinander verschachtelt, bis eine sinnvolle und überschaubare Struktur entstanden ist.

Die folgenden vier connect-Methoden (connect() ist thread-safe und gehört zur Klasse QObject) realisieren das Signals-and-Slot-Konzept. Wird ein Signal ausgesendet (beispielsweise durch Klick auf einen fiktiven Button Open file), kann es an anderer Stelle, zum Beispiel vom QTextEdit abgefangen werden (es könnte Text in das Widget eingefügt werden).

Es folgt dann die Implementierung der drei Slot-Methoden. Sie verrichten die eigentliche Arbeit und reagieren, wie bereits erwähnt, auf die passenden Signale. Die Methode void MainWindow::startProc() startet den Prozess (das kompilierte C-Programm; es heißt bekanntlich cli und muss im Projektverzeichnis liegen). Dazu einige Ausführungen: Hier handelt es sich um Interprozesskommunikation (IPC). Allgemein gesagt bedeutet IPC, dass eine Anwendung über ihre Standardeingabe die Ausgabe einer anderen Anwendung einlesen kann (ein Beispiel unter Linux sind Pipes auf der Konsole: SORT MY_FILE | UNIQ). Glücklicherweise ist QProcess von QIODevice abgeleitet – was das Lesen aus beziehungsweise das Schreiben in Prozesse sehr komfortabel macht. Man unterscheidet des Weiteren zwischen synchronen und asynchronen Prozessen. Erstere blockieren ein GUI solange, bis der Prozess beendet ist. Der asynchrone Prozess in unserem Programm verwendet hingegen das Signal readRead():

connect(asynProc, SIGNAL(readyRead() ), this, SLOT(readProc() ) );

Wird dieses Signal ausgelöst, „weiß“ der passende Slot readProc(), dass jetzt die Ausgabe des C-Clients fertig für den Lesevorgang ist (somit wird das GUI nicht „eingefroren“). Die Slot-Methode findItem() wird unten erläutert (es handelt sich um eine Suche).

[ header = Seite 4: All together + Fazit]

All together

Jetzt fügt sich alles zusammen. Listing 4 zeigt die main-Methode.

#include <QtGui/QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[]) {

  QApplication a(argc, argv);
  QApplication::setStyle(new QWindowsStyle);

  MainWindow *w = new MainWindow;
  w->show();

  return a.exec();
}

Bauen Sie das Projekt qt_process (es enthält alle hier gezeigten Dateien plus Makefile und Projektdatei) in Qt Creator mit dem Shortcut STRG + B. Die Ausgabe könnte so aussehen:

Leaving directory '/home/xxx54/artikel/SuS/DE/artikel/process/qt_process'
13:03:20: Der Prozess "/usr/bin/make" wurde normal beendet

Mit STRG + R starten Sie das GUI (Abb. 2). Sie sehen ein kleines Fenster mit einem QLineEdit, einem QTextEdit sowie zwei QPushButtons.

Das GUI in Aktion

Geben Sie in das Eingabefeld oben die IP 193.99.144.85 (www.heise.de) ein und betätigen Sie die ENTER-Taste. Nach einem kurzen Augenblick wird im QTextEdit die Ausgabe des C-Programms angezeigt. Dafür sorgt diese Zeile:

connect(le, SIGNAL(returnPressed()), this, SLOT(startProc() ) );

Hier passiert Folgendes: Wenn Sie im QLineEdit die IP eingeben und ENTER drücken, wird das Signal returnPressed() ausgelöst und der SLOT startProc() stößt das C-Programm an. Kurz darauf sehen Sie die Ausgabe. Dieselbe wird wiederum mithilfe der Methode readProc() bewerkstelligt. In der Methode readProc() fragen wir zuerst den Prozessstatus ab: Läuft der Prozess, gibt die while-Schleife Zeile für Zeile der Eingabe von cli aus.

Die Benutzung der Buttons ist selbsterklärend. Mit dem Shortcut ALT + Q beenden Sie die Anwendung. Mit ALT + S durchsuchen Sie den Text im QTextEdit: Hier kommt die Methode findItem() ins Spiel, welche ihrerseits myFind() aufruft. Es handelt sich, nomen est omen, um eine Suche, die alle Treffer im Text anzeigt (geben Sie beispielsweise den Suchbegriff heise ein):

connect(buttSearch, SIGNAL(clicked()), this, SLOT(findItem() ) );

Wird das Signal clicked() ausgelöst, fängt der SLOT findItem() dieses Signal ab und stößt die Suche an (jeder Treffer wird mit Rot markiert). Dafür sorgen diese Zeilen aus myFind():

QString s_b = "<STRONG><font color="#FF0000">";
QString s_e = "</font></STRONG>";

Schlussendlich interpretiert die Zeile editor->setHtml(text); in myFind() den HTML-Quelltext der index.html als Webseite. Das war’s.

Fazit

Programmieren mit Qt macht augenscheinlich Spaß, weil es so effektiv ist. Zudem ist Qt für kleinere Anwendungen schnell zu erlernen und es ist last but not least plattformunabhängig. Kleine Fenster und Dialoge sind im Handumdrehen erstellt, vorausgesetzt, Sie verfügen über grundlegende C++-Kenntnisse oder etwas Erfahrung in der GUI-Entwicklung mit anderen Sprachen. Überdies kann Qt kostenlos bezogen werden, solange Sie Open Source-Software schreiben. Viel Spaß beim Hacken.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -