Portabilität - Linux ist ja auch nur ein Windows


  • Mod

    "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:

    #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.net 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.



  • hi,

    Dass dies nicht immer so einfach ist, hat Marcus Bäckmann beim ersten Forentreffen eindrucksvoll gezeigt.

    was war da genau, wenn ich fragen darf?



  • eine kleine frage schrieb:

    hi,

    Dass dies nicht immer so einfach ist, hat Marcus Bäckmann beim ersten Forentreffen eindrucksvoll gezeigt.

    was war da genau, wenn ich fragen darf?

    eigentlich hat er gezeigt, daß es recht einfach ist, wenn ich mich recht erinner.

    er hat nen kleinen vortrag über's MVC-Konzept (siehe humes page) gehalten. und den räuber-beute-system-simulator aus seinem buch mit ner GUI verziert. und vorgemacht, wie man mit dem visitor-pattern prima den simulator von der gui trennen kann. ne kleine konsole-anwendung zum testen. und wenn der simulator steht auch ne gui drum. und theorettich auch ne gui mit nem anderen gui-tool oder nem anderen bs.



  • Potierbarkeit muss immer im Design schon berücksichtigt werden.
    Ursprünglich waren alle C-Programme portierbar bis die Hersteller
    anfingen dies bewusst aufzugeben. Heute versucht man diese
    Entwicklung mit wxWidget, QT, VLX und wie sie alle heisen.

    Oft findet man vollkommen unnötige Abhängfigkeiten von
    z. B. der MFC. Deswegen verwende ich wenn möglich immer
    Cross-Platform-Librarys wie cbccl.



  • Das ist mal ein guter Bericht über das Thema. Fange gerade damit an, möglichst systemunabhängig zu schreiben. Macht irgendwie ne Menge Spaß, aber für mich ist atm wirklich die "Compilerunabhängigkeit" das größte Ärgernis.

    Was ich mich schon lange gefragt habe: warum muss man (soweit ich weiß) NUR unter Windows verschiedene Hauptprozeduren verweden, je nach Programmtyp? Also statt main() auch mal WinMain, LibMain oder gar deren Unicode-Äquivalente... Ich finde, dass Microsoft da wirklich großen Mist verzapft hat.



  • Shade Of Mine schrieb:

    Viel tun, um sich gegen Compilerprobleme zu schützen, kann man leider nicht.

    Am einfachsten ist es, sich direkt auf einen Compiler zu beschränken. Wenn man den gcc nimmt, deckt man wahrscheinlich schon alle relevanten Systeme mit ab. Wenn man lieber mit einem anderen Compiler entwickelt, zB. weil dort die IDE so schön ist, hat man zumindest nur noch zwei Compiler, die man testen muss.

    Noch mehr Compiler zu unterstützen, macht nur bei OpenSource Sinn.



  • DrGreenthumb schrieb:

    Noch mehr Compiler zu unterstützen, macht nur bei OpenSource Sinn.

    darf man fragen, welchen?



  • solaris-cc, icc...



  • Man kann viele Probleme vermeiden, wenn man aktuelle Compiler einsetzt, die so gut wie möglich erstmal den ISO-Standard umsetzen. Klar gibts dann immer noch Stolperfallen, aber man minimiert es schon mal. Somit muß man sich nicht mal auf einen bestimmten Compiler einstellen.



  • SG1 schrieb:

    solaris-cc, icc...

    thx..



  • andere Möglichkeiten der Distribution bieten Scons und CMake, da sie portable Wrapper um Make sind

    sie ersetzen make, aber sie wrappen es nicht!



  • asdasd schrieb:

    andere Möglichkeiten der Distribution bieten Scons und CMake, da sie portable Wrapper um Make sind

    sie ersetzen make, aber sie wrappen es nicht!

    Das trifft nur auf Scons zu. CMake generiert Makefiles.


Log in to reply