Understanding Symbol Visibility in CMake with GenerateExportHeader


What it Does

The GenerateExportHeader module provides a function called GENERATE_EXPORT_HEADER(). This function helps manage symbol visibility for your C or C++ libraries. It generates a header file containing macros that control how functions and classes are exported from your library and accessed by other projects linking against it.

How it Works

  • It takes several arguments to configure the generated header:
    • Library Target
      The name of your library target (e.g., MY_LIBRARY).
    • Output File
      The name of the generated header file (e.g., my_library_export.h).
    • Visibility Flags
      These flags control which symbols are exported and how (e.g., PUBLIC, PRIVATE).
  • You call GENERATE_EXPORT_HEADER() within your CMakeLists.txt file.

Benefits

  • Standardized Approach
    It provides a consistent way to manage symbol visibility across different platforms and compilers.
  • Controls Symbol Visibility
    You can control which functions and classes from your library are accessible to other projects. This promotes better encapsulation and avoids unintended symbol conflicts.
  • Customization Options
    You can customize the generated header further with options like:
    • INCLUDE_GUARD_NAME: Specify a custom include guard for the generated header.
    • CUSTOM_CONTENT_FROM_VARIABLE: Append additional content to the generated header from a variable.
    • Versioning Support: It can integrate with version headers for version-specific symbol deprecation.
  • Supported Libraries
    It works with both C and C++ libraries (since CMake 3.12).


Basic Example

add_library(my_library SHARED source1.cpp source2.cpp)
generate_export_header(my_library OUTPUT_FILE my_library_export.h)
install(TARGETS my_library DESTINATION lib)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/my_library_export.h DESTINATION include)

This example:

  1. Creates a shared library named my_library.
  2. Generates the export header my_library_export.h using GenerateExportHeader.
  3. Installs the library itself (my_library) to the lib directory.
  4. Installs the generated header (my_library_export.h) to the include directory, making it accessible to other projects.

Example with Customization

add_library(custom_lib SHARED source1.cpp source2.cpp)
generate_export_header(custom_lib
  OUTPUT_FILE custom_lib_export.h
  VISIBILITY_PRESET hidden
  INCLUDE_GUARD_NAME CUSTOM_LIB_EXPORT_H
  CUSTOM_CONTENT_FROM_VARIABLE CUSTOM_HEADER_CONTENT
)

This example shows some customization options:

  1. Sets the output file to custom_lib_export.h.
  2. Sets the visibility preset to hidden, marking most symbols as non-exported.
  3. Defines a custom include guard CUSTOM_LIB_EXPORT_H.
  4. Appends content from the variable CUSTOM_HEADER_CONTENT to the generated header.

Remember
You'll need to define the CUSTOM_HEADER_CONTENT variable with the desired content before calling GenerateExportHeader.

Advanced Example with Versioning

This example integrates GenerateExportHeader with versioning using external tools (not shown here for simplicity).

# Assuming some versioning mechanism sets VERSION_MAJOR and VERSION_MINOR

generate_export_header(my_versioned_lib
  OUTPUT_FILE my_versioned_lib_${VERSION_MAJOR}.${VERSION_MINOR}_export.h
  VERSION_LIBRARY ${VERSION_MAJOR}.${VERSION_MINOR}
)
  • Sets the library version using VERSION_LIBRARY for potential version-based symbol deprecation.
  • Generates a version-specific header file named my_versioned_lib_<major>.<minor>_export.h.


  1. Manual Header Creation
  • Refer to compiler-specific documentation for macros related to symbol visibility (e.g., __declspec(dllexport) in MSVC).
  • You can write the export header file yourself. This gives you complete control over the content but requires manual maintenance and can be error-prone.
  1. Compiler-Specific Features
  • Explore options like -fvisibility=hidden in GCC or /DEF: in MSVC Linker settings. However, this approach lacks portability across different compilers.
  • Some compilers offer built-in features for symbol visibility control. These might require compiler-specific syntax in your code or build flags.
  1. Third-Party Modules
  • It's recommended to stick with core CMake modules like GenerateExportHeader for better stability and community support.
  • While uncommon, there might be third-party CMake modules offering alternative functionalities for symbol management. However, these could be less well-maintained or require additional setup.

Why GenerateExportHeader is Preferred

  • Customization Options
    It offers various customization options for controlling the generated header content and behavior.
  • Integration with CMake
    It seamlessly integrates with other CMake features like targets and installation, making the build process smoother.
  • Standardized Approach
    It provides a consistent and well-documented way to manage symbol visibility across platforms and compilers.