views:

347

answers:

6

I'm looking for a pattern to organize header files for multiple platforms in C++.

I have a wrapper .h file that should compile under both Linux and Win32.

Is this the best I can do?

// defs.h

#if defined(WIN32)
#include <win32/defs.h>
#elif defined(LINUX)
#include <linux/defs.h>
#else
#error "Unable to determine OS or current OS is not supported!"
#endif

//  Common stuff below here...

I really don't like preprocessor stuff in C++. Is there a clean (and sane) way to do this better?

+2  A: 

Preprocessor isn't a bad thing in and of itself. It's a powerful tool, just like many other development tools. The question is how do you use it?

If you make your preprocessing clear, documented, and logical, it's not a problem at all. It's when you start doing "clever" tricks that are difficult to decipher that it becomes a problem.

Ideally, you would limit how often you do this. So you would perhaps have a defs.h that you include in all your files, and inside that one file you do your OS specific logic.

glowcoder
+5  A: 

One way to do this is put the OS specific headers into different directories and control which directory is found by passing that directory to the compiler via the -I flag.

In you case you just would have

#include "defs.h"
Mark
lack of #ifdef's ftw
tenpn
@tenpn - the point is that you don't need #ifdefs
Mark
That's what I mean. It is for the win that there is a lack of #ifdefs. If you're not careful I'll take your upvote away! :)
tenpn
I like this because it keeps everything nicely organized, and reduces possibility of errors (no #ifdef's). Even better, the file "defs.h" can be generated by autotools etc.
John
Personally I think thats a nasty method as it isn't instantly obvious what you are doing and that makes for unmaintainable code. Verbose on such things is ALWAYS preferrable to "tricks" in my opinion.
Goz
@Goz - in the case given it might seem explicit is simpler as you can see what is being done but as you get more compolex then you end up with code that has #ifdefs for every line of code and this is much easier to read if separated out - see http://www.chris-lott.org/resources/cstyle/ifdefs.pdf for #ifdef complexity
Mark
+16  A: 

You should use a configuration script able to perform platform checks and generate the appropriate compiler flags and/or configuration header files.

There are several tools able to perform this task, like autotools, Scons, or Cmake.

In your case, I would recommend using CMake, as it nicely integrates with Windows, being able to generate Visual Studio project files, as well as Mingw makefiles.

The main philosophy behind these tools is that you do not test again the OS itself, but against features that might or might not be present, or for which values can vary, reducing the risk that your code fails to compile with a "platform non supported" error.

Here is a commented CMake sample (CMakeFiles.txt):

# platform tests
include(CheckFunctionExists)
include(CheckIncludeFile)
include(CheckTypeSize)
include(TestBigEndian)

check_include_file(sys/types.h  HAVE_SYS_TYPES_H)
check_include_file(stdint.h     HAVE_STDINT_H)
check_include_file(stddef.h     HAVE_STDDEF_H)
check_include_file(inttypes.h   HAVE_INTTYPES_H)

check_type_size("double"      SIZEOF_DOUBLE)
check_type_size("float"       SIZEOF_FLOAT)
check_type_size("long double" SIZEOF_LONG_DOUBLE)

test_big_endian(IS_BIGENDIAN)
if(IS_BIGENDIAN)
  set(WORDS_BIGENDIAN 1)
endif(IS_BIGENDIAN)

# configuration file
configure_file(config-cmake.h.in ${CMAKE_BINARY_DIR}/config.h)
include_directories(${CMAKE_BINARY_DIR})
add_definitions(-DHAVE_CONFIG_H)

With that, you have to provide a config-cmake.h.in template that will be processed by cmake to generate a config.h file containing the definitions you need:

/* Define to 1 if you have the <sys/types.h> header file. */
#cmakedefine HAVE_SYS_TYPES_H
/* Define to 1 if you have the <stdint.h> header file. */
#cmakedefine HAVE_STDINT_H
/* Define to 1 if your processor stores words with the most significant byte
   first (like Motorola and SPARC, unlike Intel and VAX). */
#cmakedefine WORDS_BIGENDIAN
/* The size of `double', as computed by sizeof. */
#define SIZEOF_DOUBLE @SIZEOF_DOUBLE@
/* The size of `float', as computed by sizeof. */
#define SIZEOF_FLOAT @SIZEOF_FLOAT@
/* The size of `long double', as computed by sizeof. */
#define SIZEOF_LONG_DOUBLE @SIZEOF_LONG_DOUBLE@

I invite you to go to the cmake website to learn more about this tool.

I'm personally a fan of cmake, which I'm using for my personal projects.

SirDarius
+4  A: 

There is nothing inherently "bad" about using the preprocessor. It was created for a reason, and conditional compilation/inclusion like this is one of the things that does rather well.

The key is to keep your code readable. If you end up having a lot of #ifdef WIN32 blocks in your code, or if you find yourself doing this in a large number of files, here are a few tips to clean up your code a bit.

1) Move your conditional inclusion into a separate file. Your source files will simply #include <defs.h>, and move your conditional inclusion block into defs.h (even if that's all that is in that file).

2) Let your makefile determine which file to include. Again, your source files will simply #include <defs.h>. Your makefile will detect the current platform and add -iwin32 or -ilinux to the compiler options string as appropriate. This is the approach that I typically take. You can do the platform detection work inside the makefile or during the configuration stage (if you are dynamically-generating your makefiles). I also find this option easiest in terms of adding support for a new platform at a later date; it seems to minimize the number of changes that need to be made.

3) If you are building on a Linux system (implying that any Win32 code will be generated by a cross-compiler), you can have your build scripts create a symbolic link to the appropriate header file directory. Your code can reference the directory as #include <platform\defs.h> and let the makefile or configure script worry about symlinking the correct folder.

bta
+1  A: 

This really depends on the granularity of the actual differences between the implementations for the particular platforms (in your code this corresponds to "common stuff below here").

When the differences are considerable, I often prefer to factor out all platform-dependent code to several components (.cpp) sharing the same platform independent interface (.hpp).

For example, suppose we need to acquire images from a firewire camera within a cross-platform application program. Ways to achieve that on different platforms are hopelessly different. Then it makes sense to have a common interface which would be placed in CameraIeee1394.hpp, and to place the two platform dependent implementations into CameraIeee1394_unix.cpp and CameraIeee1394_w32.cpp.

The script which prepares the makefile for a particular platform can automatically disregard source files for foreign platforms by simply checking the filename suffix.

ssegvic
very good advice, although doesn't really address this particular question about types.
Ben Voigt
A: 

If you're going to use the preprocessor, this is the time to use it. There are different solutions but I believe that you can solve this in a clean way.

The way I usually solve it is I first create a header that does a platform and compiler detection, eg:

#define LIB_PLATFORM_WIN32 (1)
#define LIB_PLATFORM_LINUX (2)

#if defined(WIN32)
   #define LIB_PLATFORM LIB_PLATFORM_WIN32
#elif defined(LINUX)
   #define LIB_PLATFORM LIB_PLATFORM_LINUX
#endif

Now you've got your platform; you can then just use your own defines throughout your code.

For example, if you want a compiler independent 64bit integer you could use this

#if LIB_COMPILER == LIB_COMPILER_MSVC
    #define int64   __int64
    #define uint64  unsigned __int64
 #else
    #define int64   long long
    #define uint64  unsigned long long
#endif

If you keep it well structured you shouldnt have a problem keeping it clean and sane. :)

You could also use typedefs, but that might bring other problems if you want to overload a type later in your application.

Chaoz