This recipe demonstrates how to set the visibility of symbols for a shared library. The best practice is to keep all symbols hidden by default, explicitly exposing only those symbols that we want to be used by dependents on our library. This is achieved in two steps. First of all, we need to instruct the compiler to hide symbols. Of course, different compilers will have different options available, and directly setting these by hand in our CMakeLists.txt would not be cross-platform. CMake offers a robust and cross-platform way of setting symbol visibility by setting two properties on the shared library target:
- CXX_VISIBILITY_PRESET hidden: This will hide all symbols, unless explicitly marked otherwise. When using the GNU compiler, this adds the flag -fvisibility=hidden for the target.
- VISIBILITY_INLINES_HIDDEN 1: This will hide symbols for inline functions. If using the GNU compiler, this corresponds to -fvisibility-inlines-hidden.
On Windows, this is the default behavior. Recall, in fact, that we needed to override it in the previous recipe by setting the WINDOWS_EXPORT_ALL_SYMBOLS property to ON.
How do we mark the symbols we want to be visible? This is determined by the preprocessor, and we thus need to provide preprocessor macros that expand to visibility attributes that the given compiler on the chosen platform will understand. Once again, CMake comes to the rescue with the GenerateExportHeader.cmake module file. This module defines the generate_export_header function, which we invoked as follows:
include(GenerateExportHeader)
generate_export_header(message-shared
BASE_NAME "message"
EXPORT_MACRO_NAME "message_EXPORT"
EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/messageExport.h"
DEPRECATED_MACRO_NAME "message_DEPRECATED"
NO_EXPORT_MACRO_NAME "message_NO_EXPORT"
STATIC_DEFINE "message_STATIC_DEFINE"
NO_DEPRECATED_MACRO_NAME "message_NO_DEPRECATED"
DEFINE_NO_DEPRECATED
)
The function generates the messageExport.h header file, which will contain the preprocessor macros needed. The file is generated in the directory ${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}, as requested via the EXPORT_FILE_NAME option. If this option is left empty, the header file would be generated in the current binary directory. The first argument to this function is an existing target, message-shared in our case. Basic invocation of the function only requires passing the name of an existing target. Optional arguments, for fine-grained control of all of the generated macros, can also be passed:
- BASE_NAME: This sets the base name of the generated header and macros to the passed value.
- EXPORT_MACRO_NAME: This sets the name of the export macro.
- EXPORT_FILE_NAME: This sets the name for the export header file generated.
- DEPRECATED_MACRO_NAME: This sets the name for the deprecation macro. This is used to mark deprecated code, the compiler will emit a deprecation warning if clients use it.
- NO_EXPORT_MACRO_NAME: This sets the name of the no-export macro.
- STATIC_DEFINE: This is for the name of the macro to use when also compiling a static library out of the same sources.
- NO_DEPRECATED_MACRO_NAME: This sets the name of the macro to be used to exclude deprecated code from being compiled.
- DEFINE_NO_DEPRECATED: This instructs CMake to generate preprocessor code to exclude deprecated code from compilation.
On GNU/Linux and using the GNU compiler, CMake will generate the following messageExport.h export header:
#ifndef message_EXPORT_H
#define message_EXPORT_H
#ifdef message_STATIC_DEFINE
# define message_EXPORT
# define message_NO_EXPORT
#else
# ifndef message_EXPORT
# ifdef message_shared_EXPORTS
/* We are building this library */
# define message_EXPORT __attribute__((visibility("default")))
# else
/* We are using this library */
# define message_EXPORT __attribute__((visibility("default")))
# endif
# endif
# ifndef message_NO_EXPORT
# define message_NO_EXPORT __attribute__((visibility("hidden")))
# endif
#endif
#ifndef message_DEPRECATED
# define message_DEPRECATED __attribute__ ((__deprecated__))
#endif
#ifndef message_DEPRECATED_EXPORT
# define message_DEPRECATED_EXPORT message_EXPORT message_DEPRECATED
#endif
#ifndef message_DEPRECATED_NO_EXPORT
# define message_DEPRECATED_NO_EXPORT message_NO_EXPORT message_DEPRECATED
#endif
#if 1 /* DEFINE_NO_DEPRECATED */
# ifndef message_NO_DEPRECATED
# define message_NO_DEPRECATED
# endif
#endif
#endif
We can prepend the classes and functions to be exposed to users with the message_EXPORT macro. Deprecation can be achieved by prepending with the message_DEPRECATED macro.
The static library is built out of the same sources. However, all symbols are supposed to be visible in the static archive, and as can be seen from the contents of the messageExport.h header file, the message_STATIC_DEFINE macro comes to the rescue. Once the target has been declared, we set it as a compile definition. The additional target properties on the static library are as follows:
- ARCHIVE_OUTPUT_NAME "message": This will ensure that the name of the library file is just message, rather than message-static.
- DEBUG_POSTFIX "_sd": This will append the given postfix to the library. This uniquely identifies the library as static in a Debug configuration.
- RELEASE_POSTFIX "_s": This is similar to the previous property, but just appends the postfix for a static library in case the target was built in Release configuration.