Frage Cross-Platfrom design?



  • Hallo Leute,

    was ist das best-practise Design Pattern für Cross-Platfrom code?

    So ganz pragmatisch:

    //plattform.h
    
    void Foo();
    
    

    c- module

    //platform.c
    
    #ifdef OS_A
    void Foo(){...}
    #elseif OS_B
    void Foo(){...}
    #else
    #error "Error no OS Selected"
    #endif
    

    aber wie mache ich das bissel modularer?

    Idee:

    module osa.c

    //os_a.c
    
    #if defined(OS_A) && ! defined(OS_B)
    void Foo(){...}
    #else
    #error "Error no OS Selected"
    #endif
    

    module osb.c

    //os_b.c
    
    #if defined(OS_B) && ! defined(OS_A)
    void Foo(){...}
    #else
    #error "Error no OS Selected"
    #endif
    

    oder lieg ich komplett daneben ? macht es hier vll. auch sind extern func def zu verwenden oder is das dann ehr schlecht?

    Vielen Dank



  • Wenn man ein buildsystem wie z.b. cmake nutzt dann könnte man die platform spezifischen source files (in deinem beispiel *.c) nur dann in dem build hinzufügen, wenn für die entsprechende Platform gebaut werden soll.


  • Mod

    Präprozessormagie würde ich benutzen, um Schnittstellen anzugleichen. So hast du nur eine Stelle im Programm, die sich mit unterschiedlichen Arten und Weisen auskennen muss, wie der plattformabhängige Code aufgerufen wird. In deinem Beispiel ist schon alles eine einheitliche Schnittstelle (void Foo()), aber du kannst dir sicher leicht Erweiterungen vorstellen, wo beispielsweise plattformabhängige Datentypen auftauchen, die hinter Präprozessormakros verborgen werden können.

    Die eigentlichen plattformabhängigen Codeteile würde ich überhaupt gar nicht mit dem Präprozessor trennen. Das gehört ins Builscript. Wenn du für OS_A übersetzt werden eben andere Dateien übersetzt als für OS_B.



  • @SeppJ sagte in Frage Cross-Platfrom design?:

    Die eigentlichen plattformabhängigen Codeteile würde ich überhaupt gar nicht mit dem Präprozessor trennen. Das gehört ins Builscript. Wenn du für OS_A übersetzt werden eben andere Dateien übersetzt als für OS_B.

    Da schließe ich mich an. os_a.c und os_b.c sind eine gute Idee und m.E. gängige Praxis, aber das Präprozessor-Zeug würde ich nur machen, wenn es sich absolut nicht vermieden lässt - z.B. in einem gemeinsamen Header. Das erzeugt sonst nur unnötiges Code-Rauschen und schadet der Lesbarkeit. Wenn die falsche Datei für das OS kompiliert wird, dann läuft das eben vor die Wand. Genau so wie wenn eine notwendige Bibliothek nicht installiert ist oder der falsche Compiler verwendet wird.

    Eine Ausnahme würde ich allerdings bei feingranularen Unterschieden machen, die zu wenige sind um eine eigene C-Datei zu rechtfertigen oder die sich nur schlecht aus dem Code herauslösen lassen, so dass der Code drumherum ansonsten doppelt vorhanden müsste. Z.B. sowas hier:

    // os_a.c
    void Foo()
    {
        ...
        #if OS_A_VERSION >= 200
             moderne_os_funktion();
        #else
             legacy_os_funktion();
        #endif
        ...
    }
    

    Wenn diese Unterschiede aber zu viele werden, kann man auch hier überlegen, ob man den entsprechenden Code nicht in eine eigene Funktion in einer separaten Datei auslagert und auf die Präprozessor-Direktiven verzichtet.



  • @Finnegan sagte in Frage Cross-Platfrom design?:

    @SeppJ sagte in Frage Cross-Platfrom design?:

    Die eigentlichen plattformabhängigen Codeteile würde ich überhaupt gar nicht mit dem Präprozessor trennen. Das gehört ins Builscript. Wenn du für OS_A übersetzt werden eben andere Dateien übersetzt als für OS_B.

    Da schließe ich mich an. os_a.c und os_b.c sind eine gute Idee und m.E. gängige Praxis, aber das Präprozessor-Zeug würde ich nur machen, wenn es sich absolut nicht vermieden lässt - z.B. in einem gemeinsamen Header. Das erzeugt sonst nur unnötiges Code-Rauschen und schadet der Lesbarkeit. Wenn die falsche Datei für das OS kompiliert wird, dann läuft das eben vor die Wand. Genau so wie wenn eine notwendige Bibliothek nicht installiert ist oder der falsche Compiler verwendet wird.

    Eine Ausnahme würde ich allerdings bei feingranularen Unterschieden machen, die zu wenige sind um eine eigene C-Datei zu rechtfertigen oder die sich nur schlecht aus dem Code herauslösen lassen, so dass der Code drumherum ansonsten doppelt vorhanden müsste. Z.B. sowas hier:

    // os_a.c
    void Foo()
    {
        ...
        #if OS_A_VERSION >= 200
             moderne_os_funktion();
        #else
             legacy_os_funktion();
        #endif
        ...
    }
    

    Wenn diese Unterschiede aber zu viele werden, kann man auch hier überlegen, ob man den entsprechenden Code nicht in eine eigene Funktion in einer separaten Datei auslagert und auf die Präprozessor-Direktiven verzichtet.

    ja der code wird in cmake und auch mit msvc vs gebaut.. würde gehen.

    @Finnegan sagte in Frage Cross-Platfrom design?:

    @SeppJ sagte in Frage Cross-Platfrom design?:

    Die eigentlichen plattformabhängigen Codeteile würde ich überhaupt gar nicht mit dem Präprozessor trennen. Das gehört ins Builscript. Wenn du für OS_A übersetzt werden eben andere Dateien übersetzt als für OS_B.

    Da schließe ich mich an. os_a.c und os_b.c sind eine gute Idee und m.E. gängige Praxis, aber das Präprozessor-Zeug würde ich nur machen, wenn es sich absolut nicht vermieden lässt - z.B. in einem gemeinsamen Header. Das erzeugt sonst nur unnötiges Code-Rauschen und schadet der Lesbarkeit. Wenn die falsche Datei für das OS kompiliert wird, dann läuft das eben vor die Wand. Genau so wie wenn eine notwendige Bibliothek nicht installiert ist oder der falsche Compiler verwendet wird.

    Eine Ausnahme würde ich allerdings bei feingranularen Unterschieden machen, die zu wenige sind um eine eigene C-Datei zu rechtfertigen oder die sich nur schlecht aus dem Code herauslösen lassen, so dass der Code drumherum ansonsten doppelt vorhanden müsste. Z.B. sowas hier:

    // os_a.c
    void Foo()
    {
        ...
        #if OS_A_VERSION >= 200
             moderne_os_funktion();
        #else
             legacy_os_funktion();
        #endif
        ...
    }
    

    Wenn diese Unterschiede aber zu viele werden, kann man auch hier überlegen, ob man den entsprechenden Code nicht in eine eigene Funktion in einer separaten Datei auslagert und auf die Präprozessor-Direktiven verzichtet.

    also ohne präprocesseor in den code differenziert in verschieden c module die dann je nach build setup kompliert werden... wie auch firefly meinte?

    wie sieht es dann mit typedefs aus?

    //OS_A
    typedef Int16 osAint16
    
    //OS_B
    typedef Int16 osBint16
    

    müsste ich diese dann in jeweilige 2 header tun und die auch je nach build setup includen!?


  • Mod

    Nein, wenn ein Unterschied nach außen sichtbar ist (also am Ende im Header steht), dann ist die Zeit für Präprozessormagie gekommen.



  • @SeppJ sagte in Frage Cross-Platfrom design?:

    Nein, wenn ein Unterschied nach außen sichtbar ist (also am Ende im Header steht), dann ist die Zeit für Präprozessormagie gekommen.

    ok aber dann würde ich ja die seperierung des build files und preprocesser magie vermischen.. will man dass hmm.. oder hab ich as nicht verstanden?;)


  • Mod

    header.h

    #if defined(OS_A)
       #include <osa.h>
       typedef OsaType DataType;
    #elif defined(OS_B)
       #include <osb.h>
       typedef OsbType DataType;
    #endif
    
    void Foo(DataType dt);
    

    impl_a.c

    #include "header.h"
    
    void Foo(DataType dt) {...OS-A Zeug...}
    

    impl_b.c

    #include "header.h"
    
    void Foo(DataType dt) {...OS-B Zeug...}
    

    Buildscript baut entweder impl_a.c oder impl_b.c, und schert sich (wie es sich für ein Buildscript gehört) nicht um Header.



  • @SeppJ sagte in Frage Cross-Platfrom design?:

    header.h

    #if defined(OS_A)
       #include <osa.h>
       typedef OsaType DataType;
    #elif defined(OS_B)
       #include <osb.h>
       typedef OsbType DataType;
    #endif
    
    void Foo(DataType dt);
    

    impl_a.c

    #include "header.h"
    
    void Foo(DataType dt) {...OS-A Zeug...}
    

    impl_b.c

    #include "header.h"
    
    void Foo(DataType dt) {...OS-B Zeug...}
    

    Buildscript baut entweder impl_a.c oder impl_b.c, und schert sich (wie es sich für ein Buildscript gehört) nicht um Header.

    perfekt, danke dir



  • @SoIntMan sagte in Frage Cross-Platfrom design?:

    #if defined(OS_A)
    #include <osa.h>
    typedef OsaType DataType;
    #elif defined(OS_B)
    #include <osb.h>
    typedef OsbType DataType;
    #endif

    Wenn man es ganz schön machen wollte, könnte man hier auch was ähnliches wie HANDLE, HWND oder FILE einführen. Dadurch wäre der Header unabhängig von osa.h und osb.h.


  • Mod

    @Quiche-Lorraine sagte in Frage Cross-Platfrom design?:

    Wenn man es ganz schön machen wollte, könnte man hier auch was ähnliches wie HANDLE, HWND oder FILE einführen. Dadurch wäre der Header unabhängig von osa.h und osb.h.

    Stimmt. Für @SoIntMan : Das nennt sich PIMPL bzw. Opaque Pointer und das macht das nochmals viel besser (Auch allgemein, nicht nur für plattformabhängigen Code). https://en.wikipedia.org/wiki/Opaque_pointer



  • @SeppJ sagte in Frage Cross-Platfrom design?:

    Stimmt. Für @SoIntMan : Das nennt sich PIMPL bzw. Opaque Pointer und das macht das nochmals viel besser (Auch allgemein, nicht nur für plattformabhängigen Code). https://en.wikipedia.org/wiki/Opaque_pointer

    ja kenn ich benutzt ich an andere stellen;) in diesem platform context hab ich das mal außen or gelassen..

    danke


Anmelden zum Antworten