views:

516

answers:

10

I'm currently working on cross-platform applications and was just curious as to how other people tackle problems such as:

  • Endianess
  • Floating point support (some systems emulate in software, VERY slow)
  • I/O systems (i.e. display, sound, file access, networking, etc. )
  • And of course, the plethora of compiler differences

Obviously this is targeted at languages like c/c++ which don't abstract most of this stuff (unlike java or c#, which aren't supported on a lot of systems).

And if you were curious, the systems I'm developing on are the Nintendo DS, Wii, PS3, XBox360 and PC.


EDIT
There have been a lot of really good answers on here, ranging from how to handle the differences yourself, to library suggestions (even the suggestion of just giving in and using wine). I'm not actually looking for a solution (already have one), but was just curious as to how others tackle this situation as it is always good to see how others think/code so you can continue to evolve and grow.

Here's the way I've tackled the problem (and, if you haven't guessed from this list of systems above, I'm developing console/windows games). Please keep in mind that the systems I work on generally don't have cross-platform libraries already written for them (Sony actually recommends that you write your own rendering engine from scratch and just use their OpenGL implementation, which doesn't quite follow the standards anyway, as a reference).

Endianess
All of our assets can be custom made for each system. All of our raw data (except for textures) is stored in XML which we convert to a system specific binary format when the project is built. Seeing as how we are developing for game consoles, we don't need to worry about data being transfered between platforms with different endian formats (only the PC allows the users to do this, thus, it is insulated from the other systems as well).

Floating point support
Most modern systems do floating point values fine, the exception to this is the Nintendo DS (and GBA, but thats pretty much a dead platform for us these days). We handle this through 2 different classes. The first is a "fixed point" class (templated, can specify what integer type to use and how many bits for the decimal value) which implements all arithmetic operators (taking care of bit-shifts) and automates type conversions. The second is a "floating point" class, which is a basically just a wrapper around the float for the most part, the only difference is that it also implements the shift operators. By implementing the shift operators, we can then use bit shifts for fast multiplications/divisions on the DS and then seamlessly transition to platforms that work better with floats (like the XBox360).

I/O Systems
This is probably the trickiest problem for us, because every system has there own method for controller input, graphics (XBox360 uses a variant of DirectX9, PS3 has OpenGL or you can write your own from scratch and the DS and Wii have thier own proprietary systems), sound and networking (really only the DS differs in protocol by much, but then they each have their own server system that you have to use).

The way we ended up tackling this was by simply writing fairly high level wrappers for each of the systems (e.g. meshes for graphics, key mapping systems for controllers, etc.) and having all the systems use the same header files for access. It's then just a matter of writing specific cpp files for each platform (thus forming "the engine").

Compiler Differences
This is one thing that can't be tackled too easily, as we run into problems with compilers, we usually log the information on a local wiki (so others can see what to look out for and the workarounds to go with it) and if possible, write a macro that will handle the situation for us. While its not the most elegant solution, it works and seeing how some compilers a simply broken in certain places, the more elegant solutions tend to break the compilers anyway. (I just wish all of the compilers implemented Microsoft's "#pragma once" command, so much easier than wrapping everything in #ifdef's)

+8  A: 

A great deal of this complexity is generally solved by the third party libraries (boost being the most famous) you are using. One rarely writes everything from scratch...

Edouard A.
i agree with you, using multi-platform libraries is a very good way to deal with this issue
thrantir
+4  A: 

I usually encapsulate system-specific calls in a single class. If you decide to port your application to a new platform, you only have to port one file...

milan1612
A: 

For things like endianness using different functions or classes is a bit nore overhead. try using the pre-processor like:

#ifdef INTEL_LINUX:
   code here
#endif

#ifdef SOLARIS_POWERPC
   code here
#endif
Xolve
Do a lot of Solaris PowerPC coding? :)
postfuturist
-1: test for features, not specific platforms
dwc
@steveth45: No I just quoted an example and power pc is big endian as compared to x86
Xolve
@dwc: Well is for same feature on different platform.
Xolve
+2  A: 

Usually, this kind of portability problem are left to the build system (autotools or cmake in my case) which detect specific of the system. Finally, I get a config.h from this build system and then I just have to use constant defined in this header (using IF DEFINED).

For example here is a config.h :

/* Define to 1 if you have the <math.h> header file. */
#define HAVE_MATH_H

/* Define to 1 if you have the <sys/time.h> header file. */
#define HAVE_SYS_TIME_H

/* Define to 1 if you have the <errno.h> header file. */
#define HAVE_ERRNO_H

/* Define to 1 if you have the <time.h> header file. */
#define HAVE_TIME_H

Then the code will look like this (for time.h for example) :

#ifdef (HAVE_TIME_H)
//you can use some function from time.h
#else
//find another solution :)
#endif
claferri
+3  A: 

I normally use multi-platform libraries, like boost or Qt, they solves about the 95% of my problems dealing with platform specific codes (i admit the only platform i-m dealing with are win-xp and linux). For the remaining 5%, I usually encapsulate the platform specific code in one or more classes, using factory pattern or generic programming to reduce the #ifdef/#endif sections

thrantir
+1  A: 

For data formats - use plain text for everything. For compiler differences, be aware of the C++ standard and make use of compiler switches such as g++ -pedantic, which will warn you of portability problems.

anon
+1  A: 

It depends on the kind of things you are doing. One thing which is almost always the right choice is to port the basic stuff to any target platform, and then deal with it with a common API.

For example, I do a lot of numerical computation coding, and some platforms have a lot of broken/non standard code: the way to solve it is to reimplement those functions, and then use those new functions everywhere in your code (for platforms which work, the new function just calls the old one).

But this only really works for low level stuff. For GUI, high level IO, using an already existing library is definitely a better option almost every time.

David Cournapeau
+4  A: 

For endian issues in data loaded from files, embed a value such as 0x12345678 in the file header.

The object that loads the data, look at this value, and if it matches its internal representation of the value, then the file contains native endian values. The load is simple from there.

If the value does not match, then it is a foreign endianism, so the loader needs to flip the values before storing them.

EvilTeach
+3  A: 

I think the other answers have done a great job of addressing all your concerns except for endianness, so I'll add something about that... it should only be a problem at your interfaces to the outside world. All your internal data processing should be done in the native endianness. When communicating via TCP/IP (or any other socket protocol), there are functions you should always use to convert your values to and from network byte order. IIRC, the functions are htons() and htonl(), (host to network short, host to network long) and their inverses, which I can't remember... perhaps something like ntohl(), etc?

The only other place you should be interacting with data that has the wrong byte order is reading files from your local hard drive, so make your file loaders and writers use similar functions (perhaps you can even get away with using the network functions).

By using these library-provided functions for dealing with endianness always (use them even in code you never intend to port, unless you have a compelling reason not to -- it'll make like easier later when you decide to port), you can run the code on any platform and it will "just work", regardless of the native endianness.

rmeador
+1  A: 

For platforms without native floating point support, we have used some own fixed point type and some typedefs. Like this:

// native floating points
typedef float Real;

or for fixed points something like:

typedef FixedPoint_16_16 Real;

Then math functions may look this:

Real OurMath::ourSin(const Real& value);

Actual implementation might ofcourse be:

float OurMath::ourSin(const float& value)
{
  return sin(value);
}
// for fixed points something more or less trickery stuff
Virne