1

So I have a project (meant to be supported on MacOS, Linux, and Windows) where I am building a shared library and a set of executables linked to that library. In my root CMakeLists.txt I have added:

set(CMAKE_CXX_VISIBILITY_PRESET hidden)

So that all symbols are hidden by default regardless of on Windows (which does this by default) or Linux. This will require me to manually export symbols of the public API. To help facilitate this, I've written a defines.hpp header in my project's utils/ directory which contains the following:

#ifndef __DEFINES_H_
#define __DEFINES_H_

#if defined(linux) || defined(__linux__)
    #define EXPORT __attribute__((visibility("default"))) 
#elif defined(_WIN64) || defined(__WIN32__)
    #if defined(COMPILE_LIB)
        #define EXPORT __declspec(dllexport)
    #elif
        #define EXPORT __declspec(dllimport)
    #endif
#endif

#endif

The idea being then I would include this header into any header that contains declarations that need to be exported (and subsequently imported as well) via:

#include "utils/defines.hpp"

class EXPORT MyCoolClass();

EXPORT void MyCoolFunction();

(I have not tested this yet so if there are any glaring issues with what I've written please let me know!)

My question is, it is not entirely clear to me how to define the COMPILE_LIB macro, given that the library and exectuables are built/linked with the same CMake setup. How can I ensure that the library is built using dllexport and then linked to the executables using dllimport within the same CMake build? Or would I need to take a different approach?

The accepted answer in this previous SO post seems to indicate using a compiler argument and building each separately, which isn't exactly what I am looking for. A separate answer indicates you should use the CMake defined projectname as when compiling the DLL it will define the macro projectname_EXPORTS. I'm also not sure that this will work again my shared library and the executables are all built within the same CMake project. (Relevant CMakeLists.txt files):

CMakeLists.txt for executables:

add_executable(quick-render quick_render.cpp)
target_link_libraries(quick-render ${PROJECT_NAME})
INSTALL(TARGETS quick-render DESTINATION bin)
...

CMakeLists.txt for library:

...

add_library(${PROJECT_NAME} SHARED
   ...
)

target_link_libraries(${PROJECT_NAME} PUBLIC
   ...
)
...

Would this approach still work here, or is it my CMake setup bad for using the project in multiple ways like this?

starball
  • 20,030
  • 7
  • 43
  • 238
Chris Gnam
  • 311
  • 2
  • 7
  • 1
    Define the macro by using [target_compile_definitions](https://cmake.org/cmake/help/latest/command/target_compile_definitions.html) command with PRIVATE keyword. That way, the macro will be defined only when the library itself is compiled. – Tsyvarev Feb 13 '23 at 22:49

1 Answers1

2

As Tsyvarev said in the comments,

Define the macro by using target_compile_definitions command with PRIVATE keyword. That way, the macro will be defined only when the library itself is compiled.

Adding to what Tsyvarev said, if you just use the GenerateExportHeader module, that part will be done for you automatically. It will also take care of generating C++ attributes for the compiler you configured the buildsystem to use.

starball
  • 20,030
  • 7
  • 43
  • 238
  • To make sure I understand what you've said (and what I've read glancing over the documentation) the use of `generate_export_header(my_lib)` will replace needing to deal with these preprocessor statements/macros? CMake will handle it all for me and I will not need to explicitly modify my underlying C++ code? – Chris Gnam Feb 13 '23 at 23:19
  • The only thing you will need to modify is what the name of the header you import is (that contains these macro definitions) and the name of the macro definition (probably). Or you can just use the customization parameters of `generate_export_header` to fit what names you want. Funnily, GenerateExportHeader was what made me want to actually try using CMake for the first time in a project (when that project was still small enough that not using any buildsystem was still tenable) – starball Feb 13 '23 at 23:20
  • I am admittedly confused as to how to actually make use of this generated header file. Its platform specific and so I can't track it on git. But it also needs to be present as the files need to be able to include it... Is the idea that the CMake configuration (prior to building) generates the file, and then you simply include the path its generated to with `include_directories()`? It defaults to the build directory which changes on Linux vs Windows, so if what I just described is what you'd need to do, I think I'd specify to generate it somewhere else and add it to the `.gitignore` – Chris Gnam Feb 14 '23 at 02:11
  • @ChrisGnam Yep. It's generated during buildsystem configuration and if your build directory already isn't tracked / gitignored, you don't need to do anything special to your gitignore to make the generated file not get tracked. It's actually a smart thing to put generated files in the build directory tree. if they're generated from tracked files, there's no point tracking them, and as you said, tracking them is bad because what's generated may differ by platform and configuration. – starball Feb 14 '23 at 02:38