Main Page | Modules | File List | Globals | Related Pages
As a general rule, there is no great mystery when it comes to writing portable software. There are a certain number of obvious gotchas a programmer encounters, and techniques that a programmer learns, to make sure his or her software compiles and runs on a wide range of hardware, compilers and operating systems.

Unfortunately, a lot of the work that programmers undertake when writing cross-platform software is redundant, tedious, error prone and, well, boring. The most fundamental, basic tasks -- inferring your current configuration environment, byte order/endian safety routines, import/export function signatures for Windows DLLs, correctly sized data types -- are performed over and over by thousands of programmers around the world.

The Catalyst

Every major cross-platform, open source project provides these facilities, usually in the form of a "config.h" or "types.h" header and some associated source files, but extracting just the configuration elements from these projects can prove cumbersome and, in the case of licensing, impractical.

I ran into this problem when I was trying to extract some of my own code to make it open source. That sub-library was dependent on a lot of bits of unrelated code, mostly having to do with the issues I described earlier. So I did the obvious thing -- I extracted those pieces separately, and made a "libconfig.h", and all was well.

Until I wanted to open source another sublibrary.

Now, was I going to basically copy and rename a bunch of stuff in "libconfig.h", or was I going to figure out a way of sharing libconfig.h between two completely unrelated projects? I opted for the latter, and it was about that time that I realized that no one else has tried to make a cross-platform, project agnostic "libconfig.h".

And thus the Portable Open Source Harness was born.

The Goals

I set out to make a single header file that could compile almost anywhere and, in the process, tell me about the host platform and target system. My goals were simple:

Once those goals were defined, I then had to determine what specific features I wanted POSH to have. The original idea was to create a set of sized types; define macros that indicated target operating system, CPU and endianess; and proper handling of import/export identifiers when building Windows DLLs.

After discussion with some friends, I decided to add support for in-memory serialization/deserialization, verification and architecture string reporting. These elements required the addition of a single source file. Things were already getting more complicated, but two source files is livable complexity.

But in the end, POSH still basically does two things: compile time configuration management and optional run-time routines for endianess conversion, serialization and verification.

Configuration Management

POSH's configuration management consists of detecting the target architecture and operating system, along with the current compiler, and appropriately defining a set of manifest constants that can be "queried" during compilation. POSH only tries to infer basic information such as data type sizes; endianess; pointer size; and the availability of 64-bit integers. More complex information, such as the existence of certain packages, APIs or operating system features, is not supported.

Another key element of POSH's configuration management is the automation of the various magic keywords and linkage specifications necessary to create and use a Windows DLL (specifically, I'm talking about __declspec(dllimport) and __declspec(dllexport)).

The final element of POSH's configuration management is defining a set of correctly sized types (including 64-bit integers) that an application can count on.

Optional Utility Functions

In addition to configuration management, POSH provides a set of optional functions and macros that are relevant and useful to nearly all applications. These routines include retrieving a string representation of the current platform; endianess conversion; and macros for compile time assertions.

What POSH is Not

One problem with many open source projects is overambitiousness -- a simple concept takes on a life of its own and becomes too big and unwieldy for its own good. For that reason, I've gone out of my way to define what POSH is not and what it will hopefully never be.

POSH is not a general cross-platform framework such as SDL, Qt, wxWindows or GTK. It is focused on providing an extremely simple set of features that are useful regardless of the user's domain of interest.

POSH is not an abstraction library. It does not attempt to abstract platform details in a way that isolates the programmer from the underlying system. In fact, POSH does quite the opposite by trying to communicate to the programmer (via the build system) as much about the target and host platforms as possible.

For example, POSH provides a common access mechanism to the compiler's underlying 64-bit integer type, but if the compiler does not support this feature, POSH simply punts, as opposed to trying emulate such support.

Finally, POSH is not religious about how extreme it will be when trying to achieve portability. At some point, you have to just say "screw it" when a platform fights you too much. If a particular architecture or compiler is so idiosyncratic that it breaks the rest of POSH, I will not compromise the cleanliness or functionality of POSH just so we can say we run on yet one more platform.

Assumptions

It's pretty hard to write code that is transparently, 100% portable while still remaining vaguely readable and easy to maintain. It's possible, but it sure isn't particularly pleasant.

For this reason, POSH isn't trying to be compatible with every compiler, CPU and operating system combination ever made. It makes a fundamental set of assumptions, and if it finds that those assumptions are invalid (via compile time assertions), it will generate an error during compile time. I simply refuse to make POSH less functional and/or less clean to appease less popular architectures and development tools, even though this may go against the grain of the whole portability argument.

In certain (hopefully) rare instances, POSH_GetArchString() will report an error when a run-time analysis does not match the static analysis of the target operating system's characteristics. You should always check POSH_GetArchString() at least once during development for a quick sanity check.

If an error is encountered, please contact us at poshlib@poshlib.org with the specifics and we'll try to see what went wrong and, if possible, fix it for later versions of POSH.

Floating Point Assumptions

Cross-platform floating point is a nightmarish situation, so to stay sane I have to make the following assumptions:

The size assumptions are necessary for the serialization/deserialization routines. The floating point format assumption is necessary so that if you serialize a float value on one system, it can be loaded successfully on another architecture. Fortunately, the vast majority of systems with functional floating point do adhere to the above conventions.

Just for completeness, the IEEE single-precision (float) format is:

S EEEEEEEE FFFFFFFFFFFFFFFFFFFFFFF
0 1      8 9                    31

The IEEE double-precision (double) format is:

S EEEEEEEEEEE FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
0 1        11 12                                                63

Minor note: POSH doesn't care about how certain bit patterns are interpreted, so if a system uses the above bit representations but doesn't handle NaN, infinity or denormals correctly, then technically POSH is cool since the serialization/deserialization aspects are still valid.

(For those wondering, some examples of systems that have native floating point but which are not IEEE compliant include DEC VAX; some Cray; some embedded processors; and various mainframe/supercomputer architectures.)

In the event that these assumptions are not valid, you can manually disable POSH's floating point support by defining the symbol POSH_NO_FLOAT globally via a compiler switch, or just by inserting #define POSH_NO_FLOAT 1 at the top of posh.h.

Pointer Size Assumptions

POSH makes a "soft" assumption that pointers are at least 32-bits, and possibly 64-bits. In the latter case, it will define the constant POSH_64BIT_POINTER.

In theory POSH will work just fine on architectures with, say, 16-bit pointers, however it's never been tested and POSH doesn't give you any way to detect this situation. POSH basically assumes that if it's not a 64-bit architecture, then it must be a 32-bit architecture.

If the sizeof(void*)==4 compile time assertion in posh.h is hit and you're aware of the ramifications, feel free to remove the assertion and continue using POSH.

ANSI C/C++ Compiler

POSH assumes that you're building with an ANSI C/C++ compliant compiler, or at the very least one that won't choke on function prototypes. The sheer grossness required to support compilers that don't have function prototype support simply isn't worth the incremental advantages.
Generated on Tue Jan 31 18:27:35 2006 for POSH by doxygen 1.3.7