If you’ve ever had to write a portable application in C, you’ve likely run into the same problem faced by countless other programmers: no matter how much you try to stick to a well-defined application programming interface (API), the program just doesn’t work the same on every platform.
While POSIX does a passable job of providing a portable API for most Unix and Unix-like platforms, POSIX either doesn’t exist on other operating systems or is so full of bugs as to be unusable. Moreover, POSIX isn’t always the best choice. Non-Unix platforms, such as Microsoft Windows, have their own APIs that are better mantained and perform better on that platform.
So, to make something portable, you could write, rewrite, and tweak your code several times — at least so the code compiles on several platforms. Or, you can use the Apache Portable Runtime (http://apr.apache.org/) the same library that makes the ubiquitous Apache HTTP server portable. If it’s good enough for Apache, well, enough said.
Apache 1.3 was ported to a variety of platforms, including many that weren’t POSIX based, such as Windows, OS/2, and BeOS. On those platforms, Apache 1.3 often relied on #ifdef blocks to acheive portability, effectively forking the source into mainline code and platform-specific code, making the code harder to read, debug, and maintain.
When development started on Apache 2.0, the developers knew that they needed a better solution. Initially, two existing solutions were considered. One was the Adaptive Communication Environment (ACE), and the other was the Netscape Portability Runtime (NSPR). However, both were rejected.
ACE was implausible because Apache requires that all code be written strictly in C, and ACE is a combination of C and C++. And while NSPR looked like a good fit, it’s license was incompatible with Apache’s. (The licensing issues were eventually resolved, but by that time, APR was already in development.)
Nonetheless, writing APR from scratch has worked well for the Apache community — and others. (See the sidebar, “Who’s Using the Apache Runtime?” for more details.) It’s a portability layer specifically written with servers in mind.
Let’s see how to write applications with APR. And to appreciate APR’s power, let’s start with code that looks portable, but’s not. As you’ll see, there are devils in the details.
Almost Portable
Some code is inherently portable, because it uses very well documented APIs
that are implemented everywhere. For example, the code…
char *var = getenv(”SHELL”);
… compiles and runs on all platforms, but there are subtleties that may make it behave differently. For instance, is the SHELL variable name case sensitive? On Unix it is, but on Windows, it isn’t. Also, on Windows, applications can be compiled in either UNICODE or ANSI modes. If your application is compiled for UNICODE, then the environment table is UNICODE — but this code always tries to read the environment variables as ANSI strings. These details can be absolutely infuriating for any developer.
In APR, this same concept can be written as:
char *var;
apr_status_t rv;
rv = apr_env_get(&var, “SHELL”, p);
In this case, the code isn’t much more complex than the original, and it resolves the issues of the original code. Because APR always uses native functions under the covers, APR is able to determine if it should be reading as UNICODE or ANSI and react accordingly.
Listing One shows a very simple program that demonstrates getting a single environment variable.
No comments yet.