Portability Tips, Part I: Compilers

Background

I'm coming at this as a freeware author
of programs for which full source code is available in addition to the
executables, and which must compile with as many compilers as possible
on as many platforms as possible.
This necessarily leads to a ``lowest common denominator'' bias which I'll
acknowledge at the outset. In addition, Tips of the Month tend to be
somewhat brief, and portability is a topic which could consume volumes...

Makefiles

In this age of GUI development and integrated development environments
(IDEs), command-line-oriented makefiles are clearly a relic of an earlier
age. Yet they are not only very portable but also extremely powerful.
Using recursion--that is, further make commands embedded within
the makefile--one can:

provide targets for cross-compilation across OS/2, DOS, NT and possibly
other operating systems

provide targets for multiple-compiler support on a single platform

provide targets for multiple compilation options for a single compiler
(e.g., both statically and dynamically linked versions of a program)

The OS/2 makefile for Info-ZIP's
UnZip
contains examples of all of these. Note that some basic makefile concepts
such as using the same object module in more than one program are impossible
to implement with some IDEs (notably Borland's old project files).

Macros

Also known as "ifdefs," macros--particularly those of the predefined
persuasion--are perhaps the most useful of all portability tools. Predefined
macros can identify the operating system, compiler, compiler version, hardware
and memory model, among other things. Some of the more useful ones include:

OS2 or __OS2__

MSDOS or __MSDOS__

__32BIT__

__GNUC__

__IBMC__

__WATCOMC__

__BORLANDC__

_MSC_VER

__VERSION__

__SMALL__ or M_I86SM

As an aid in creating portable code these might be used to conditionally
include certain header files whose names differ among various compilers,
or to set buffer sizes based on the memory model or OS version.

Predefined macros are also useful for debugging portable code.
The various version() routines in UnZip identify the compiler and
model used to compile the executable, something which is vital when
full source code is available and there are no guarantees that the user
is using the "official" versions. The
OS/2 version() routine is basically one big
example of how to use the macros listed above. (Note that the Borland
version numbers are derived from the DOS product and are probably wrong
for the OS/2 version.)

32-bit vs. 16-bit

OS/2 1.x is about as popular these days as CP/M, but for those who wish
to support it there are a number of potential gotchas.

First and foremost are the new APIs introduced in OS/2 2.x and 3.x, such
as Drg and Dive. I don't know if somewhere exists a list of
new APIs as a function of the OS/2 version in which they first appeared, but
I certainly don't have this information.

Second are the old APIs which have been dropped or which are expected to
be dropped in Workplace OS, such as the Kbd, Mou and Vio
functions. These are probably less of an issue since most of today's
programmers start in OS/2 2.x or 3.0 rather than 1.3 or earlier. (By the
way, a micro-tip:
online documentation
for these APIs is available from the Hobbes archive.)

Third and trickiest are the common APIs which either acquired extra
parameters in later versions of OS/2, changed name or simply had a painful
transition from 16-bit variables to 32-bit. Here, for example, is how one
might define a few macros so that what look like 32-bit function calls can
be used for both 16-bit and 32-bit code:

Note that the DosFindFirst() macro doesn't look exactly like either the
16-bit or the 32-bit function call; this is a place where the unwary can
get tripped up by failing to notice the macro definition when modifying
the code.

Here's an example where not only were parameters added, but the order of
parameters was changed. In this case a wrapper function was created in
order to simulate the 16-bit behavior even in 32-bit code:

Finally, here's a case where the size of a variable changed, in this case
a struct. This routine is both more and less than a simple wrapper, by
the way; it formats the file's timestamp and returns that as a long. Note
the use of the DosQueryPathInfo macro from above:

Acknowledgments

The Info-ZIP
code is the sum of contributions from many people, and the OS/2-specific
code is largely due to Kai Uwe Rommel, a veritable god of portability (or
at least of porting, especially from Unix to OS/2). In particular, the
makefile and 16/32-bit code samples used here
are principally Kai Uwe's code. The version()
routine is by Greg Roelofs.