Dieser und noch weitere Artikel wurde von Shade Of Mine erstellt.


Folgende Themen werden von diesem Artikel berührt:


Druckversion des Artikels


"Linux ist ja auch nur ein Windows"

Wenn wir eine Anwendung von Windows auf Linux (oder umgekehrt) portieren wollen, gibt es da denn große Unterschiede?

Ja, es gibt sie. Nein, es gibt sie nicht. Ja was nun? Die Unterschiede zwischen Linux und Windows liegen offenbar in der Architektur; das spielt aber für uns keine Rolle. Uns interessiert, wie sich unser Code unter den beiden Systemen verhält. Wenn wir von vornherein wissen, dass unsere Anwendung auf mehreren Betriebssystemen laufen muss, dann können wir uns von vornherein vieles erleichtern.

Kaum eine Anwendung kommt ohne externe Librarys aus. In der Designphase ist es wichtig, sich für plattformunabhängige Bibliotheken zu entscheiden. So mag zum Beispiel wxWidgets eine bessere Wahl als Gtk sein, da es sowohl unter Windows als auch unter Linux nativ aussieht. Vor allem unter Windows sind hier auch noch lizenzrechtliche Probleme zu beachten, sodass zum Beispiel Qt oft nicht die beste Wahl ist, denn eine Qt Lizenz kostet viel Geld und die freie Variante steht unter der GPL. Hier punktet wieder Gtk. Es ist also nicht leicht, die richtige Entscheidung zu treffen.

Wenn wir also wissen, dass wir unsere Anwendung portabel gestalten wollen, erleichtern wir uns durch portable Bibliotheken später viel Arbeit. Allerdings, wie der Teufel so spielt, weiß man nicht alles im Voraus. Deshalb sollte man auch, sofern es nicht allzu großen zusätzlichen Aufwand verursacht, zu diesen portablen Librarys greifen. Boost, stlsoft und ACE sind hier gute Anlaufstellen.

Es ist aber nicht immer möglich (und sinnvoll) eine plattformunabhängige Lösung zu verwenden. In diesen Fällen kann es sinnvoll sein, die plattformabhängige Bibliothek zu wrappen oder per MVC (oder dem oft einfacherem Doc/View) Pattern zu integrieren, denn wenn man an 100.000 Stellen den Code ändern muss, um das Programm portieren zu können, ist der Aufwand oft viel zu hoch.

Gerade die Ausgabe- von der Businesslogik zu trennen kann hier Gold wert sein, denn es kann sehr leicht passieren, dass man ein GUI-Toolkit wie zum Beispiel VCL plötzlich nicht auf der Zielplattform verwenden kann. Wenn man sich schön an MVC oder Doc/View gehalten hat, kann man recht schön nur das GUI austauschen. Dass dies nicht immer so einfach ist, hat Marcus Bäckmann beim ersten Forentreffen eindrucksvoll gezeigt. Eine andere GUI-Architektur kann zu viele/wenige Aktualisierungen vom Controller verlangen und dadurch die Performance stark beeinflussen. In solchen Situationen muss man refaktorieren.

Oft können es aber auch die kleinen Unstimmigkeiten sein, die das Debuggen auf der neuen Plattform zur Hölle machen. Es ist deshalb durchaus sinnvoll, boost/cstdint.hpp zu verwenden. Boost definiert darin Integer-Typen mit garantierter Größe, sodass man boost::int_least16_t schreiben kann und sich sicher sein kann, einen Typen mit mindestens 16 Bit bekommen zu haben. Falsche Typengrößen sind nur selten ein Problem, aber dank boost kann man sie gratis fast ganz vermeiden.

Der Rest der Arbeit läuft so ähnlich ab, wie als wenn man eine Library wrappt – denn etwas anderes tut man ja nicht. Das Schöne daran ist, dass man gleich ein OO-Interface über diese Funktionen legen kann. Da dies aber viel Arbeit ist, ist es oft sinnvoll etwas nur "on demand" zu wrappen (das heißt nur dann, wenn man es wirklich braucht). Stlsoft stellt dafür eine gute Anlaufstelle dar.

Wenn man für zwei oder mehr Plattformen entwickelt, wird man an verschiedenen Stellen ein Problem verschieden lösen müssen, auch wenn die Bibliotheken alle plattformunabhängig sind (wir leben ja leider nicht in einer perfekten Welt). Dann ist es wichtig, den Code nicht durch viele #ifdefs unleserlich zu machen. Eine gute Lösung hierbei ist es, für jede Plattform eine eigene Source Datei anzulegen. Dadurch hat man zwar mehr Code zu warten und man muss etwaige Bugfixes auf andere Plattformen übertragen, aber man erkauft sich dafür leichter zu lesenden Code. Denn bei vielen #ifdefs kann man leicht den Überblick verlieren, und was wohl am schlimmsten ist: auf eine neue Plattform Portieren kann unnötig kompliziert werden (weil wir schon wieder an 100.000 einzelnen Stellen den Code ändern müssen).

Um die Transparenz zu wahren, kann man Proxy-Dateien verwenden. Man erstellt pro Plattform einen eigenen Verzeichnisbaum (zum Beispiel code/programm/win32, code/programm/linux, ...) und legt an der zentralen Stelle (bei uns zum Beispiel code/programm) eine Proxy-Datei ab. Diese inkludiert lediglich die richtige Plattformdatei. Dank des Präprozessors kann man hier recht simpel #ifdefs machen, um die richtige Plattform auszuwählen. Der Clientcode bekommt davon nichts mit.

Eine Foo.cpp sieht dann in etwa so aus:
C++:
#if MY_OS == MY_WINDOWS
 #include "win32/Foo.cpp"
#elif MY_OS == MY_LINUX
 #include "linux/Foo.cpp"
#elif MY_OS == MY_WASANDERES
 #include "wasanderes/Foo.cpp"
#endif

In einer zentralen Konfigurationsdatei bestimmt man dann das Betriebssystem oder lässt den Anwender das richtige #define setzen.

Der Clientcode linkt jetzt nur Foo.cpp und muss sich nicht mehr darum kümmern, ob es in Wirklichkeit win32/Foo.cpp oder linux/Foo.cpp ist.

Ein oft viel größeres Problem ist aber die Compilerunabhängigkeit. Denn auf Plattformunabhängigkeit lässt sich leicht achten, indem man alle systemabhängigen Librarys wrappt. Aber Compilerunbhängigkeit besteht meistens daraus, Compilerbugs zu umgehen. Dieses Unterfangen kann recht mühselig sein, vor allem da es kaum Ressourcen zu diesem Thema gibt.

Eine einfache Möglichkeit besteht darin, schon recht früh mit mehreren Compilern zu testen. Idealerweise lässt man hierbei die Unit Tests mit den verschiedenen Compilern laufen. Das Problem hierbei ist, dass viele Compiler nur kommerziell verfügbar sind. Die Linkliste von www.c-plusplus.de hat hier aber ein paar interessante Compilerressourcen.

Ein interessanter Link zu den Compilerunterschieden ist vielleicht auch die Dokumentation einer alten Version einer CGI/Library, die ich einmal geschrieben habe. Sie zeigt die Workarounds, die nötig waren, diese Bibliothek unter verschiedenen Compilern einzusetzen. Der Link ist aber eher für Leute, die Spaß am Basteln haben, denn wenn man unter Zeitdruck steht, dann sollte man diese Spielereien lassen und lieber konservativeren Code verwenden.

Viel tun, um sich gegen Compilerprobleme zu schützen, kann man leider nicht. Die Unittests können einem viele Probleme ersparen, da man rechtzeitig viele Inkompatibilitäten entdeckt. Oft ist es auch sinnvoll zu templatelastigen Code (vor allem Template Metaprogrammierung) zu meiden, da damit noch viele Compiler große Probleme haben. Ein großes Nein sind hier vor allem Template Template Argumente. Oft muss man leider auch das Interface ändern, um um einen Compilerbug herumzukommen. Frühes Testen kann hier viel Ärger ersparen.

Das Deployment von Crossplattform-Anwendungen ist ein nächstes großes Kapitel. Hier teilt es sich in zwei Teile auf: Bibliotheken und Anwendungen.

Bibliotheken:
Da man diese in der Regel von Source kompilieren muss (Bibliotheken in Binärform sind meistens nur dann sinnvoll, wenn man sie als DLL/SO vertreibt), muss man viele verschiedene Makefiles anbieten. wxWidgets entwickelt deshalb an bakefile, einer Möglichkeit für viele verschiedene Compiler Makefiles zu erstellen.

Unter Unix kann und sollte man natürlich Autotools verwenden. Das Problem stellt sich eigentlich nur in der Windows-Welt, denn hier herrschen viele verschiedene Compiler und es existieren keine Autotools, um das Kompilieren zu vereinfachen. Andere Möglichkeiten der Distribution bieten Scons und CMake, da sie portable Wrapper um Make sind. Der Nachteil ist, dass der Anwender diese installiert haben muss.

Bei Anwendungen ist die Sache sogar viel komplizierter. Das große Problem hierbei ist, dass eine Anwendung ,die unter Windows sehr beliebt ist, unter Linux vielleicht keine Anwender findet, denn hier muss man nicht nur technische Aspekte beachten, sondern vor allem die Systemintegration. Nicht jeder BSD-Desktop hat eine "System Notification Area", sodass eine Anwendung, die sich darauf verlässt, recht schnell nicht mehr zu bedienen sein kann.

Auch andere Aspekte wie Look & Feel und sonstige Usability Guidelines unterscheiden sich von Plattform zu Plattform. Dies kann oft ein viel größeres Problem als alle technischen Gründe werden, denn wenn das Programm nicht verwendet oder gekauft wird, dann bringt die Plattformunabhängigkeit nichts. Wenn die Anwendung unter allen Plattformen gleich aussieht und sich gleich benimmt, so kann dies für User, die diese Anwendung unter mehreren Plattformen betreiben, ein Vorteil sein. Für User, die nur eine Plattform benutzen, ist dies allerdings ein Problem, denn die Anwendung sieht nicht so aus und verhält sich nicht so, wie es der User erwarten würde.

Des Weiteren kostet Plattformunabhängigkeit auch Performance. Oft ist das nicht sehr wichtig, in manchen Fällen kann es aber relevant werden. Plattformunabhängigkeit heißt im Prinzip nichts anderes als Abstraktion, und Abstraktion hat manchmal versteckte Kosten. Eine dieser Kosten ist folgendes: plattformspezifische Optimierungen. Da wir die Zielplattform nicht kennen oder nicht genau wissen, was alles Ziel werden wird, oder wir den kleinsten gemeinsamen Nenner dieser Plattformen finden müssen, nehmen wir uns die Möglichkeit speziell für diese Plattform zu optimieren.

Alles in allem ist es nicht leicht, portable Software zu entwickeln. Meistens bedeutet Plattformunabhängigkeit höhere Komplexität, schlechtere Performance, schlechtere Wartbarkeit oder nicht optimales Userinterface. Man muss immer abwägen, was wichtiger ist. Denn Portabilität ist nicht das einzige Qualitätsmerkmal einer Software und schon gar nicht das wichtigste.

Sie können Kommentare zu diesem Artikel im Forum schreiben. (Eine Registrierung ist nicht notwendig.)

Logo-Design: MastaMind Webdesign