This time, let’s take a look at how to create dynamic and static libraries using CMake. The technical details differ significantly between creating and using dynamic and static libraries, but for CMake, you only need to specify whether you need a static or dynamic library. This article contains a fair amount of code, mostly framework code, so let’s first look at the file structure (demo7):
Contents of the info.cpp file:
// File src/info.cpp
#include "info.h"
const char* GetInfo() {
return "C++ Fantasy";
}
Contents of the main.cpp file:
// File src/main.cpp
#include "info.h"
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << GetInfo() << std::endl;
return 0;
}
Contents of the CMakeLists.txt file:
# File src/CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(demo-7)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(INFO_SOURCES
info.cpp
# ... Of course, more source files can be included here if needed
)
# Define target library my_info -- no constraints are defined here for dynamic or static library
# If the value of BUILD_SHARED_LIBS variable is ON
# then, my_info is a dynamic library
# If the value of BUILD_SHARED_LIBS variable is OFF
# then, my_info is a static library
# BUILD_SHARED_LIBS is equivalent to a boolean type, default value is ON
add_library(my_info ${INFO_SOURCES})
# This line means that all source files compiling my_info define the macro MY_INFO_IMPL
# but all source files using the my_info library (i.e., the executable program test below) do not define the macro MY_INFO_IMPL
# Besides PRIVATE, there are also INTERFACE and PUBLIC used previously
# For a detailed discussion of this, refer to Appendix 1
target_compile_definitions(my_info PRIVATE MY_INFO_IMPL)
if (BUILD_SHARED_LIBS)
# Here we use PUBLIC
# All source files of the my_info library and those depending on it (e.g., the executable program test below) define the macro MY_INFO_SHARED_LIBS
target_compile_definitions(my_info PUBLIC MY_INFO_SHARED_LIBS)
endif()
set(TEST_SOURCES
main.cpp
# ... Of course, more source files can be included here if needed
)
add_executable(test ${TEST_SOURCES})
# This indicates that test links to the library my_info
target_link_libraries(test PUBLIC my_info)
Contents of the info.h file:
// File src/info.h
#ifndef __INFO_H___
#define __INFO_H___
#ifdef MY_INFO_SHARED_LIBS
// Dynamic library
#ifdef _WIN32
// Windows
// Actually, using the _WIN32 macro for judgment may have issues,
// A better method is to use CMake to determine and define the corresponding macro.
#ifdef MY_INFO_IMPL
#define MY_INFO_EXPORT __declspec(dllexport)
#else
#define MY_INFO_EXPORT __declspec(dllimport)
#endif // MY_INFO_IMPL
#else
// If not Windows, we simply assume it is Linux or MacOS
#if __has_attribute(visibility)
#define MY_INFO_EXPORT __attribute__((visibility("default")))
#else
#define MY_INFO_EXPORT
#endif
#endif // _WIN32
#else
// Static library
#define MY_INFO_EXPORT
#endif // MY_INFO_SHARED_LIBS
MY_INFO_EXPORT const char* GetInfo();
#endif // __INFO_H___
This header file is essentially the declaration of the function GetInfo, but it is quite complex; refer to Appendix 2.This project can still use the C++ project build from the second article, Introduction to CMake with two build scripts (rebuild.sh and rebuild.bat). However, to clearly explain how both dynamic and static libraries are compiled, the contents of four build scripts are shown below.Contents of the rebuild_shared.bat file:
:: File rebuild_shared.bat
:: Note: This file contains Chinese characters and needs to be encoded in GBK or a compatible encoding (Chinese Windows system)
:: Otherwise, the bat file will have issues
:: Another requirement for the bat file is that line endings must be CRLF
@echo off
:: Enter the directory where the script is located
CD /d %~dp0
:: Set two variables representing the build directory and build type
SET BUILD_DIR=build
SET BUILD_TYPE=Release
:: VC_VERSION variable indicates the version of VS used
::SET VC_VERSION=Visual Studio 15 2017
SET VC_VERSION=Visual Studio 17 2022
:: Windows programs are divided into 64-bit and 32-bit, represented by the following variable
SET ARCH_TYPE=x64
::SET ARCH_TYPE=win32
:: Clear the build directory
:: If previously compiled, this will recompile
IF EXIST "%BUILD_DIR%" (
RMDIR /s /q "%BUILD_DIR%"
)
:: Compared to the previous rebuild.bat script, the difference is this line which adds -DBUILD_SHARED_LIBS=ON
:: Of course, without this line, BUILD_SHARED_LIBS will use the default value, which is also ON
:: This means that using the previous script rebuild.bat will also compile a dynamic library
cmake -B "%BUILD_DIR%" -S src -G "%VC_VERSION%" -A %ARCH_TYPE% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DBUILD_SHARED_LIBS=ON
IF %ERRORLEVEL% neq 0 (
echo CMake project creation failed
goto end
)
cmake --build "%BUILD_DIR%" --config %BUILD_TYPE%
IF %ERRORLEVEL% neq 0 (
echo CMake compilation failed
goto end
)
Contents of the rebuild_shared.sh file:
# File rebuild_shared.sh
# Enter the directory where the script is located
cd $(dirname "$0")
# Set two variables representing the build directory and build type
BUILD_DIR=build
BUILD_TYPE=Release
if [ -d "$BUILD_DIR" ]; then
rm -rf "$BUILD_DIR"
fi
# Compared to the previous rebuild.sh script, the difference is this line which adds -DBUILD_SHARED_LIBS=ON
# Of course, without this line, BUILD_SHARED_LIBS will use the default value, which is also ON
# This means that using the previous script rebuild.sh will also compile a dynamic library
cmake -B "$BUILD_DIR" -S src -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_SHARED_LIBS=ON
if [ $? != 0 ]; then
echo "CMake project creation failed"
exit 0
fi
cmake --build "$BUILD_DIR" --config $BUILD_TYPE
if [ $? != 0 ]; then
echo "CMake compilation failed"
exit 0
fi
Contents of the rebuild_static.bat file:
:: File rebuild_static.bat
:: Note: This file contains Chinese characters and needs to be encoded in GBK or a compatible encoding (Chinese Windows system)
:: Otherwise, the bat file will have issues
:: Another requirement for the bat file is that line endings must be CRLF
@echo off
:: Enter the directory where the script is located
CD /d %~dp0
:: Set two variables representing the build directory and build type
SET BUILD_DIR=build
SET BUILD_TYPE=Release
:: VC_VERSION variable indicates the version of VS used
::SET VC_VERSION=Visual Studio 15 2017
SET VC_VERSION=Visual Studio 17 2022
:: Windows programs are divided into 64-bit and 32-bit, represented by the following variable
SET ARCH_TYPE=x64
::SET ARCH_TYPE=win32
:: Clear the build directory
:: If previously compiled, this will recompile
IF EXIST "%BUILD_DIR%" (
RMDIR /s /q "%BUILD_DIR%"
)
:: Compared to the previous rebuild.bat script, the difference is this line which adds -DBUILD_SHARED_LIBS=OFF
cmake -B "%BUILD_DIR%" -S src -G "%VC_VERSION%" -A %ARCH_TYPE% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DBUILD_SHARED_LIBS=OFF
IF %ERRORLEVEL% neq 0 (
echo CMake project creation failed
goto end
)
cmake --build "%BUILD_DIR%" --config %BUILD_TYPE%
IF %ERRORLEVEL% neq 0 (
echo CMake compilation failed
goto end
)
Contents of the rebuild_static.sh file:
# File rebuild_static.sh
# Enter the directory where the script is located
cd $(dirname "$0")
# Set two variables representing the build directory and build type
BUILD_DIR=build
BUILD_TYPE=Release
if [ -d "$BUILD_DIR" ]; then
rm -rf "$BUILD_DIR"
fi
# Compared to the previous rebuild.sh script, the difference is this line which adds -DBUILD_SHARED_LIBS=OFF
cmake -B "$BUILD_DIR" -S src -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_SHARED_LIBS=OFF
if [ $? != 0 ]; then
echo "CMake project creation failed"
exit 0
fi
cmake --build "$BUILD_DIR" --config $BUILD_TYPE
if [ $? != 0 ]; then
echo "CMake compilation failed"
exit 0
fi
If you compile on Windows, you may encounter compilation errors (refer to Appendix 3).Appendix:Appendix 1The valid values for the second parameter of the command target_compile_definitions in CMake are three:
- PUBLIC
- PRIVATE
- INTERFACE
This constrains a concept where the current compilation target is called the producer (i.e., the first parameter, such as my_info in demo7). The target using this producer is called the consumer (e.g., test in demo7).For PUBLIC, both the producer and consumer are effective, meaning both have this macro definition.For PRIVATE, only the producer is effective, meaning this macro is defined in the producer but not in the consumer.For INTERFACE, it is only effective in the consumer, meaning:The consumer defines this macro, but the producer does not.Commands that use this parameter besides target_compile_definitions include the following:
- target_compile_features
- target_compile_options
- target_include_directories
- target_link_libraries
- target_link_directories
- target_link_options
- target_precompile_headers
- target_sources
Appendix 2Regarding the info.h file, if it is a static library (or a dynamic library for Linux or MacOS), the content can be as follows:
#ifndef __INFO_H___
#define __INFO_H___
const char* GetInfo();
#endif // __INFO_H___
Of course, for dynamic libraries on Linux and MacOS, we have done a bit more in demo7 (this detail can be ignored).However, for dynamic libraries on Windows, for the producer (concept from Appendix 1), the content of info.h should be:
#ifndef __INFO_H___
#define __INFO_H___
__declspec(dllexport) const char* GetInfo();
#endif // __INFO_H___
For the consumer (concept from Appendix 1), the content of info.h should be:
#ifndef __INFO_H___
#define __INFO_H___
__declspec(dllimport) const char* GetInfo();
#endif // __INFO_H___
Appendix 3In the second article on building C++ projects, Introduction to CMake, I mentioned encoding issues. Besides bat files, all should use UTF-8 encoding with LF line endings.However, this may lead to compilation failures for demo7 on Windows. A simple modification method is to change the line endings of the info.h file to CRLF.However, this modification method is not recommended; the essence of this problem is an encoding issue. For more discussions on encoding issues, refer to the collection on encoding, but I don’t remember which article in this collection explained it. The modification method based on CMake is to change the content of the CMakeLists.txt file to:
cmake_minimum_required(VERSION 3.21)
project(demo-7)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Just added the following segment
if(MSVC)
# When compiling with MSVC, it defaults to using local encoding. But the code actually uses UTF-8
# To avoid this issue, force MSVC to use UTF-8 encoding
add_compile_options(/utf-8)
endif()
set(INFO_SOURCES
info.cpp
)
add_library(my_info ${INFO_SOURCES})
target_compile_definitions(my_info PRIVATE MY_INFO_IMPL)
if (BUILD_SHARED_LIBS)
target_compile_definitions(my_info PUBLIC MY_INFO_SHARED_LIBS)
endif()
set(TEST_SOURCES
main.cpp
)
add_executable(test ${TEST_SOURCES})
target_link_libraries(test PUBLIC my_info)
Note: The above uses add_compile_options instead of target_compile_options. This usage is convenient and aims for global effect; specifying each target explicitly would feel rather foolish.