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).
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.)
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) #endifNote 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 #endifFinally, 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; }