Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Writing Portable Applications with APR


Writing Portable Applications with APR

Anybody who has ever written a C or C++ program that must work on many operating systems has faced the same problem: not all C/C++ libraries are portable. Every platform implements POSIX functions slightly differently, and if your program needs to work on platforms other than Unix, such as Windows, the problem is made worse by the fact that those platforms have their own APIs. Although those platforms have a POSIX implementation, the native APIs are always faster and have fewer bugs than the POSIX APIs.

Programmers have developed many solutions to the portability problem through the years. The first solution is to program using strictly POSIX functions because most platforms have a POSIX layer. This solution works, but POSIX causes its own problems. For example, on Unix platforms, writing in append mode ensures that lines are always printed at the end of the file. Windows, however, has no way to do this. So, to emulate append mode in Windows, you must seek to the end of the file and write. Of course, seek and write aren't atomic operations, which would require a lock. But Windows has a solution to this problem if you use native functions. It is possible to open a file for overlapped I/O, which allows you to seek and write in a single atomic operation.

The second option is to pick a core platform and find an emulation library for all other platforms. Many programmers use Unix as their core platform and Cygwin as their emulation layer. Although this solution works, applications that run within this kind of environment are running in an emulation mode, which means they don't behave like native applications to the user, and they are usually slower than native applications.

The third solution is for the programmer to write a portability library that abstracts the differences between platforms. The problem with this solution is that writing portable code isn't easy, and it takes a long time and a lot of testing to get a robust library. Unless your goal is to create a portable runtime, you are spending time duplicating work that has already been done many times.

The final option is to find a portable and robust library that has already been written. The rest of this article discusses the Apache Portable Run-time (APR), an Open Source portability run-time that aims to solve the C/C++ portability problem. This project is covered by the Apache License, which is a BSD-like license.

Introducing APR

Apache 1.3 was ported to many versions of Unix, Windows, OS/2, BeOS, Netware, OS/390, and AS/400. But, Apache 1.3 relied on POSIX for it's portability, which means that although it worked (for the most part), it wasn't always stable on non-Unix platforms. Apache 1.3 also had performance problems on Windows, because the POSIX wrappers have performance problems. Finally, to fix some of these problems, the Apache developers chose to fork some of the Apache 1.3 code using #ifdefs so that the important parts of the code would be written specifically for each platform. This approach caused maintenance problems as the product was updated, a common problem with programs that choose to use POSIX for portability.

The Apache Foundation developed APR as a part of creating the second version of the Apache web server. We wanted to port Apache 2.0 to as many versions of Unix as possible, as well as Windows, OS/2, BeOS, Netware, OS/390, and AS/400, and other platforms. However, we also wanted to solve all of the problems associated with Apache 1.3. To that end, APR uses native function on all platforms and only relies on POSIX when POSIX is the best option. Also, because all of the platform differences are isolated in the APR layer, the Apache code eliminates most of the #ifdefs that have caused such a maintenence problem. At its most basic level, APR is just an abstraction layer between the operating system and the application. When an #ifdef does occur in most code that uses APR, it almost always refers to one of APR's feature macros and doesn't tie the code inside the #ifdef to a particular platform.

APR is a runtime library analagous to the C runtime or the Microsoft runtime. To use APR, you must adapt your code to call APR equivalent functions. For example, if you are trying to open a file, instead of calling fopen or CreateFile, you would call apr_file_open. Of course, introducing APR functions to your code means that you are tying your application to APR, but the payoff is an application that works on more platforms. APR has functions for all of the most common operations. For a small sample of the features available in APR, see Table 1.

Although Apache was created to support the Apache web server, the Apache Foundation makes APR available to all programmers as a general purpose portability tool. To use APR in your programs, you will have to link against the APR library for your system. Currently APR developers are only distributing source code, so you will have to build APR before you can use it in your applications. APR uses a standard autoconf build system on Unix, so to build APR run the commands:

./configure; make; make install
For builds on Windows, APR has a project file that you can use in Visual Studio. Once the library is built, you need to link it into your program. This is done with the -l flag to the linker on Unix. On Windows, you will have to add APR to your project.

The rest of this article examines two standard Unix utilities re-written with APR to demonstrate how APR can solve the portability problem. These programs are not easily available on Windows as native programs. They are available as part of most Unix portability packages, such as Cygwin, but since they are not native programs, the Windows versions do not behave like standard Windows programs. The APR-based examples provide a more native implementation to the utilities. The example programs described in this article are intended for illustration purposes and do not implement all of the options found in their Unix equivalents.

A simple cat program

The first program, cat.c (see Listing 1), is a simplified version of the Unix cat command. The first two lines that I want to draw your attention to are lines 13 and 14. These lines are always found at the beginning of every APR program.The apr_app_initialize function allocates some internal APR variables and ensures that APR is configured properly for the current machine. The second line sets up apr_terminate, which will be called when the program exits. Just as apr_app_initialize allocates some memory, apr_terminate deallocates the same memory. On some platforms, apr_terminate releases semaphores, so if this function isn't called, you may find that other programs fail in unexpected ways.

After these initial setup steps, the program creates its first memory pool. If APR has a major drawback, this is it. APR was designed around a very specific memory model: pools. The idea is that memory is allocated early and reused as often as possible. This design can be a major advantage for programs that do the same operations repeatedly, because the memory usage hits a steady state and the same memory can be used repeatedly. However, pools may not work well for programs that perform many different tasks, such as games, which are constantly changing their memory usage. For a more complete description of memory pools, see the sidebar.

The next two lines open the two file descriptors that I need for this application. The first line is stdout. Most programmers are used to using stdout for this purpose, but stdout doesn't always work if you are in Windows. For example, it is standard practice in a Unix daemon to redirect stderr to a log file for easy debugging. Windows Services, however, do not have stdin, stdout, or stderr. By providing functions to access the equivalent of those file handles, APR can remove a major portability hurdle. The second file descriptor is to the file that we want to read. Unfortunately, file permissions are not well done in APR and are mostly meaningless on non-Unix platforms. This is a hard problem, and hopefully the APR developers will tackle it in a later release. Also notice that I check for success using APR_SUCCESS. This check is standard in APR; almost all APR functions return APR_SUCCESS if the function finished successfully and the exact error code if it did not. Functions that do not do this generally cannot fail and so do not return any value.

Finally, the program loops through the file, reading one line at a time and writing it to stdout. Notice that I did not close either file descriptor. The pool model lets APR applications drop file descriptors when they are no longer needed. APR applications can register cleanups to run when a pool is cleared or destroyed. When APR opens a file, socket, or any other resource, it registers a cleanup to run when the pool is cleared. For files, the cleanup closes the file descriptor. As long as pools are used judiciously, this ensures that resource leaks are rare because resources are cleaned as a part of memory management.

A Version of rm for Windows

rm.c, see Listing 2, is a replacement for rm. Like the cat replacement, this program starts by preparing for APR.The program then opens stdin and stderr. The rm application prompts the user for information while executing, so I need a way to read the response. The really interesting logic happens from line 37 to line 52, however. Getopt is a simple function that is standard in Unix for processing command line arguments. Many Unix programmers find it hard to believe, but there is no equivalent function in the Windows API. That means that all Windows programmers must re-implement command line parsing. APR has solved this problem by providing a robust implementation of getopt for all platforms. In fact, APR uses its own getopt implementation even if the current platform provides an implementation. The problem is that getopt is not exactly standard on Unix either. Some Unix variants allow optional arguments, others do not. Some platforms have getopt implementations that allow long option names, such as --force, others do not. Rather than allow programmers to write non-portable software, APR imposes its getopt version.

To use getopt, you must initialize it first using apr_getopt_init. Then you can loop, calling apr_getopt every time through the loop. As long as apr_getopt returns APR_SUCCESS, you know that the next option on the command line is an acceptable argument. In this case, I have defined f and i to be the only arguments I will accept, and neither takes an argument. After returning from getopt, you can act on the option. In this case, I am keeping track of the number of options so that I can find the list of files to delete later on. Then, depending on whether the user told us to force the delete (f), or prompt interactively (i), I set a boolean. If an unrecognized option is given, I call a simple program that reminds the user of the possible options and exits.

One quick warning about apr_getopt. Like most getopt implementations, it automatically prints an error if an illegal flag is passed to the program. For example, if -G is passed to rm, the following error message appears:

a.out: illegal option -- G
./a.out [-fi] file_name
Notice that I did not put the illegal option error message in the code.

This message can be suppressed by adding the line

opt->errfn = NULL
after the call to apr_getopt_init. This line tells APR to leave it up to the programmer to print all error messages from apr_getopt. Most people will not want to do this, because printing the error message is standard for most getopt implementations.

Now we get to the meat of the program: a simple loop that tries to delete every file in the argument list. This simple example does not allow the user to delete directories. To support directory deletion, you would need to recursively delete every file in the directory, which you could easily add using apr_dir_read, but that step is left as an exercise for the reader. In order to keep people from deleting directories though, I must know when somebody tries to do so. This can be done using apr_stat. If you are used to using the standard stat function, apr_stat will look a little strange. The strangest part is the third argument, which in this case is APR_FINFO_TYPE. The APR developers had two major goals when writing APR: portability and performance. Often those goals are in conflict; apr_stat is a good example of this. The problem is that stat is a very expensive call, and some platforms (Windows most notably) can return some information very easily, while other information requires more time. So, to balance between portability and performance, the third argument was added to apr_stat. This third argument is an OR'ed list of the type of information that you want returned. The contract from apr_stat is that it can return more information than you have asked for, but it can never return less (unless there is an error). Since all I care about is the type of the file, that is all I have asked for.

If the user does ask to delete a directory, the program prints a simple error message and continues to the next file on the command line. However, notice the error message that is printed. This is standard practice for APR applications. The APR_EOL_STR macro is defined by APR to be the correct end-of-line sequence for a given platform, so for Unix it will map to \n, but on Windows it is \r\n.

After checking for directories, the program checks if the user should be prompted before deleting the file and, if so, prompts accordingly. Assuming the user has elected to continue, the program can finally delete the file.

Conclusion

Both of these examples were very simple, and they have barely scratched the surface of what APR is capable of. APR is already being used by some of the most portable applications available today. While APR's original charter was to abstract out the lowest level functions, such as file I/O, network I/O, and shared memory, it has gone much farther than that. APR has two sub-projects, apr-util and apr-iconv, that provide more portable features. apr-util includes string matching routines, encoding routines, and Database access. apr-iconv, on the other hand, only provides one feature; portable character set conversion. For anybody who has ever tried to use iconv on a wide array of platforms, you know that most platforms either do not support iconv, or they have a wide-array of supported features. apr-iconv is an attempt to solve this problem.

You'll find more information on APR, including full source code, at <http://apr.apache.org>. The website includes full documentation for all APR APIs, as well as information on how to contribute to the APR project.

About the Author

Ryan Bloom is a consultant for TechLink Systems, currently working with Covalent Technologies. He was one of the creators of the Apache Portable Run-time project and is the author of "Apache Server 2.0: The Complete Reference". He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.