Chris Gnam
Chris Gnam

Reputation: 590

Is there a way to avoid redefining boilerplate macros for shared library export attributes in every source file when using CMake?

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?

Upvotes: 1

Views: 377

Answers (1)

starball
starball

Reputation: 50013

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).

Upvotes: 1

Related Questions