[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Written by Eric Sunshine, [email protected], 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.
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'.
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; } |
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.
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)'.
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.
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.
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).
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).
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.
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.
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.
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 ] | [ >> ] |
This document was generated using texi2html 1.76.