Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cross platform way to include system header files, when there is an identically named file in path?

I am trying to get Bloomberg's BDE library to compile in Visual Studio 2015. Because they re-implement the standard libraries typically provided by the compiler, there are header files that have names that exactly match the standard library names, such as stddef.h. They optionally allow you to turn off the overriding of the standard library, and to facilitate this, the files they re-implemented will optionally just include the original compiler supplied version such as stddef.h. They do this include through macros such as the following:

#   if defined(BSLS_COMPILERFEATURES_SUPPORT_INCLUDE_NEXT)
#     include_next <stddef.h>
#   else
#     include BSL_NATIVE_C_LIB_HEADER(stddef.h)
#   endif

Source

Where BSL_NATIVE_C_LIB_HEADER expands to something like this:

#if defined(BSLS_PLATFORM_CMP_SUN) // Sun Compiler
#   define BSL_NATIVE_C_LIB_HEADER(filename) <../include/filename>

#elif defined(BSLS_PLATFORM_CMP_CLANG) || defined(BSLS_PLATFORM_CMP_GNU)
  // Clang and GCC use 'include_next'

#elif defined(BSLS_PLATFORM_CMP_HP) // HP Compiler
#   define BSL_NATIVE_C_LIB_HEADER(filename) <../include_std/filename>

#else
  // Most other compilers
#   define BSL_NATIVE_C_LIB_HEADER(filename) <../include/filename>

#endif

Source

The issue is that Visual Studio 2015 introduces some refactoring that moves some of the C Standard Library header files to a path like this: C:\Program Files (x86)\Windows Kits\10\Include\10.0.10150.0\ucrt. This obviously means that <../include/filename> will no longer find the moved files. The issue is that all files have not moved. For example, iso646.h is still in C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include and will be picked up by the include.

So here's my question in a nutshell: Is there a way I can continue to support the BSL_NATIVE_C_LIB_HEADER macro being used, while behind the scenes figuring out whether the import should be from ../ucrt/ or ../include, based on the file name? I know I could create two separate macros, but I'd rather keep the interface the same if possible.

like image 486
GBleaney Avatar asked Nov 19 '15 02:11

GBleaney


1 Answers

Ideally, the BSL_NATIVE_C_LIB_HEADER()-like macros would continue to work, while behind the scenes there is a mechanism to override some them based on filename to point at a different directory. At first it seems impossible because the argument to the macros is a filename and may contain "." or "/". But with enough macro-foolery I think it can still be done without changing the build scripts or the filesystem.

BTW, I'm not sure what this says about the C preprocessor, but here goes...

The idea is to construct a series of macros that would be inserted into bsl_stdhdrs_incpaths.h in a MSVC 2015 specific block. There are several of these macros that do basically the same thing, so I have factored the solution so that each user macro like BSL_NATIVE_C_LIB_HEADER is implemented in terms of a common macro FINDER. The implementation macros have short names for readability, they should get some sort of prefix.

Caveat: these examples have only been tested on GCC 4.8.4 and unstable, clang-3.5, and MSVC 2015.

Simple Idea

The first clue is that even if the argument to BSL_NATIVE_C_LIB_HEADER(stddef.h) contains odd characters, it is not a single token. It is the list {'stddef', '.', 'h'}.

So we could #define stddef to ../include/stddef and it would work because the '.' and 'h' get appended after expansion! But better, we can use ## to paste the first token into macro with a unique name, say SELECTOR_stddef. This can then be #defined with a unique path based on each filename:

#define ANGLES(f) <f>
#define BSL_NATIVE_C_LIB_HEADER(file) ANGLES(SELECTOR_##file)

/* each can now have a different prefix */
#define SELECTOR_stddef ../ucrt/stddef
#define SELECTOR_stdarg ../include/stdarg

/* later on, down in the user code... */
#include BSL_NATIVE_C_LIB_HEADER(stddef.h) /* #include <../ucrt/stddef.h> */
#include BSL_NATIVE_C_LIB_HEADER(stdarg.h) /* #include <../include/stddef.h> */

So this is working great! The only problem is that every single file that is called must have a selector. If not, it will try to include some stupid filename that looks like #include <SELECTOR_stdio.h> and it won't work that well. Some maintenance programmer will probably come around and "fix" it by adding a bunch of symlinks to SELECTOR_stdio.h in the /usr/include directory and it will just end badly for everyone.

What would be really nice is if there could be a default for most files. Most appear to have been shuffled into .../ucrt and only compiler specific ones are left in .../include. So it would be nice to just override the ones we want. But even in this first form, it may work just fine. It has the advantage of simplicity.

A bunch of SO questions that helped with this:

  • Can macros be overloaded by number of arguments?
  • Overloading Macro on Number of Arguments
  • MSVC doesn't expand __VA_ARGS__ correctly
  • Optional Parameters with C++ Macros
  • C preprocessor macro specialisation based on an argument
  • Variadic macro trick
  • Is it possible to iterate over arguments in variadic macros?

A More Complete Solution

/* presumably within an #if MSVC 2015 conditional in bsl_stdhdrs_incpaths.h */

#define DELIMITER(a) a

/* same as DELIMITER, but named to distinguish the MSVC __VA_ARGS__ bug */
/* workaround is fine to leave in place for standard compilers */
#define MSVCFIXER(a) a

/* add the angle brackets and re-attach the "rest" tokens */
#define FORMATER(x1, x2, pre, rest, ...) <DELIMITER(pre)rest>

/* if __VA_ARGS__ only has one argument, shift so that pre is the default
 * otherwise if __VA_ARGS__ has two, pre is the override */
#define SHIFTER(pre, rest, def, ...) MSVCFIXER(FORMATER(__VA_ARGS__, pre, rest, def))

/* expand the commas */
#define EXPANDER(...) MSVCFIXER(SHIFTER(__VA_ARGS__))

/* main implementation - pass both the selector override and default */
#define FINDER(file, defloc) \
    EXPANDER(HEAD_LOC_OVERRIDE_##file, DELIMITER(defloc)file,,)

/* now implement the top level macros */
#define BSL_NATIVE_C_LIB_HEADER(file) FINDER(file, HEAD_LOC_DEFAULT_PREFIX)

#define BSL_NATIVE_SYS_TIME_HEADER(file) FINDER(file, HEAD_LOC_DEFAULT_PREFIX)

#define BSL_NATIVE_CISO646_HEADER(file) FINDER(file, /tmp/)

/* maybe define a common default prefix, or hard code it like iso646
 * since most files appear to be in ucrt, make this the default
      (file.h) will become <../ucrt/file.h> */

#define HEAD_LOC_DEFAULT_PREFIX ../ucrt/

/* override any other files NOTE: the commas
 *   (stdarg.h) will become <../include/stdarg.h>
 *   (stdint.h) will become <../include/stdint.h> */

#define HEAD_LOC_OVERRIDE_stdarg ../include/stdarg,
#define HEAD_LOC_OVERRIDE_stdint ../include/stdint,

/* and you can even override the name part too, or remove or add the .h
 *   (where.h) will become <../somewhere/when> (note: use two commas)
 *   (sys/*.h) will become <../include/sys/*.h>
 *   (cstdio)  will become <windows.h> */

#define HEAD_LOC_OVERRIDE_where  ../somewhere/when,,
#define HEAD_LOC_OVERRIDE_sys    ../include/sys,
#define HEAD_LOC_OVERRIDE_cstdio windows.h,

/* later on, down in the user code... */

#include BSL_NATIVE_C_LIB_HEADER(stdarg.h)   /* <../include/stdarg.h */
#include BSL_NATIVE_C_LIB_HEADER(stdio.h)    /* <../ucrt/stdio.h */
#include BSL_NATIVE_C_LIB_HEADER(cstdio)     /* <windows.h> */
#include BSL_NATIVE_C_LIB_HEADER(what.h)     /* <../ucrt/what.h> */
#include BSL_NATIVE_C_LIB_HEADER(where.h)    /* <../somewhere/when> */
#include BSL_NATIVE_CISO646_HEADER(iso646.h) /* </tmp/iso646.h> */
like image 122
Anders Avatar answered Nov 14 '22 23:11

Anders