Frage Cross-Platfrom design?



  • @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



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

    @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..

    PIMPL würd ich auf jeden Fall empfehlen, falls z.B. lediglich für einen Datentyp irgendwo <windows.h> und Konsorten in einem Header mit inkludiert werden müssten. Sonst holt man sich ganz schnell die Krätze ins Haus. In dem Kontext würde ich dann auch den anderen Namen für PIMPL verwenden: compilation firewall 😁

    (Sorry, das musste sein. Die oben erwähnten HANDLE, HWND und FILE haben mich getriggert 🙄)



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

    PIMPL würd ich auf jeden Fall empfehlen, falls z.B. lediglich für einen Datentyp irgendwo <windows.h> und Konsorten in einem Header mit inkludiert werden müssten. Sonst holt man sich ganz schnell die Krätze ins Haus. In dem Kontext würde ich dann auch den anderen Namen für PIMPL verwenden: compilation firewall
    (Sorry, das musste sein. Die oben erwähnten HANDLE, HWND und FILE haben mich getriggert )

    gibt es auch ein art Opaque typedef, analog zu struct?;)



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

    gibt es auch ein art Opaque typedef, analog zu struct?;)

    Ich verstehe die Frage nicht ganz.

    Ziel ist es doch Implementierungsdetails zu verbergen. Und solange du dies beherzigst, kannst du Handles frei nach Herzenslust definieren.

    Das kann ein Pointer sein, ein Index, ein Bitfeld, ein Typedef, ein Struct,... sein.

    Hauptsache die Implementierungsdetails sind nach außen nicht sichtbar.



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

    gibt es auch ein art Opaque typedef, analog zu struct?;)

    Nein, aber z.B. bei Windows-Typen mache ich das dann meistens so oder ähnlich:

    // public header
    struct os_data_t;
    
    class Window
    {
        ...
        private:
            os_data_t* os_data;
    };
    
    // os_a.c
    #include <windows.h>
    
    struct os_data
    {
        HWND hwnd;
    };
    
    ...
    

    Wichtig ist halt, im Header das #include <windows.h> zu vermeiden, damit man sich nicht so übles Zeug wie z.B. #define min ... in seinen gesamten Code reinzieht und dann bei jedem std::min(a, b) einen Heidenspaß bekommt 😉 ... es gibt zwar #define NOMINMAX für speziell dieses Problem, aber die Header haben auch noch ne Menge anderer Makros, die für Überraschungen sorgen können (z.B. near / far und solche Dinge).



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

    Wichtig ist halt, im Header das #include <windows.h> zu vermeiden, damit man sich nicht so übles Zeug wie z.B. #define min ... in seinen gesamten Code reinzieht und dann bei jedem std::min(a, b) einen Heidenspaß bekommt

    Spaßig ist auch winsock2.h. 😉



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

    Spaßig ist auch winsock2.h. 😉

    Nicht viel mit der gemacht, aber beim groben Überfliegen fallen mir schon ein paar ungünstige Makros auf: SOCKET_ERROR, NO_ADDRESS, NO_DATA, HOST_NOT_FOUND, BIGENDIAN, LITTLEENDIAN (Kollision mit eigenen enums),. Überhaupt, viel zu viele Makros. Ich denke du meinst sowas, oder?


Anmelden zum Antworten