Over the last few years, I’ve been writing a lot of C++ that I’ve targeted for multiple platforms and/or multiple compilers. It has always been somewhat of a delicate task to make C++ code portable, especially when starting with Visual Studio and targeting other platforms like Linux using GCC.
Along with multi-target compilations, C++ pre-processor macros can be a useful tool in reducing the amount of mundane, rather simple, and rudimentary code that many C++ programmers find themselves writing. Here’s a few macros that I’ve used to ease my own development pain, I hope someone else can find them as handy as I have.
Please, by all means if I am incorrect somewhere please correct me. Also, if you wish to add to this list, feel free to do so.
Cross-Compilation Helpers
First up, one of the more common tasks, especially when writing libraries versus executable programs, is exporting symbols. The process to export symbols is different for the various compilers. The following should take care of GCC and MSVC:
#if defined(_MSC_VER)
# define LIB_EXPORT __declspec(dllexport)
# define LIB_IMPORT __declspec(dllimport)
#elif defined(__GNUC__)
# define LIB_EXPORT
# define LIB_IMPORT extern
#endif
#
#if defined(__cplusplus)
# define EXTERN_C extern "C"
#else
# define EXTERN_C
#endif
Use the EXTERN_C macro when name-mangling on exported functions must be avoided.
There are many ways to tackle symbol exports. In fact if I’m not mistaken, GCC now has synonyms for __declspec(dllexport/dllimport) so there may not even be a need to use the pre-processor to first establish which compiler is in use for the import/export macros mentioned above. It all depends on which version of GCC you’re using.
Calling Convention Helpers
Most, if not all compilers assume a calling convention when a function is not decorated with one. When problems arise with mis-matched calling conventions (almost always with exported functions) then use the following macros to specify a calling convention rather than leaving it up to the compiler.
#if defined(_MSC_VER)
# define STDCALL __stdcall
# define CDECL __cdecl
# define FASTCALL __fastcall
#elif defined(__GNUC__)
# define STDCALL __attribute__((stdcall))
# define CDECL
# define FASTCALL __attribute__((fastcall))
#endif
Function Inlining
Compilers will often attempt to make functions inline where it makes sense to do so (depending on compiler flags, etc.). If you wish to force the compiler to make a function inline, use the following macro:
#if defined(_MSC_VER)
# define FORCEINLINE __forceinline
#elif defined(__GNUC__)
# define FORCEINLINE inline
#endif
Struct Member Alignment
Another possible area of compatibility mismatch is with member alignment. To force the compiler to use a specific member alignment byte-boundary, use the following macro:
#if defined(_MSC_VER)
# define DECL_ALIGN(x) __declspec(align(x))
#elif defined(__GNUC__)
# define DECL_ALIGN(x) __attribute((aligned(x)))
#endif
Utility Macros
I’ll end this post with some utility macros that I find to be pretty helpful…
Inline-Square
#if !defined(sqr)
# define sqr(x) ((x)*(x))
#endif
Min/Max
#if defined(min)
# undef min
# define min(a, b) (((a)<(b))?(a):(b))
#endif
#if defined(max)
# undef max
# define max(a,b) (((a)>(b))?(a):(b))
#endif
Class Declarations (my personal favorite)
#define DECLARE_CLASS_NOBASE(className) typedef className ThisClass
#define DECLARE_CLASS(className, baseClass) typedef baseClass BaseClass;\
DECLARE_CLASS_NOBASE(className)
You can get rather creative with the above macro. I use it for a redumentary form of reflection or for logging purposes by converting the class name to a string that can be evaluated at run-time.
Example:
#define DECLARE_CLASS_NOBASE(className) typedef className ThisClass;\
virtual const char * ThisClassName() const { return #className; }
#define DECLARE_CLASS(className, baseClass) typedef baseClass BaseClass;\
virtual const char * BaseClassName() const { return #baseClass; }\
DECLARE_CLASS_NOBASE(className)