Allesfresser

KDEs flexible I/O-Architektur

Wissen | Know-how

Der Browser zeigt die Fotos auf der Digitalkamera als Vorschaubild an, via Drag & Drop wandern sie direkt auf den Desktop. Mit dem neuen I/O-Konzept lässt sich das unter KDE 2 vergleichsweise einfach realisieren.

Der Browser in KDE 2, Konqueror, unterstützt eine Reihe von Protokollen und lässt so die Grenze zwischen lokaler Festplatte und dem Internet verschwimmen: Als Dateimanager arbeitet er lokal auf dem Dateisystem. Er kann aber auch als FTP-Client agieren, per HTTP beziehungsweise HTTPS auf Webserver zugreifen, selbst Gopher-Server sind ihm nicht fremd. Je nach Protokoll und Benutzerrechten kann Konqueror damit Dateien lesen, schreiben, umbenennen und auch löschen.

Diese Funktionen sind jedoch nicht speziell für den Browser implementiert worden, sondern generell für alle KDE-Applikationen verfügbar. Öffnet man beispielsweise im Editor KWrite den Dateidialog, so kann man dort genauso wie im Konqueror eine URL eingeben. KWrite lädt dann bei Bedarf die durch die URL spezifizierte Datei aus dem Netz und öffnet sie. Beim Speichern einer solchen Datei öffnet KWrite erneut eine Netzwerkverbindung und lädt sie wieder auf den Server hoch. All dies geschieht völlig transparent für den Benutzer - er muss sich keine Gedanken darüber machen, ob die Daten via FTP, SMB oder HTTP transportiert werden.

Selbst der Entwickler von KWrite braucht sich nicht darum zu kümmern, wo die angeforderten Dateien herkommen. Er programmiert einfach das Laden und Speichern von lokalen Dateien und überlässt der KDE-Bibliothek KIO den Rest. KIO implementiert die entsprechenden Funktionen und ermöglicht dabei automatisch das Browsen und das Herunter- beziehungsweise Hochladen der Dateien.

Konqueror nutzt zur Anzeige des Hello-Textes die HTML-Komponente.

Dieses Konzept hat mehrere Vorteile: So müssen sich die Anwendungsprogramme (beziehungsweise deren Entwickler) nicht mit den Details der verschiedenen Protokolle befassen. Anwendungsprogramme und I/O-Module können völlig unabhängig voneinander entwickelt werden. Installiert der Benutzer neue Protokollmodule, erhalten alle Programme automatisch Zugriff auf die neuen Funktionen, ohne dass eine Anpassung erforderlich ist.

Ein wichtiges Feature von KIO ist die asynchrone Bearbeitung der Anfragen. Während des Downloads blockiert das Programm nicht, wie man am Beispiel des Konqueror sehen kann. Der Download läuft im Hintergrund ab, während der Benutzer das Programm ganz normal weiter bedienen kann. Ein langsamer oder ausgefallener Server kann das Programm nicht zum Stillstand bringen.

Sofern das Protokoll der eingetippten URL das Auflisten von Verzeichnissen unterstützt, kontaktiert KIO schon während des Tippens den Server und komplettiert die Angaben automatisch, sobald sie eindeutig sind. Die einzigen Kontakte, die ein Anwender mit KIO hat, sind eventuelle Passwortdialoge, die Durchsatzanzeige, die es auch ermöglicht, einen Upload oder Download abzubrechen, und im schlimmsten Falle ein Fehlerdialog, falls eine Datei nicht vorhanden oder der Server nicht erreichbar ist.

Die KIO-Bibliothek selbst ist modular aufgebaut. Die einzelnen I/O-Module werden als kioslaves bezeichnet. Jeder Slave ist für mindestens ein bestimmtes Protokoll zuständig. Dabei muss es sich nicht unbedingt um ein Netzwerkprotokoll handeln. Ein Slave kann auch das Lesen und Schreiben der Datenstruktur eines Packformates wie tar oder gzip implementieren oder die Musikstücke von einer Audio-CD auslesen.

Die Anfragen an KIO haben generell die Form einer URL, wobei das erste Element das jeweilige Protokoll festlegt. Will der Benutzer die Datei ftp://ftp.kde.org/pub/kde/README_FIRST herunterladen, überprüft KIO, ob ein Slave für das ftp-Protokoll vorhanden ist. Da der ftp-Slave zum Standardumfang von KDE 2 gehört, wird dieser mit der Bearbeitung der Anfrage betraut.

Der ftp-Slave kontaktiert den Server, loggt sich ein und lädt die gewünschte Datei herunter. Um die asynchrone, nichtblockierende Kommunikation zu erreichen, startet KIO Slaves als externe Prozesse, die mit dem Anwendungsprozess über Sockets kommunizieren. Aus Gründen der Portabilität und der immer noch schlechten Unterstützung auf manchen Plattformen haben die Entwickler dafür keine Threads verwendet. Das Listing 1 zeigt Ausschnitte aus einem Anwendungsprogramm, das via KIO eine Datei aus dem Web holt. Es benutzt dazu den Signal/Slot-Mechanismus der Qt-Bibliothek (siehe 'Entwickeln für KDE'), über den man Methoden festlegen kann (Slots), die für die Bearbeitung von bestimmten Signalen zuständig sind.

Die Methode slotDataArrived() wird immer dann aufgerufen, wenn der Job neue Daten erhält. Da die Übertragung asynchon erfolgt, müssen die Daten nicht in einem Rutsch ankommen, sie können auf mehere Methodenaufrufe verteilt sein. Das Ende der Übertragung ist erreicht, wenn slotDataArrived() mit einem leeren Datenpaket aufgerufen wurde und KIO über das Signal result slotResult() aktiviert. (Die Klasse KIO::NetAccess stellt auch Methoden für synchrone Datenübertragung bereit, die jedoch hier nicht weiter diskutiert werden.)

Abschließend terminiert KDE den Job und gibt seine Ressourcen frei. Neben der im Listing aufgerufenen get-Funktion unterstützen Slaves eine ganze Reihe weiterer Aktionen, die alle typischen Dateioperationen abdecken (siehe Kasten ‘KIO-Aktionen’).

Da nicht jedes Protokoll jede Aktion unterstützen muss, stellt eine Basisklasse Standardaktionen bereit, die aber lediglich einen Fehler zurückgeben. Aus Gründen der Effizienz gibt es im Services-Verzeichnis für jedes Protokoll zusätzlich eine Beschreibungsdatei (meistens /opt/kde2/share/services/*.protocol), in der unter anderem die unterstützten Aktionen aufgelistet sind. Diese registriert einen Slave im System und ermöglicht es, seine Fähigkeiten zu ermitteln, ohne ihn vorher zu starten.

Der Eintrag exec enthält den Dateinamen des Slaves, er hat eigentlich immer die Form kio_<Protokoll>. protocol gibt den Namen des Protokolls selber an, input und output können die Werte none, filesystem und stream haben. Diese Werte werden für Verkettungen von Slaves interessant, um zum Beispiel .tar.bz2 Dateien transparent zu lesen. Dazu lenkt man die stream-Ausgabe eines bzip2-Slaves in die Eingabe eines tar-Slaves, der dann die eigentlichen Verzeichnisse ermittelt. Das Prinzip ist dem Pipe-Mechanismus der Unix-Shells nachempfunden.

Der Wert von listing ist eine Liste von Attributen, die die Aufrufe von stat() und listDir() zurückgeben. Name, Type, Size, Date, AccessDate, Access, Owner, Group, Link, URL, MimeType sind gültige Elemente dieser Liste, ihre Reihenfolge ist unwichtig. Das im Kasten aufgeführte FTP-Protokoll liefert weder Informationen über den MIME-Typ noch über das Datum des letzten Zugriffs (AccessDate).

Die folgenden vier Elemente reading, writing, makedir, deleting zeigen die zulässigen Aktionen an. Da der Standardwert für Aktionen ‘false’ ist, muss man nur diejenigen aufführen, die auch unterstützt werden. Im Beispiel fehlen die Aktionen linking, moving und copying, da FTP dafür keine Befehle bietet.

Mit Hilfe der Informationen aus der Protokolldatei kann KIO entscheiden, wie es angeforderte Jobs auf Aktionen der Slaves abbildet. Kann ein Protokoll keine Dateien kopieren (wie es bei FTP der Fall ist), verknüpft KIO einen Lese-Job mit einem Schreib-Job und kopiert die Datei auf diesem Weg. Für Änderungen des Dateinamens (move) wird zusätzlich noch die Originaldatei gelöscht. Tatsächlich könnte man auf Kopieren und Verschieben ganz verzichten, doch in der Regel sind die direkten Kommandos um einiges schneller.

Komplexere Aktionen, wie das Löschen eines ganzen Verzeichnisses, teilt KIO in mehrere Einzelaktionen auf.

Die Grafik oben illustriert den generellen Ablauf beim Löschen eines Verzeichnisses. Solche komplexen Jobs werden in kleinere aufgespalten und von einem Scheduler dann an Slaves verteilt. Der Scheduler stellt auch sicher, dass ein Slave für das Protokoll zur Verfügung steht und dass auch nicht zu viele Slaves im System gleichzeitig aktiv sind. Einmal aktive Slaves können später von anderen Anwendungen verwendet werden, erst nach einer gewissen Zeit der Inaktivität beendet sie der Scheduler.

Als Einstieg in die KIO-Programmierung eignet sich ein kleiner Slave, der das hello-Protokoll implementiert. Es kann nur lesen, die Protokoll-Datei ist deshalb recht übersichtlich:

[Protocol]
exec=kio_hello
protocol=hello
input=none
output=filesystem
reading=true

Die Zeile ‘output=filesystem’ klingt etwas übertrieben für ein Protokoll, das nur Hallo sagen kann. Sie dient lediglich dazu, Konqueror vorzugaukeln, er bekäme eine ‘richtige’ lokale Datei.

Die Definition von HelloProtocol sieht folgendermaßen aus:

class HelloProtocol : public KIO::SlaveBase
{
public:
HelloProtocol( const QCString &amp;pool,
QCString &amp;app)
: SlaveBase( "hello", pool, app ) {}
virtual void get( const KURL&amp; url );
};

KIO ist der Namensraum, in dem alle KIO-Klassen definiert sind. Eine davon ist SlaveBase, die alle Basisoperationen eines Slaves definiert. Im Beispiel ist dies lediglich die Methode get().

Der Konstruktor von HelloProtocol im Listing ruft den Konstruktor von SlaveBase auf und setzt den Protokoll-Namen auf ‘hello’. pool und app sind Strings, die kdeinit dem Slave auf der Kommandozeile übergibt. Sie verweisen auf lokale Sockets, über die die Kommunikation mit den Slaves erfolgt.

Die eigentliche Instanzierung der Klasse erfolgt in der Funktion kdemain() (Listing2 hello.cpp Zeile 15). Diese ruft kdeinit beim dynamischen Laden auf und ist somit der eigentliche Eintrittspunkt jedes kdeinit-Moduls (und damit jedes Slaves). Sie ist vergleichbar mit der main-Funktion eines C/C++-Programms. Da beim dynamischen Laden von Modulen nur die C-Aufrufkonvention erlaubt ist, muss die Deklaration von kdemain() als

extern "C"

erfolgen.

KInstance ist quasi die kleine Schwester der Anwendungsentwicklern sicher vertrauten Klasse KApplication und gibt der Komponente einen eindeutigen Namen (kio_hello). Des Weiteren steuert sie den Kontext bei Übersetzungen, Zugriffe auf Konfigurationsdateien und ähnliche Dinge, bei denen ein Name wichtig ist. Für diesen einfachen Slave ist das zwar nicht unbedingt erforderlich, aber es gehört einfach dazu.

Die folgende Listing-Zeile erzeugt das Objekt slave vom Typ HelloProtocol und reicht die Kommandozeilenparameter weiter. Die Anweisung

slave.dispatchLoop()

startet eine Schleife, die Anweisungen über die Sockets pool und app entgegennimmt und in entsprechende Aktionen umsetzt - also beispielsweise die folgende get-Methode aufruft. Beim Hello-Protokoll hat diese die Form

void HelloProtocol::get( const KURL&amp; url )
{
mimeType("text/html");
data(QCString("&lt;HTML&gt;\n"
"&lt;BODY&gt;Hello World&lt;/BODY&gt;\n"
"&lt;/HTML&gt;\n");
finished();
}

und liefert ein paar passende HTML-Zeilen zurück. Listing 2 enthält eine etwas ausgefeiltere Version, die es erlaubt, beliebige Grußbotschaften abzusetzen. Dazu wertet sie das übergebene Argument aus, das die komplette URL enthält, wie sie in Konqueror oder einem Dateiauswahldialog eingegeben wurde.

Die einzelnen Teile dieser URL (das Protokoll, den Rechnernamen, den Benutzernamen, die Referenz, die Query und den Dateipfad) kann man über Methoden wie das im Listing verwendete path() ermitteln. Die Abfrage von isEmpty() fängt den Fall ab, dass der Benutzer keinen expliziten Pfad spezifiziert hat und mid(1) spaltet das für die Ausgabe überflüssige ‘/’ ab.

Im Allgemeinen ermittelt das Programm - also beispielsweise Konqueror - den Mimetyp der empfangenen Daten selbst. Da kio_hello jedoch grundsätzlich HTML-Seiten zurückliefert, bietet es sich an, den Mimetyp gleich auf das passende ‘text/html’ zu setzen.

Die Rückgabe der Daten erfolgt über den Aufruf von data() mit einem Object der Klasse QByteArray, das eine Folge von Bytes enthält. Die in HelloProtocol verwendete Klasse QCString ist eine Spezialisierung davon, die sich für einfache Strings eignet.

finished() sorgt dafür, dass KIO der Anwendung (oder einem übergeordneten Job) das Ende der angeforderten Aktion signalisiert. Die Bearbeitung des Signals erfolgt im zugeordneten result-Slot des Programms.

Die Entwickler haben eine Infrastruktur geschaffen, um KDE-Anwendungen und -Komponenten auf verschiedenen Plattformen wie Linux (SuSE, Red Hat, ...) oder auch Solaris und FreeBSD zu übersetzen. So reduziert sich das Kompilieren des Quellcodes auf:

./configure
make
make install

Dafür muss der Entwickler eine Reihe von Dateien wie configure.in und Makefile.am bereitstellen. Das tgz-Paket zu kio_hello in unserem Listing-Service enthält ein entsprechendes Gerüst, das man für eigene Projekte benutzen kann. Wer sich genauer für das Zusammenspiel der einzelnen Programme und Konfigurationsdateien interessiert, findet in ‘KDE 2.0 Development’ (siehe Besprechung Seite 252 in der c't 5/2001) weitere Informationen.

Die Slave-Aktion listDir() wird immer dann aktiviert, wenn eine angeforderte URL auf ein Verzeichnis verweist (also beispielsweise mit einem ‘/’ endet). Kann KIO an der URL nicht eindeutig erkennen, ob es sich dabei um eine Datei oder ein Verzeichnis handelt, liefert die Slave-Aktion stat() diese Information. listDir() gibt alle Verzeichniseinträge an die Anwendung zurück. (Die Einträge werden dabei allerdings gepuffert, um den Kommunikationsoverhead zu minimieren.)

Die eigentlichen Informationen werden in so genannten UDSEntries übertragen, wobei UDS für Universal Directory Service steht - der Name impliziert also schon Flexibilität. Ein UDSEntry besteht aus einer Liste von UDSAtomen. Diese wiederum setzen sich aus einer UDS-ID (UDS-IDs haben so tragende Namen wie UDS_NAME, UDS_SIZE, UDS_LINK_DEST oder UDS_MODIFICATION_TIME), einem Typ (UDS_STRING oder UDS_LONG für Integerwerte) und dem dazugehörigen Wert zusammen.

Die folgenden Anweisungen hängen einen Verzeichniseintrag an die zugehörige Liste an:

UDSEntry entry;
UDSAtom atom;
atom.m_uds = KIO::UDS_NAME;
atom.m_str = "Dateiname";
entry.append(atom);

Auf der Anwendungsseite repräsentiert jede Datei in einer Dateiansicht des Konquerors oder im Dateidialog einen UDSEntry. Um ein Verzeichnis aufzulisten, würde man analog zum ersten Listing einen List-Job erstellen und dessen Signale mit einem eigenen Slot verbinden.

data ( KIO::Job *,const KIO::UDSEntryList& )

Die UDSEntryList, die man dort empfängt, enthält alle bis dato empfangenen Dateien und Unterverzeichnisse, des angegebenen Verzeichnisses.

Da das Auflisten von Verzeichnissen häufig vorkommt und UDSEntry ein etwas umständliches, da generisches API bietet, gibt es für KDE-Applikationen die Klasse KFileItem. KFileItem bietet einen Konstruktor, der einen UDSEntry entgegennimmt, sodass das resultierende KFileItem-Objekt alle Attribute des UDSEntry übernimmt. Damit stehen dann Zugriffsmethoden zur Verfügung, die die URL zurückliefern, den MimeType, Dateigröße, Zugriffsrechte oder ein assoziiertes Icon. Außerdem gibt es eine Methode run(), die die Datei ‘ausführt’ - also entweder als Programm startet, oder die Datei mit der verknüpften Anwendung öffnet.

Anwendungsprogrammierer, die gar nicht mit UDSEntry hantieren wollen, verwenden die Klasse KDirLister, der man einfach die zu listende URL in der Methode openURL() übergibt und eine Methode (also einen Slot) mit dem Signal

newItems( const KFileItemList& )

verbindet. Diese erhält dann direkt eine Liste von KFileItems. Ein weiterer Vorteil von KDirLister ist die automatische Benachrichtigung über Änderungen im gelisteten Verzeichnis, wenn Dateien gelöscht, umbenannt oder hinzugefügt werden (Signale deleteItem(), refreshItems() und newItems()).

Der als Nächstes vorgestellte KIO-Slave ist schon nützlicher - aber auch anspruchsvoller als der einfache Hello-Slave. Er liest das Verzeichnis einer Audio-CD aus und erlaubt es, via Drag & Drop komplette Stücke als WAV-Datei auf dem Desktop abzulegen. Baut man in diesen Slave zusätzlich noch CDDB-Abfragen ein, erscheinen im CD-Listing statt aussageloser Bezeichner wie ‘Track1.wav’ sogar gleich die Titel der einzelnen Stücke. Die eigentliche Kommunikation mit dem CDROM-Laufwerk übernimmt dabei cd paranoia, das alle benötigten Funktionen praktischerweise gleich in der Bibliothek libcdda_paranoia bereitstellt. Es fehlt also nur noch deren Integration in einen KIO-Slave.

Natürlich kann in diesem Artikel nicht das komplette Auslesen einer CD behandelt werden, deshalb möchten wir schon jetzt auf die Quellen für den audiocd-Slave im KDE-CVS hinweisen.

Das Listing 4 enthält Code-Fragmente des AudioCDProtocol-Slaves. Sie beschreiben das Erstellen eines virtuellen Directory-Listings einer Audio-CD, das beim Anfordern der URL ‘audiocd:/’ angezeigt wird. Die Funktionen der cdparanoia-Bibliothek sind aus Platzgründen nicht aufgeführt.

Die Slave-interne Methode initRequest() initialisiert cdparanoia und liest das Inhaltsverzeichnis der Audio-CD aus. Damit füllt sie die Datenstruktur d des Slaves. Diese enthält hauptsächlich die Tracknamen sowie CDDB-Informationen. Diese CDDB-Informationen werden von einem CDDB-Server abgefragt, sobald die Disc-ID der CD feststeht. Derzeit benutzt der Slave dazu freedb.freedb.org.

Um das Navigieren innerhalb der CD zu vereinfachen, stellt das AudioCDProtocol einige virtuelle Verzeichnisse zur Verfügung. Dazu gehören ‘By Name’, ‘By Track’ und allgemeine Informationen zur CD. Die for-Schleife in Zeile 84 zeigt die einzelnen Tracks an. Sie errechnet die Größe aus der Anzahl der Sektoren und nummeriert die Einträge der Reihe nach durch. app_file() füllt einen Verzeichnis-Eintrag mit dem angegebenen Namen und der Größe. Dieser wird dann listEntry() übergeben, das für die eigentliche Ausgabe sorgt.

Die Einsatzmöglichkeiten für Slaves sind vielfältig - und ständig kommen Programmierer auf neue, pfiffige Ideen. Am Häufigsten werden sicherlich kio_file, kio_ftp und kio_http benutzt. Weitere Netzwerkprotokolle sind in kio_nntp, kio_ldap, kio_gopher, kio_smb, kio_pop3 und endlich auch in kio_imap4 implementiert. Seit kurzem existieren auch Module zum Zugriff auf SQL-Datenbanken und auf Napster- und Napigator-Server.

The Kompany hat vor kurzem einen Slave vorgestellt, der Digitalkameras auslesen und im Konqueror als Thumbnails darstellen kann. Der Slave nutzt die gphoto-Bibliothek, die bereits eine ganze Reihe von Kameras unterstützt.

Besitzer des MP3-Players von Diamond interessiert vielleicht der neue RIO-Slave, der allerdings bisher nur das Inhaltsverzeichnis des Players anzeigen kann. In KDE 2.1 überwacht kio_lan das lokale Netzwerk und registriert dabei alle ansprechbaren Rechner und deren freigegebene Ressourcen. Ab Version 2.1 kann man sich auch im KDE Control Center Informationen über alle installierten Slaves anzeigen lassen.

Wer jetzt Lust bekommen hat, selbst einen Slave zu programmieren, kann ja mal einen Blick auf KDEs Archivprogramm ark werfen. Es unterstützt eine Vielzahl von Formaten, die bisher allerdings alle noch intern implementiert sind - nicht als Slaves. Auf der Wunschliste der KDE-Entwickler steht auch ein Slave, der Webseiten komplett herunterladen kann und zur Offline-Darstellung in einem Verzeichnis speichert - inklusive alle referenzierten Bilder, Videos et cetera. Aber vielleicht haben Sie ja schon selbst eine viel interessantere Idee? (ju)


setHost: setzt alle Informationen, um einen Rechner zu kontaktieren. Alle nachfolgenden Aktionen basieren auf diesen Informationen. In der Regel kontaktiert diese Funktion auch gleich den Rechner, dies ist aber nicht vorgeschrieben.

get: liest eine Datei aus und übergibt die Daten so wie sie anfallen über data() in Blöcken zurück. Diese Blöcke können von beliebiger Größe sein.

put: schreibt eine Datei. Blöcke von Daten können über Aufrufe von readData() gelesen werden.

stat: findet Details über eine Datei oder ein Verzeichnis heraus. Welche Informationen zur Verfügung stehen, ist von Protokoll zu Protokoll verschieden und wird über eine Liste von Atomen (UDSEntry) zurückgegeben.

mimetype: versucht den MIME-Type einer URL (Datei oder Verzeichnis) zu ermitteln.

listDir: Liest den Inhalt eines Verzeichnisses aus. Die gefundenen Einträge werden über listEntry() zurückgegeben (und aus Performance-Gründen intern gepuffert). Um dem Benutzer Informationen über den Fortschritt beim Auslesen zu geben, sollte auch die Informationsfunktion totalFiles() aufgerufen werden, sobald diese Zahl feststeht.

mkDir: erzeugt ein Verzeichnis.

rename: benennt ein Objekt um.

symlink: erzeugt einen symbolischen Link.

chmod: setzt die Zugriffsrechte eines Objektes.

copy: kopiert eine Datei. Verzeichnisse werden in mehreren Schritten kopiert.

del: löscht ein Objekt. Es wird die Information mit übergeben, ob es sich hierbei um eine Datei oder ein Verzeichnis handelt. Bei einem Verzeichnis kann davon ausgegangen werden, dass es zu diesem Zeitpunkt leer ist.

special: wird für protokollspezifische Dinge benutzt. Es wird zum Beispiel für Dinge wie das Mounten von Dateisystemen benutzt, die nur für das file:-Protokoll existieren.


KDE-Programme beziehungsweise -Dienste müssen gegen die KDE-Bibliotheken gelinkt werden. Da diese mittlerweile recht umfangreich geworden sind, dauert es eine ganze Weile, bis der dynamische Loader ld.so beim Start eines solchen Dienstes alle Bibliotheken geladen und die Symbole relokiert hat. Starten Sie einfach mal:

ldd /opt/kde2/bin/kedit
kdeinit und klauncher beschleunigen den Start von KDE-Programmen und -Diensten.

Sie erhalten eine Liste mit ganzen 25 Bibliotheken. Dieses Problem umgeht ein kleiner Daemon namens kdeinit. Er ist gegen die Bibliotheken gelinkt, die von vielen KDE-Programmen benötigt werden, unter anderem DCOP, Qt, kdecore, kdeui, kio, kparts. Diese Bibliotheken befinden sich somit bereits im Speicher, ihre Symbole sind relokiert. Der Start eines neuen Programms erfolgt dann nicht mehr über die Systemfunktion exec(), sondern indem der kdeinit-Prozess eine neue Bibliothek dynamisch in seinen Adressraum lädt.

Dieses Konzept spart nicht nur einiges an Zeit beim Start, sondern auch noch einige 100 KByte Speicher pro Programm. Allerdings muss das Programm dazu natürlich nicht nur als ausführbare Datei, sondern auch als Bibliothek vorliegen. Auch KIO-Slaves startet KDE via kdeinit, sie müssen also als dynamische Bibliothek übersetzt werden. Die zum Listing mitgelieferten Konfigurationsdateien demonstrieren, wie man das bewerkstelligt.


Die KDE-Bibliotheken verteilen sich auf mehrere Komponenten.

Die Basis von KDE ist das Qt-Toolkit der norwegischen Firma TrollTech. Qt ist eine Klassenbibliothek für C++, die Standard-GUI-Elemente, Containerklassen, Abstraktion von Plattformspezifika (Dateizugriff, Grafikausgabe, Drucken) und vieles mehr bietet. Qt ist für X11, Windows und das Linux Framebuffer-Device für Embedded Systeme erhältlich und bietet für alle Plattformen die gleiche Programmierschnittstelle (API).

Qt und die KDE-Bibliotheken (kdelibs) sind objektorientiert programmiert und fördern durch ihr API auch einen objektorientierten Programmierstil. Insbesondere von Vererbung und Überschreiben von Methoden wird häufig Gebrauch gemacht. Die Klasse QWidget zum Beispiel, mit der Qt ein Fenster modelliert, enthält Funktionen wie

mousePressEvent( QMouseEvent * )
keyPressEvent( QKeyEvent * )

die aufgerufen werden, wenn jemand mit der Maus in dieses Fenster klickt beziehungsweise dort eine Taste drückt. Diese Events kann man als Anwendungsprogrammierer abfangen, indem man von der Klasse QWidget eine eigene Klasse ableitet und diese Methoden überschreibt. Dadurch, dass die Methoden in C++ als ‘virtual’ deklariert sind, wird anstatt der Standardimplementation in QWidget die Methode in der abgeleiteten Klasse aufgerufen.

Auch die KDE-Bibliotheken bieten Programmierern eine Reihe von Features, die die Programmierung erleichtern, modulare Konzepte ermöglichen und allen Programmen ein einheitliches Aussehen und eine einheitliche Bedienung geben. Sie sind nicht wie Qt komplett in einer Bibliothek zusammengefasst, sondern verteilen sich auf modulare Komponenten, die aufeinander aufbauen. Ein Programm braucht daher nur gegen den Teil der kdelibs zu linken, den es wirklich benötigt.

Eine zentrale Stellung nimmt das Desktop Communications Protocol (DCOP) ein. Es enthält unter anderem Möglichkeiten zur Interprozesskommunikation (IPC) und zum Ausführen von Funktionen in anderen Prozessen (RPC).

Die Bibliothek KFile beinhaltet hauptsächlich den KDE-Dateidialog und einige nützliche GUI-Elemente und Dialoge, die diesen benutzen. KParts ist das Framework zum Einbetten von Komponenten. Vor allem Konqueror und KOffice nutzen KParts zur Darstellung verschiedener Datei- und Dokumenttypen. KHTML ist die Bibliothek zum Anzeigen von HTML-Dokumenten, die Unterstützung für Stylesheets (CSS), Java Applets, Javascript und das Document Object Model (DOM) bietet.

Die LTDL-Bibliothek stellt eine portable Lösung zum dynamischen Laden von Bibliotheken bereit. Darauf beruht unter anderem das Komponentenframework KParts. Auch die Dekorationen des Windowmanagers kwin und die KDE2-Stile sind dynamisch geladene Module.

Momentan arbeiten die Entwickler an der 2.1-Release von KDE. Die zweite Betaversion wurde am 31. Januar veröffentlicht, mit einer fertigen Version von KDE 2.1 kann man ab Ende Februar rechnen. Sie enthält eine ganze Reihe von Funktionen und Programmen, die bereits in KDE 1 enthalten waren, aber für 2.0 nicht rechtzeitig fertig wurden. Aber auch neue Programme wie die Entwicklungsumgebung KDevelop und der Bildbetrachter und -editor Pixie werden erstmals integriert. Hinzu kommen der Multimedia-Player Noatun und diverse Spiele. Das Bildbearbeitungsprogramm Krayon (ehemals KImageShop) hat zwar noch lange nicht den vollen Funktionsumfang, wird aber voraussichtlich mit der nächsten KOffice-Release zum ersten Mal veröffentlicht - wie auch Kivio, das Flowchart-Programm von TheKompany. Eine vorläufige Liste der Änderungen von KDE 2.0 nach 2.1 ist im Internet zu finden


Wer mit den KDE-Bibliotheken Anwendungen erstellen möchte, muss auf die Version achten: die Bibliotheken von KDE 2 sind nicht kompatibel zu denen der Vorgängerversion. Viele Klassen wurden umgeschrieben oder sogar komplett neu implementiert, andere wurden durch neue Konzepte obsolet.

Zum Übersetzen von KDE-Anwendungen benötigt man zusätzlich zu den eigentlichen Bibliotheken auch die Header-Dateien für kdelibs, Qt und X11. Viele Linux-Distributionen enthalten Extrapakete für Entwickler, die die Header-Dateien sowie eventuelle Dokumentation bereitstellen. Neben dem Compiler (C/C++) benutzt KDE einige weitere Standardentwicklungstools, wie make, autoconf, automake und perl, um das Übersetzen und Integrieren von neuem Quellcode so einfach wie möglich zu gestalten.

Das Tool kapptemplate erstellt ein Verzeichnis mit allen erforderlichen Konfigurationsdateien und einer Beispielanwendung, die man als Vorlage für eigene Programme nutzen kann. Aktuelle Versionen von KDevelop bieten eine ähnliche Funktion.

Wer nach Vorlagen für eigene Slaves sucht, wird im Quellcode von KDE fündig. Einige Slaves (file, ftp, http) sind im kdelibs-Paket unter kio/ untergebracht, die meisten anderen im kdebase-Paket unter kioslave/. Neuere, noch nicht fertig gestellte Slaves sind üblicherweise im kdenonbeta Paket zu finden, unter anderem kio_napster und kio_rio.

Zum Programmieren gehört leider auch die Suche nach Fehlern. KDE bietet eine einfache Möglichkeit, innerhalb einer Anwendung (oder auch in einem Systemdienst) Warn-, Fehler- oder Debug-Ausgaben zu erzeugen. Dazu sind in kdebug.h vier Ausgabe-Streams definiert:

kdbgstream kdDebug( int region=0 );
kdbgstream kdWarning( int region=0 );
kdbgstream kdError( int region=0 );
kdbgstream kdFatal( int region=0 );

kdDebug() hat dabei den Vorteil, dass die Debug-Ausgabe in Produktionsversionen des Programms - also wenn das Programm mit NDEBUG definiert kompiliert wird, beispielsweise durch ‘configure - disable-debug’) unterdrückt wird. Die Benutzung der Streams erfolgt wie in

kdDebug( 500 ) &lt;&lt; "Stringinhalt: " &lt;&lt; meinString &lt;&lt; 
" L&auml;nge: " &lt;&lt; len &lt;&lt; endl;

wobei meinString vom Typ ‘char *’, QString oder QCString sein kann. Auch andere Basisdatentypen wie char, integer und double werden unterstützt. Per Default landet die Ausgabe auf der Konsole. Über das Tool ‘kdebugdialog [-fullmode]’ kann man sie jedoch in eine Datei, eine Messagebox oder zum syslog-Dienst umleiten oder auch komplett stumm schalten.

Damit man nicht immer die Ausgaben aller KDE-Komponenten auswerten muss, wurde das Konzept der Debug-Areas eingeführt. Jede KDE-Applikation oder -Komponente kann damit eigene Debug-Streams verwenden, deren Nummer kdDebug als Parameter übergeben wird. Trägt man für sein eigenes Programm in /opt/kde2/share/config/kdebug.area einen Wert wie 500 (oder einen ganzen Bereich) ein, kann man diesen Ausgabestream in kdebugdialog separat aktivieren und umleiten.

Die Entkoppelung von Slaves von den aufrufenden Programmen, wirft besondere Probleme beim Testen und Debuggen dieser Komponenten auf. Um einen Slave komplett durchzutesten, gibt es in KDE-Quellcode-Baum unter kdelibs/kio/tests das Tool kioslavetest, das mit ‘make kioslavetest’ übersetzt wird. Damit kann man alle möglichen Slave-Operationen testen.

Erste Anlaufstelle für allgemeine Informationen zum Entwickeln mit und für KDE ist http://developer.kde.org/. Hier findet sich unter anderem die komplette API-Dokumentation im HTML-Format. Einen guten Einstieg vermittelt auch das Buch ‘KDE 2.0 Development’ von David Sweet und weiteren KDE-Entwicklern, das auch online verfügbar ist (siehe Besprechung S. 252 in der c't 5/2001).


So könnte ein einfaches Programm die Dienste der KIO-Bibliothek nutzen.

1 
2 KIO::TransferJob *job = KIO::get( "http://www.kde.org/index.html" );
3 connect( job, SIGNAL( data( KIO::Job *, const QByteArray&amp; ),
4 SLOT( slotDataArrived( KIO::Job *, const QByteArray&amp; )));
5 connect( job, SIGNAL( result( KIO::Job * )),
6 SLOT( slotResult( KIO::Job * )));
7 [...]
8
9 void BeispielAnwendung::slotResult( KIO::Job *job )
10 {
11 if ( job-&gt;error() != 0 ) // irgendetwas ist schief gelaufen
12 [...]
13 }
14
15 void BeispielAnwendung::slotDataArrived( KIO::Job *job,
16 const QByteArray&amp; data )
17 {
18 // hier kommen die empfangenen Daten des Jobs job im
19 // QByteArray data an, im Beispiel hoffentlich der Inhalt der
20 // Datei index.html
21 // Die Daten kommen allerdings u.U. in mehreren Etappen
22 }

hello.cpp: Der Hello-Slave gibt HTML-Text mit einer Grußmeldung aus.

1 #include &lt;kio/slavebase.h&gt;
2 #include &lt;kinstance.h&gt;
3 #include &lt;kdebug.h&gt;
4 #include &lt;stdlib.h&gt;
5 #include &lt;qtextstream.h&gt;
6
7 class HelloProtocol : public KIO::SlaveBase
8 {
9 public:
10 HelloProtocol( const QCString &amp;pool, const QCString &amp;app)
11 : SlaveBase( "hello", pool, app ) {}
12 virtual void get( const KURL&amp; url );
13 };
14
15 extern "C" {
16 int kdemain( int, char **argv )
17 {
18 KInstance instance( "kio_hello" );
19 HelloProtocol slave(argv[2], argv[3]);
20 slave.dispatchLoop();
21 return 0;
22 }
23 }
24
25 void HelloProtocol::get( const KURL&amp; url )
26 {
27 // tell the mimetype
28 mimeType("text/html");
29
30 QString name = url.path();
31 if (name.isEmpty() || name == "/")
32 name = "c't-Leser";
33 else
34 name = name.mid(1); // strip leading /
35
36 QCString output;
37 output.sprintf("&lt;HTML&gt;\n"
38 "&lt;BODY&gt;Hallo %s&lt;/BODY&gt;\n"
39 "&lt;/HTML&gt;\n", name.latin1());
40
41 data(output);
42 finished();
43
44}

Die Protokolldatei gibt an, welche Aktionen ein KIO-Slave - hier der ftp-Slave - unterstützt.

1 [Protocol]
2 exec=kio_ftp
3 protocol=ftp
4 input=none
5 output=filesystem
6 listing=Name,Type,Size,Date,Access,Owner,Group,Link,
7 reading=true
8 writing=true
9 makedir=true
10 deleting=true
11 Icon=ftp

Auszüge aus den Quellen für die listDir-Methode von AudioCD-Protocol.

1 // f&uuml;gt einen String mit der UDS-ID uds ein
2 static void app_entry(UDSEntry&amp; e, unsigned int uds, const QString&amp; str)
3 {
4 UDSAtom a;
5 a.m_uds = uds;
6 a.m_str = str;
7 e.append(a);
8 }
9
10 // f&uuml;gt einen Zahlenwert mit der UDS-ID uds ein
11 static void app_entry(UDSEntry&amp; e, unsigned int uds, long l)
12 {
13 UDSAtom a;
14 a.m_uds = uds;
15 a.m_long = l;
16 e.append(a);
17 }
18
19 // interne Funktion
20 // f&uuml;llt ein Directory-Item mit dem Namen und der Gr&ouml;&szlig;e
21 static void app_dir(UDSEntry&amp; e, const QString &amp; name, size_t size)
22 {
23 e.clear();
24 app_entry(e, KIO::UDS_NAME, name);
25 app_entry(e, KIO::UDS_FILE_TYPE, S_IFDIR);
26 app_entry(e, KIO::UDS_ACCESS, 0400);
27 app_entry(e, KIO::UDS_SIZE, size);
28 }
29
30 // interne Funktion
31 // f&uuml;llt ein Datei-Item mit dem Namen und der Gr&ouml;&szlig;e
32 static void app_file(UDSEntry&amp; e, const QString &amp; name, size_t size)
33 {
34 e.clear();
35 app_entry(e, KIO::UDS_NAME, name);
36 app_entry(e, KIO::UDS_FILE_TYPE, S_IFREG);
37 app_entry(e, KIO::UDS_ACCESS, 0400);
38 app_entry(e, KIO::UDS_SIZE, size);
39 }
40
41 // von SlaveBase &uuml;berschriebene Funktion zum Listen eines Verzeichnisses.
42 void AudioCDProtocol::listDir(const KURL &amp; url)
43 {
44 // Initialisierung des CD-ROMs mit Hilfe von cdparanoia-Funktionen
45 struct cdrom_drive * drive = initRequest(url);
46 if (!drive)
47 return;
48
49 // UDSEntry ist ein Item
50 UDSEntry entry;
51
52 // Da ListDir nur Verzeichnisse listen kann, ist es ein Fehler,
53 // wenn initRequest herausfindet, wir listen eine Datei
54 if (d-&gt;which_dir != Device)
55 {
56 // f&uuml;r error() gibt es eine ganze Reihe von vordefinierten Fehler-
57 // codes und das zweite Argument ist ein String, der meist mit in
58 // der Fehlermeldung steht
59 error(KIO::ERR_IS_FILE, url.path());
60 return;
61 }
62
63 /* Auflistung einiger virtueller Verzeichnisse, die der einfacheren
64 Navigation auf der CD dienen */
65 if (d-&gt;based_on_cddb)
66 {
67 // app_dir f&uuml;llt einen Directory-Eintrag
68 app_dir(entry, i18n("By Name"), d-&gt;tracks);
69 // listEntry uebertraegt einen Eintrag. Diese werden gepuffert
70 // &uuml;bertragen. Das false deutet an, dass es noch nicht das letzte
71 // Element ist.
72 listEntry(entry, false);
73 }
74 app_dir(entry, i18n("Information"), 1);
75 listEntry(entry, false);
76 app_dir(entry, d-&gt;cd_title, d-&gt;tracks);
77 listEntry(entry, false);
78 app_dir(entry, i18n("By Track"), d-&gt;tracks);
79 listEntry(entry, false);
80 app_dir(entry, QString("dev"), 1);
81 listEntry(entry, false);
82
83 // Auflisten der Tracks
84 for (int i = 1; i &lt;= d-&gt;tracks; i++)
85 {
86 if (d-&gt;is_audio[i-1])
87 {
88 long size = CD_FRAMESIZE_RAW *
89 ( cdda_track_lastsector(drive, i) -
90 cdda_track_firstsector(drive, i));
91
92 QString name;
93 name.sprintf("track%02d.cda", i);
94 app_file(entry, name, size);
95 listEntry(entry, false);
96 }
97 }
98
99 // listEntry mit true als zweitem Argument erzwingt eine
100 // Leerung des listEntry-Puffers
101 listEntry(entry, true);
102
103 // CD-Zugriff beenden
104 cdda_close(drive);
105
106 // und ein finished() wie &uuml;blich in slave Aktionen, um das
107 // Ende zu markieren
108 finished();
109
110 }

Download der Listings