multi-platform-target compilation

 

Overview

these days, it is important to target multiple platforms if you are making commercial code. not everybody can use a 32-bit program (besides, they run slower on windows because they get virtualized on a 64-bit OS). and if you are targeting the mac and linux, you need to

technique

using common compiler #defines lists some common #defines you can use for targeting multi-platform, such as _WIN64.

using this, among other #defines, would enable you to make 32+64+ARM+ARM64+DOS32 target code using 1 set of source code, for users of both platforms.

cprintf, printf also has %I64U (see inttypes.h, cinttypes) for 64-bit unsigned long long or uint64_t (from stdint.h, cstdint). those 2 libs should allow you to standardize your integer types. I have a types.h file I wrote for "cross-platform" compatibility of integer types before stdint.h and cstdint came out. I still need to convert that DOS code - it's bulky.

global search and replaces (sed scripts? or notepad++ find in files ctrl-f) would allow you to change your source code to handle the new standard integer types. but printf's and cprintf's would take some manual modifications. for instance, to print a 64-bit unsigned long long:

#include <cstdint> //c++ version of C99's stdint.h
#include <cinttypes> //c++ version of C99's inttypes.h
printf("%" PRIu64, n);

you should know that some things double in size when you go from 32-bit to 64-bit compilation:

  • TYPE SIZE CHANGES: size_t in std::string/, size_type in std::vector, std::list and similar collections, anything that looks like an index into a string or array or .size() in c++ is now 64-bits. so don't assume it's going to be 64 bits.
  • if you use a 32-bit array index, make sure it's a 32-bit integer for the 32-bit target and a 64-bit integer for the 64-bit target.
  • address and pointer sizes (because now you have a 64-bit flat address model instead of a 32-bit flat address model I think, even 32-bit DOS has a flat address model unlike the seg:ofs real mode model because of dos extenders)
  • FILE I/O: RE: stdio.h and fopen/fclose type ops - fsetpos, fgetpos (faster than WIN32 native calls by 6x-10x) now can address 64-bit file pointers and file sizes. FILE I/O: fsetpos and fgetpos could be useful for a hex editor that only writes changes (not saying anyone must implement, probably don't need to). just saying it's there if you need to set file positions or get them. gcc iostream currently has a broken .tellg() and .seekg() as of 2014.
  • DEPENDENCIES: the address and pointer sizes of course would need to increase to 64 bits for a 64-bit version and 32-bits for 32-bit version and SAL (there would need to be a 32-bit and/or a 64-bit dependency program/dll for your program installed with the program with the corresponding bittedness version of your main program) would need to reflect that.
  • there is a windows #define called _WIN64 which lets you detect if the compiler you are using is targeting a 64-bit CPU, that gets #defined for 64-bit cpu target, but undefined for a 32-bit cpu target.
  • sizeof() will of course report differently, especially if the above types are used in structs or unions.
  • you should use a config file that contains text data if you can help it. if you can manage XML, great. I hear TinyXML2 might be worth a look.
  • you can't use a 64-bit anything as an index in an array on an x32-target compiler. the reason is that addresses are 32-bit on an x32-target compiler and you can't use a bigger number, maybe that would mes with pointr arithmetic and just foul things up if the index actually got larger than 2^32-1-dataTypeSize. but you can use any size integer (signed or unsigned) 64-bits and under as an index into an array on an x64-target compiler, just like you can use any size integer and signed or unsigned on an x32-target compiler.

C/C++

/*you put your compiler or platform-specific code in the blank lines for C/C++:
cross-platform #ifdefs:*/
#if defined(__WATCOMC__)
/*typically 32-bits or 16-bit depending on compiler*/

#elif defined(__DJGPP__)
/*32-bits*/

#elif defined(__BORLANDC__)
    #if defined(_WIN64)
    /*64-bits*/

    #else
    /*32-bits*/

    #endif
#elif defined(_MSC_VER)
    #if defined(_WIN64)
    /*64-bits*/

    #else
    /*32-bits*/

    #endif
#endif

Example using arrays:

#include <vector>
#include <cstdint>
#include <initializer_list>
...
//define a universally-usable integer type we don't have to worry about what size it is, just works for the platform
#if defined(_WIN64)
/*this excludes OpenWatcom and DJGPP)*/
typedef int64_t INTTYPE;
#else
typedef int32_t INTTYPE;
#endif
typedef std::vector VI;
typedef std::vector::size_type VIT; //can change size with cpu target bittedness
VI viData = {1, 7, 2, 9, 20, 32, 40, 16};
//alloc intArray and copy viData to intArray
int* intArray = new int[viData.size()];
for (VIT i=0; i < viData.size(); i++) {
    intArray[i]=viData[i];
}


/*you put your compiler or platform-specific code in the blank lines for C/C++:
cross-platform #ifdefs:*/
#if defined(__WATCOMC__)
/*typically 32-bits or 16-bit depending on compiler*/
printf("%" PRIu32, intArray[0]);
#elif defined(__DJGPP__)
/*32-bits*/
printf("%" PRIu32, intArray[0]);
#elif defined(__BORLANDC__)
    #if defined(_WIN64)
    /*64-bits*/
    printf("%" PRIu64, intArray[0]);
    #else
    /*32-bits*/
    printf("%" PRIu32, intArray[0]);
    #endif
#elif defined(_MSC_VER)
    #if defined(_WIN64)
    /*64-bits*/
    printf("%" PRIu64, intArray[0]);
    #else
    /*32-bits*/
    printf("%" PRIu32, intArray[0]);
    #endif
#elif defined(__MINGW32__)
    #if defined(_WIN64)
    /*64-bits*/
    printf("%" PRIu64, intArray[0]);
    #else
    /*32-bits*/
    printf("%" PRIu32, intArray[0]);
    #endif
#endif

delete intArray[]; //free the heap memory we used