Portability Tips, Part I: Compilers

Greg Roelofs, Info-ZIP


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:

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:

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:

#ifdef __32BIT__
#  define DosFindFirst(p1, p2, p3, p4, p5, p6) \
          DosFindFirst(p1, p2, p3, p4, p5, p6, 1)
#else
#  define DosFindFirst(p1, p2, p3, p4, p5, p6) \
          DosFindFirst(p1, p2, p3, p4, p5, p6, 0)
#  define DosMapCase DosCaseMap
#  define DosQueryCtryInfo DosGetCtryInfo
#  define DosQueryCurrentDisk DosQCurDisk
#  define DosQueryFSAttach(p1, p2, p3, p4, p5) \
          DosQFSAttach(p1, p2, p3, p4, p5, 0)
#  define DosQueryPathInfo(p1, p2, p3, p4) \
          DosQPathInfo(p1, p2, p3, p4, 0)
#  define DosSetPathInfo(p1, p2, p3, p4, p5) \
          DosSetPathInfo(p1, p2, p3, p4, p5, 0)
#  define DosEnumAttribute(p1, p2, p3, p4, p5, p6, p7) \
          DosEnumAttribute(p1, p2, p3, p4, p5, p6, p7, 0)
#endif
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:

#ifdef __32BIT__

USHORT DosDevIOCtl32(PVOID pData, USHORT cbData, PVOID pParms, USHORT cbParms,
                     USHORT usFunction, USHORT usCategory, HFILE hDevice)
{
  ULONG ulParmLengthInOut = cbParms, ulDataLengthInOut = cbData;
  return (USHORT) DosDevIOCtl(hDevice, usCategory, usFunction,
                              pParms, cbParms, &ulParmLengthInOut,
                              pData, cbData, &ulDataLengthInOut);
}

#  define DosDevIOCtl DosDevIOCtl32
#else
#  define DosDevIOCtl DosDevIOCtl2
#endif
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:
long GetFileTime(char *name)
{
#ifdef __32BIT__
  FILESTATUS3 fs;
#else
  FILESTATUS fs;
#endif
  USHORT nDate, nTime;

  if ( DosQueryPathInfo(name, 1, (PBYTE) &fs, sizeof(fs)) )
    return -1;

  nDate = * (USHORT *) &fs.fdateLastWrite;
  nTime = * (USHORT *) &fs.ftimeLastWrite;

  return ((ULONG) nDate) << 16 | nTime;
}


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.


Click here to look at Greg's OS/2 page.
Click here to return to Greg's home page.
Click here to return to Greg's table of contents.
Last modified 12 August 2000 by newt@pobox.com , you betcha.