

Introduction:
In fact, the relevant content of this article has been detailed in the CMake: Detailed Explanation of Static and Dynamic Libraries (Linux/Windows) notes, where it shows that CMake
provides a platform-independent way to achieve its functionality. However, the issue of symbol visibility has not been addressed. The best approach regarding symbol visibility is to specify that dynamic libraries only expose the minimal set of symbols, thereby limiting the visibility of objects and functions defined in the code.
We hope that, by default, all symbols defined in dynamic libraries are hidden from external visibility. This will allow contributors to the project to clearly delineate the interface between the library and external code, as they must explicitly mark all symbols to be used outside the project.

✦
Project Structure
✦
├── CMakeLists.txt
├── src
│ ├── CMakeLists.txt
│ ├── hello_world.cpp
│ ├── message.cpp
│ └── message.hpp
└── test
└── CMakeLists.txt
Project Address:
https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter9/02
✦
Related Source Code
✦
CMakeLists.txt
# CMake 3.6 needed for IMPORTED_TARGET option
# to pkg_search_module
cmake_minimum_required(VERSION 3.6 FATAL_ERROR)
project(
example
LANGUAGES CXX
VERSION 1.0.0
)
# <<< General set up >>>
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Check if the installation prefix has been set
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
# Set the installation directory to the output folder under the project source directory
set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/output/" CACHE PATH "..." FORCE)
endif()
message(STATUS "Project will be installed to ${CMAKE_INSTALL_PREFIX}")
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
message(STATUS "Build type set to ${CMAKE_BUILD_TYPE}")
include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
# Offer the user the choice of overriding the installation directories
set(INSTALL_LIBDIR ${CMAKE_INSTALL_LIBDIR} CACHE PATH "Installation directory for libraries")
set(INSTALL_BINDIR ${CMAKE_INSTALL_BINDIR} CACHE PATH "Installation directory for executables")
set(INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE PATH "Installation directory for header files")
if(WIN32 AND NOT CYGWIN)
set(DEF_INSTALL_CMAKEDIR CMake)
else()
set(DEF_INSTALL_CMAKEDIR share/cmake/${PROJECT_NAME})
endif()
set(INSTALL_CMAKEDIR ${DEF_INSTALL_CMAKEDIR} CACHE PATH "Installation directory for CMake files")
# Report to user
foreach(p LIB BIN INCLUDE CMAKE)
file(TO_NATIVE_PATH ${CMAKE_INSTALL_PREFIX}/${INSTALL_${p}DIR} _path )
message(STATUS "Installing ${p} components to ${_path}")
unset(_path)
endforeach()
add_subdirectory(src)
enable_testing()
add_subdirectory(test)
src/CMakeLists.txt
# Search for pkg-config and UUID
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_search_module(UUID uuid IMPORTED_TARGET)
if(TARGET PkgConfig::UUID)
message(STATUS "Found libuuid")
set(UUID_FOUND TRUE)
endif()
endif()
# <<< Build targets >>>
# SHARED library
add_library(message-shared SHARED "")
target_sources(message-shared
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/message.cpp
)
target_compile_definitions(message-shared
PUBLIC
$<$<BOOL:${UUID_FOUND}>:HAVE_UUID>
)
target_link_libraries(message-shared
PUBLIC
$<$<BOOL:${UUID_FOUND}>:PkgConfig::UUID>
)
set_target_properties(message-shared
PROPERTIES
POSITION_INDEPENDENT_CODE 1
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN 1
SOVERSION ${PROJECT_VERSION_MAJOR}
OUTPUT_NAME "message"
DEBUG_POSTFIX "_d"
PUBLIC_HEADER "message.hpp;${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/message_export.h"
MACOSX_RPATH ON
)
include(GenerateExportHeader)
generate_export_header(message-shared
BASE_NAME "message"
EXPORT_MACRO_NAME "MESSAGE_LIB_API"
EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/message_export.h"
STATIC_DEFINE "MESSAGE_STATIC_DEFINE"
DEFINE_NO_DEPRECATED
)
target_include_directories(message-shared
PUBLIC
${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}
)
# STATIC library
add_library(message-static STATIC "")
target_sources(message-static
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/message.cpp
)
target_compile_definitions(message-static
PUBLIC
MESSAGE_STATIC_DEFINE
$<$<BOOL:${UUID_FOUND}>:HAVE_UUID>
)
target_include_directories(message-static
PUBLIC
${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}
)
target_link_libraries(message-static
PUBLIC
$<$<BOOL:${UUID_FOUND}>:PkgConfig::UUID>
)
set_target_properties(message-static
PROPERTIES
POSITION_INDEPENDENT_CODE 1
ARCHIVE_OUTPUT_NAME "message"
DEBUG_POSTFIX "_sd"
RELEASE_POSTFIX "_s"
PUBLIC_HEADER "message.hpp;${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/message_export.h"
)
# EXECUTABLES
add_executable(hello-world_wDSO hello_world.cpp)
target_link_libraries(hello-world_wDSO
PUBLIC
message-shared
)
# Prepare RPATH
file(RELATIVE_PATH _rel ${CMAKE_INSTALL_PREFIX}/${INSTALL_BINDIR} ${CMAKE_INSTALL_PREFIX})
if(APPLE)
set(_rpath "@loader_path/${_rel}")
else()
set(_rpath "$ORIGIN/${_rel}")
endif()
file(TO_NATIVE_PATH "${_rpath}/${INSTALL_LIBDIR}" message_RPATH)
set_target_properties(hello-world_wDSO
PROPERTIES
MACOSX_RPATH ON
SKIP_BUILD_RPATH OFF
BUILD_WITH_INSTALL_RPATH OFF
INSTALL_RPATH "${message_RPATH}"
INSTALL_RPATH_USE_LINK_PATH ON
)
add_executable(hello-world_wAR hello_world.cpp)
target_link_libraries(hello-world_wAR
PUBLIC
message-static
)
# <<< Install and export targets >>>
install(
TARGETS
message-shared
message-static
hello-world_wDSO
hello-world_wAR
ARCHIVE
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
RUNTIME
DESTINATION ${INSTALL_BINDIR}
COMPONENT bin
LIBRARY
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
PUBLIC_HEADER
DESTINATION ${INSTALL_INCLUDEDIR}/message
COMPONENT dev
)
set_target_properties(message-shared
PROPERTIES
POSITION_INDEPENDENT_CODE 1
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN 1
SOVERSION ${PROJECT_VERSION_MAJOR}
OUTPUT_NAME "message"
DEBUG_POSTFIX "_d"
PUBLIC_HEADER "message.hpp;${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/message_export.h"
MACOSX_RPATH ON
)
-
CXX_VISIBILITY_PRESET hidden: Sets the default symbol visibility. Here, it is set to hidden, which means that unless explicitly specified, all symbols will not be exported. This helps to reduce the size of the final binary, improve loading speed, and provide a degree of encapsulation.
-
VISIBILITY_INLINES_HIDDEN 1: When using hidden visibility, this option also hides the symbols of inline functions. This further reduces the number of exported symbols.
-
OUTPUT_NAME “message”: This specifies the output name of the target. In this case, regardless of the actual name of the target (here it is
message-shared
), the generated file will be namedmessage
. -
PUBLIC_HEADER “message.hpp;${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/message_export.h”: Specifies the public header files. These files will be treated specially when installing the target, usually copied to the include directories.
-
MACOSX_RPATH ON: This is a macOS-specific option that enables the use of relative paths (rpath). RPath is a mechanism for runtime searching of shared libraries, which is important for ensuring that applications can find their dependent shared libraries.
generate_export_header(message-shared
BASE_NAME "message"
EXPORT_MACRO_NAME "MESSAGE_LIB_API"
EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/message_export.h"
STATIC_DEFINE "MESSAGE_STATIC_DEFINE"
DEFINE_NO_DEPRECATED
)
-
BASE_NAME “message”: This specifies the base name of the generated macro. The generated macro will be based on the name “message”, e.g.,
MESSAGE_EXPORT
. -
EXPORT_MACRO_NAME “MESSAGE_LIB_API”: This defines the macro name used for exporting symbols. You can use
MESSAGE_LIB_API
to mark classes, functions, or variables that need to be exported. -
EXPORT_FILE_NAME “${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/message_export.h”: Specifies the name and location of the generated header file. This header file will contain the necessary preprocessor directives to change the behavior of the macro based on whether the library itself is being compiled or used.
-
STATIC_DEFINE “MESSAGE_STATIC_DEFINE”: This macro will be defined when the library is statically linked. This is useful for seamlessly switching between static and shared linking.
-
DEFINE_NO_DEPRECATED: This will define a macro to disable warnings for deprecated functions in the library. This is very useful for maintaining older codebases as it allows developers to compile old code without triggering deprecation warnings (of course, this macro can be omitted).
src/message.hpp
#pragma once
#include <iosfwd>
#include <string>
#include "message_export.h"
class MESSAGE_LIB_API Message {
public:
Message(const std::string &m) : message_(m) {}
friend std::ostream &operator<<(std::ostream &os, Message &obj) {
return obj.PrintObject(os);
}
private:
std::string message_;
std::ostream &PrintObject(std::ostream &os);
};
std::string GetUUID();
src/message.cpp
src.hello_world.cpp
#include "message.hpp"
#include <iostream>
#include <string>
#ifdef HAVE_UUID
#include <uuid/uuid.h>
#endif
std::ostream &Message::PrintObject(std::ostream &os) {
os << "This is my very nice message: " << std::endl;
os << message_ << std::endl;
os << "...and here is its UUID: " << GetUUID();
return os;
}
#ifdef HAVE_UUID
std::string GetUUID() {
uuid_t uuid;
uuid_generate(uuid);
char uuid_str[37];
uuid_unparse_lower(uuid, uuid_str);
uuid_clear(uuid);
std::string uuid_cxx(uuid_str);
return uuid_cxx;
}
#else
std::string GetUUID() { return "Ooooops, no UUID for you!"; }
#endif
test/CMakeLists.txt
add_test(
NAME test_shared
COMMAND $<TARGET_FILE:hello-world_wDSO>
)
add_test(
NAME test_static
COMMAND $<TARGET_FILE:hello-world_wAR>
)
✦
Results Display
✦
& mkdir build
& cd build
& cmake ..
& cmake --build . --target install
Scanning dependencies of target message-static
[ 12%] Building CXX object src/CMakeFiles/message-static.dir/message.cpp.o
[ 25%] Linking CXX static library ../lib/libmessage_s.a
[ 25%] Built target message-static
Scanning dependencies of target hello-world_wAR
[ 37%] Building CXX object src/CMakeFiles/hello-world_wAR.dir/hello_world.cpp.o
[ 50%] Linking CXX executable ../bin/hello-world_wAR
[ 50%] Built target hello-world_wAR
Scanning dependencies of target message-shared
[ 62%] Building CXX object src/CMakeFiles/message-shared.dir/message.cpp.o
[ 75%] Linking CXX shared library ../lib/libmessage.so
[ 75%] Built target message-shared
Scanning dependencies of target hello-world_wDSO
[ 87%] Building CXX object src/CMakeFiles/hello-world_wDSO.dir/hello_world.cpp.o
[100%] Linking CXX executable ../bin/hello-world_wDSO
[100%] Built target hello-world_wDSO
Install the project...
-- Install configuration: "Release"
-- Installing: /home/jiangli/repo/tutorials/cmake-tutorial/chapter9/02/output/lib/libmessage.so.1
-- Up-to-date: /home/jiangli/repo/tutorials/cmake-tutorial/chapter9/02/output/lib/libmessage.so
-- Up-to-date: /home/jiangli/repo/tutorials/cmake-tutorial/chapter9/02/output/include/message/message.hpp
-- Installing: /home/jiangli/repo/tutorials/cmake-tutorial/chapter9/02/output/include/message/message_export.h
-- Installing: /home/jiangli/repo/tutorials/cmake-tutorial/chapter9/02/output/lib/libmessage_s.a
-- Up-to-date: /home/jiangli/repo/tutorials/cmake-tutorial/chapter9/02/output/include/message/message.hpp
-- Up-to-date: /home/jiangli/repo/tutorials/cmake-tutorial/chapter9/02/output/include/message/message_export.h
-- Installing: /home/jiangli/repo/tutorials/cmake-tutorial/chapter9/02/output/bin/hello-world_wDSO
-- Set runtime path of "/home/jiangli/repo/tutorials/cmake-tutorial/chapter9/02/output/bin/hello-world_wDSO" to "$ORIGIN/../lib"
-- Installing: /home/jiangli/repo/tutorials/cmake-tutorial/chapter9/02/output/bin/hello-world_wAR
Output:Finally, I wish everyone to become stronger!!! If this note has helped you, please like and share!!!🌹🌹🌹

