1

So I've been working on porting a CMake C++ to building on Windows using Visual Studio.

I've learned that, unlike Linux/g++ which exports all symbols automatically, that MSVC does not export any symbols that it is not explicitly told to do so. After some googling, I discovered CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS and I tried that but I was still receiving linking errors (this time, my shared library wouldn't link). After some more reading, I found that CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS actually isn't always a good idea to use, and in fact, it would be better to only export symbols you specifically need, even on Linux. (As it can allow for more optimizations and possibly faster linking times).

I worked out a much simpler "minimal" example to test out some things, and I've concluded that adding the following to the top of my header files solves the problem (at least in my minimal working example):

#if defined(_WIN64)
#define EXPORT __declspec(dllexport)
#elif defined(__linux__)
#define EXPORT __attribute__((visibility("default"))) 
#endif

Where I would then modify a class declaration to be:

class EXPORT MyClass {...

Doing this allowed everything to build without issue! (NOTE: while this is a template, I am explicitly instantiating it so that it only works with specific types. Because of that, the implementation for this class is in the public_class.cpp file. I don't believe that is relevant here but I wrote it that way just to ensure this approach would work with my existing code, which has some instances of this sort of thing)

While this works, it seems extremely cumbersome to need to have this entire statement at the top of every file. Especially if, down the road, I come to realize that I need to modify something about it.

Is there a way I can streamline this statement and possibly have it in one single place in my entire project? Or is it best to have it in each individual file that contains symbols I wish to export?

starball
  • 20,030
  • 7
  • 43
  • 238
Chris Gnam
  • 311
  • 2
  • 7
  • Usually compilers have flags to control global visibility of symbols. Linux with GCC has `-fvisibility` flag. https://gcc.gnu.org/wiki/Visibility – kiner_shah Feb 13 '23 at 05:46
  • 1
    My question is, assuming I am setting the default visibility to hidden (using `set(CMAKE_CXX_VISIBILITY_PRESET hidden)`), I will need to then need to explicitly define the public API as being public. Which what I have written there does do (for both Windows and Linux). My question is if those sets of preprocessor statements need to go at the top of every single file, or if there is a way for me to put them in one single place in my project – Chris Gnam Feb 13 '23 at 05:50
  • 1
    Simple, make a header file, put it there and include that header file everywhere. – kiner_shah Feb 13 '23 at 05:51
  • Huh... for some reason I was thinking that wouldn't work with the header guards but I'm not actually sure why I thought that... maybe its time for bed – Chris Gnam Feb 13 '23 at 05:56
  • 2
    `_WIN64` should be changed to `_WIN32`, which works on both x32 and x64. – HolyBlackCat Feb 13 '23 at 05:59
  • If you're worried about the header being included more than once (e.g. if you put the macros in more than one distinct header, and both can be included) just modify to precede the `#define EXPORT` lines with lines `#ifdef EXPORT`, `#undef EXPORT`, `#endif` – Peter Feb 13 '23 at 06:01
  • Concerning DLL export, I once wrote an answer. FYI: [DLL Export / Import](https://stackoverflow.com/a/69387642/7478597) – Scheff's Cat Feb 13 '23 at 06:08

1 Answers1

1

CMake has a module to help you out with this: GenerateExportHeader.

Basically, you import the module, and call generate_export_header, passing the name of the target, and then any other optional customization arguments you want. It will create a header containing that boilerplate. Then you just target_include_directories the directory it generates to (by default, it's CMAKE_BINARY_DIR) for the target you generate the header for, and then include that header in the source code wherever you want to use it.

The boilerplate it defines will detect what toolchain you're using, and pick the right attribute syntax.


The only way you could go even "smaller" and avoid that #include line in each source file that needs it is to target_compile_options compiler-specific flags that cause another file to get included "implicitly", such as GCC's -include <file> option. Clang (as it does for many things) emulates GCC's flag. For MSVC, there is /FI. If you try to do this, you'll probably need to use generator expressions quite a bit- for one thing to differentiate between -include and /FI (Ex. see the $<CXX_COMPILER_ID:id> generator expression), and another to differentiate between what base paths to use for build vs. install (see the $<BUILD_INTERFACE>, $<INSTALL_INTERFACE>, and $<INSTALL_PREFIX> generator expressions).

starball
  • 20,030
  • 7
  • 43
  • 238