[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ] [ Search: ]

7.2 Portability

Written by Eric Sunshine, sunshine@sunshineco.com, with contributions by others.

Crystal Space is a highly portable cross-platform 3D engine and toolkit. As a contributor to the project, you should familiarize yourself with the issues discussed in this section in order to ensure maximum portability of code which you write and resources which you create.

  1. Endian-ness

    Beware of code that is endian-specific. To facilitate writing portable code, key off of the ‘CS_BIG_ENDIAN’ or ‘CS_LITTLE_ENDIAN’ macros if necessary.

    Avoid casting a ‘long*’ or ‘int*’ to a ‘short*’ or ‘char*’ as a slick way of checking the value of a particular sub-byte of the number since this trick will fail on platforms with different endian-ness. In other words, given the declaration ‘long n = 1’, and the expression ‘short p = *((short*)&n)’, on a little-endian platform, ‘p’ will equal 1, but on a big-endian platform, ‘p’ will equal 0.

    Avoid using C “unions” to represent bit-sets since they can result in endian-related problems if improperly used or stored as persistent data. Using bit-masks is a much better and far more portable solution.

    Beware of reading and writing data to binary format files and sending such information over the network. Unless special care is taken to ensure portability, these files and network related facilities will suffer endian-related problems. Use the network functions ntohl(), ntohs(), htonl(), and htons() to convert numbers to canonical format before transmitting them over the network. You can also make use of the Crystal Space endian-related conversion functions found in ‘CS/include/csutil/csendian.h’.

  2. Data Alignment

    Some platforms require data values to be strictly aligned in memory. For instance, some processors can not access a ‘long’ value at an odd memory address or at an address which is not a multiple of four (which is the size in bytes of the ‘long’ data type). In such cases, the program may crash. Other processors may be able to access misaligned values, but at greatly reduced speed, which can slow down program execution significantly if done frequently enough.

    In most cases, the compiler ensures that values are properly aligned for the given processor, but this problem can crop up when reading byte-streams from external sources such as binary files or network connections. In such cases, it is up to you to ensure the proper alignment of the data before accessing it.

    For instance, if you are reading elements from a byte-stream and the next element to be read is a ‘long’, then the following code to extract the value is incorrect and non-portable since it does not ensure that the data is aligned properly for ‘long’ access:

     
    long extract_long(unsigned char const* stream)
    {
      return *((long*)stream);
    }
    

    The correct way to code this example would be as follows:

     
    long extract_long(unsigned char const* stream)
    {
      long n;
      memcpy(&n, stream, sizeof(n));
      return n;
    }
    
  3. Data type sizes

    Don't make any assumptions about the size of ‘int’, ‘long’, ‘size_t’, ‘void*’ etc such as sizeof(int)==4, sizeof(int)==sizeof(void*), sizeof(long)==sizeof(void*) and so on. If a specifically sized type is needed (for example, when reading a file and the format specification dictates a certain word size) use the sized types from ‘cstypes.h’ (e.g. ‘int32’, ‘uint8’). Consequently, also avoid constructs such as casting pointers to ‘int’s. If there's a rare case that no other way than casting a pointer to/from an integral type, use the value of the ‘CS_PROCESSOR_SIZE’ macro (usually 32 or 64) to determine an appropriate sized type.

  4. Floating Points

    Depending upon the accuracy and representation of floating point values on some platforms, a number may successfully compare to (and equal) 0.0 on one platform or processor but fail on another. In order to avoid such problems, compare numbers to a small value such as ‘EPSILON’. In other words, if ‘EPSILON’ is defined as a very small value, use ‘(val < EPSILON)’ rather than ‘(val == 0.0)’.

    As a corollary. when comparing two floating point values, employ the ‘EPSILON’ trick to ensure that they compare properly over a wide variety of platforms. In other words, instead of ‘(float1 < float2)’, use ‘(float1 < float2 + EPSILON)’.

  5. Scope of ‘for’ Statements

    Some compilers do not properly scope a variable declared in a for(;;) statement to the loop itself. In other words, some compilers treat the following code:

     
    for (int i = 0; ...) ...
    

    As though ‘i’ was declared outside of the ‘for’ statement, as in:

     
    int i;
    for (i = 0; ...) ...
    

    This can be a problem when several ‘for’ loops exists at the same scoping level in a single block since some compilers will complain that ‘i’ is being redeclared by all ‘for’ loops following the first one. In other words, the following code will generate a “variable redeclaration” warning with some compilers:

     
    for (int i = 0; ...) ...
    ... other code ...
    for (int i = 5; ...) ...
    

    In cases where the variable appears in only a single ‘for’ loop, declaring it directly in the ‘for’ statement is perfectly safe, but in cases where many such loops may want to use the same variable name, the variable should be declared once outside of all loops, as in the following example:

     
    int i;
    for (i = 0; ...) ...
    ... other code ...
    for (i = 5; ...) ...
    

    The Microsoft Visual C++ compiler is known to suffer from this problem, however most modern C++ compilers properly scope variables declared in the ‘for’ statement.

  6. Avoid Global Objects With Constructors in Plug-In Modules

    Avoid placing global objects which require automatic construction in dynamically loaded libraries (plug-in modules). Some platforms fail to call the object's constructor at the time the library is loaded. Therefore it is unsafe to rely on such objects.

  7. Avoid Initializing Global Variables via Functions in Plug-In Modules

    Avoid initializing global variables within a plug-in module via function call. In other words, in the following example, the function foo() should not rely upon the variable ‘angle’ as having been properly initialized, assuming that this code fragment appears in a plug-in module.

     
    static float angle = cos(0.23);
    void foo() { printf("angle=%g\n", angle); }
    

    Instead, you should arrange for such variables to be initialized manually by some other mechanism. Here is one possible (though not ideal) way to ensure that ‘angle’ is initialized before it is accessed.

     
    static float angle = 0;
    void foo()
    {
      static bool angle_ok = false;
      if (!angle_ok)
      {
        angle = cos(0.23);
        angle_ok = true;
      }
      printf("angle=%g\n", angle);
    }
    

    An even better solution is to utilize the initialization hooks provided by the SCF system to initialize global variables at the time that the plug-in module is loaded. See section Shared Class Facility (SCF).

  8. Pathname Syntax

    Pathname syntax varies widely from platform to platform. For instance, a Unix-style pathname such as ‘/mnt/home/zoop.c’ might appear as ‘C:\home\zoop.c’ on DOS and Windows, and ‘vol:home:zoop.c’ on Macintosh.

    When programming you should always use Unix-style pathname syntax in your #include directives; that is, always use the forward slash ‘/’, as in #include "csutil/scf.h". The forward slash is understood by compilers on all platforms including Unix, Windows, and Macintosh. Never use ‘\’ or ‘:’ in filenames mentioned by an #include directive. Even though your Windows or Macintosh compiler might accept these characters, the Unix compilers will not. The obvious exception to this rule is for source files which are intended only for a specific platform. Such files may use the prohibited characters, but in general there is no reason to do so.

    At the application level, when writing a program or library which utilizes Crystal Space you should make use of the VFS facility which provides a unified way of referring to files by hiding platform-specific pathname syntax details. Under VFS's unified naming scheme, all pathnames use the Unix-style syntax and VFS translates such pathnames to a form appropriate for the host platform. See section Virtual File System (VFS).

  9. Filesystem Case Sensitivity

    Some operating systems have case sensitive filenames, whereas others do not. Undesirable things happen if you capitalize a file one way in an #include directive and a different way for the actual filename. This problem may not even be apparent on your platform if you are using a case-insensitive file system such as DOS, Windows, or Macintosh (HFS). In general, it is preferable to use entirely lower-case filenames for files which are shared between ports.

  10. ‘Jamfile’s and Project Files

    Some platforms use custom project files, whereas other platforms use the Crystal Space Jam-based build system. If you change a ‘Jamfile’ and then change the code so that it depends on this change, ports for other platforms will probably break. This may be unavoidable to some extent, but try to minimize the breakage. For example, devise a ‘configure’ check for the feature and wrap the dependent code in an #ifdef for a definition emitted when the feature was detected, or at least for operating systems that do support the change. This will allow other systems to continue to work without your change. After committing your change to the SVN repository, be sure to inform port maintainers that their projects may need to be updated in order to support your modifications.

  11. Ensuring Rendering

    Some renderers and video drivers depend on the BeginDraw() and FinishDraw() calls to ‘iGraphics3D’ or ‘iGraphics2D’. Thus every BeginDraw() must be followed by a FinishDraw() or nothing will be rendered. Microsoft's DirectX renderer is known to have this requirement, so be sure to follow this guideline for maximum portability.

  12. Template precautions

    These days, C++ compilers used with Crystal Space support templates, and the project makes use of templates for several utility classes. For portability reasons, however, the entire template implementation should be placed into the header file. There should be no associated source code file.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

This document was generated using texi2html 1.76.