从对象库和 DLL 导出的 CMake 共享库
CMake shared library from object library & DLL exports
背景
我使用 cmake 构建了一个 open-source library。
项目设置为执行以下操作:
- 构建一个名为
gpds-objs
的 cmake OBJECT
库
- 从
gpds-objs
构建一个名为 gpds-static
的 STATIC
库
- 从
gpds-objs
构建一个名为 gpds-shared
的 SHARED
库
此外,我正在使用 cmake 的 generate_export_header()
生成必要的导出宏。
cmake 脚本的相关部分如下所示:
# Set project information
project(gpds
VERSION 1.0.0
LANGUAGES CXX
HOMEPAGE_URL "https://gpds.simulton.com"
)
# Some bacis cmake configuration
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)
# List of private source files
set(SOURCES_PRIVATE
# ...
)
# List of private header files
set(HEADERS_PRIVATE
# ...
)
# List of public header files
set(HEADERS_PUBLIC
# ...
)
# Define targets
set(NAME gpds)
set(TARGET-OBJS ${NAME}-objs)
set(TARGET-STATIC ${NAME}-static)
set(TARGET-SHARED ${NAME}-shared)
################################################################################
# Object library #
################################################################################
add_library(${TARGET-OBJS} OBJECT)
target_compile_features(
gpds-objs
PUBLIC
cxx_std_17
)
target_sources(
${TARGET-OBJS}
PRIVATE
${SOURCES_PRIVATE}
${HEADERS_PRIVATE}
)
target_include_directories(
${TARGET-OBJS}
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>
PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include/gpds>
)
################################################################################
# Shared library #
################################################################################
add_library(${TARGET-SHARED} SHARED)
target_link_libraries(
${TARGET-SHARED}
PUBLIC
gpds-objs
)
target_compile_definitions(
${TARGET-SHARED}
PRIVATE
gpds_shared_EXPORTS # We're building this library!
)
################################################################################
# Static library #
################################################################################
add_library(${TARGET-STATIC} STATIC)
target_link_libraries(
${TARGET-STATIC}
PUBLIC
gpds-objs
)
target_compile_definitions(
${TARGET-STATIC}
PUBLIC
GPDS_STATIC_DEFINE
)
# Common library properties
set_target_properties(
${TARGET-OBJS}
${TARGET-STATIC}
${TARGET-SHARED}
PROPERTIES
OUTPUT_NAME "gpds"
ARCHIVE_OUTPUT_NAME "gpds"
VERSION ${PROJECT_VERSION}
POSITION_INDEPENDENT_CODE 1
)
################################################################################
# Export header #
################################################################################
include(GenerateExportHeader)
generate_export_header(
${TARGET-SHARED}
BASE_NAME gpds
EXPORT_FILE_NAME gpds_export.hpp
DEPRECATED_MACRO_NAME "GPDS_DEPRECATED"
NO_DEPRECATED_MACRO_NAME "GPDS_NO_DEPRECATED"
EXPORT_MACRO_NAME "GPDS_EXPORT"
NO_EXPORT_MACRO_NAME "GPDS_NO_EXPORT"
STATIC_DEFINE "GPDS_STATIC_DEFINE"
DEFINE_NO_DEPRECATED
)
库中的 class 定义如下所示:
#include "gpds_export.hpp"
namespace gpds
{
class GPDS_EXPORT container
{
// ...
};
}
为了完整起见,这里是 gpds_export.hpp
的相关部分,由 cmake 的 generate_export_header()
:
生成
#ifdef GPDS_STATIC_DEFINE
# define GPDS_EXPORT
# define GPDS_NO_EXPORT
#else
# ifndef GPDS_EXPORT
# ifdef gpds_shared_EXPORTS
/* We are building this library */
# define GPDS_EXPORT __declspec(dllexport)
# else
/* We are using this library */
# define GPDS_EXPORT __declspec(dllimport)
# endif
# endif
# ifndef GPDS_NO_EXPORT
# define GPDS_NO_EXPORT
# endif
#endif
问题
我 运行 遇到的问题是在构建 gpds-objs
时,我收到以下消息:
C:\Users\joel\Documents\projects\gpds\lib\src\value.cpp:7:1: warning: 'gpds::value::value(const gpds::value&)' redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]
7 | value::value(const value& other) :
| ^~~~~
C:\Users\joel\Documents\projects\gpds\lib\src\value.cpp:17:1: warning: 'gpds::value::value(gpds::value&&)' redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]
17 | value::value(value&& other) :
| ^~~~~
C:\Users\joel\Documents\projects\gpds\lib\src\value.cpp:25:1: warning: 'virtual gpds::value::~value()' redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]
25 | value::~value() noexcept
| ^~~~~
C:\Users\joel\Documents\projects\gpds\lib\src\value.cpp:33:6: warning: 'void gpds::value::from_string(std::string&&)' redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]
33 | void value::from_string(std::string&& string)
我不确定如何正确解决这个问题。
据我了解,问题是构建 gpds-objs
目标没有定义任何相关的导出宏(GPDS_STATIC_DEFINE
和 gpds_shared_EXPORTS
都没有)。因此,GPDS_EXPORT
被定义为 __declspec(dllimport)
,这是不正确的,因为我们没有使用 gpds-objs
目标构建共享库。
我能想到的一个解决方案是在 gpds-objs
目标而不是 gpds-shared
目标上定义 gpds_shared_EXPORTS
。但是,这意味着它也在构建 gpds-static
目标时定义。只要 gpds-static
定义 GPDS_STATIC_DEFINE
.
就可以了
正确的处理方法是什么?
您不能对共享库和静态库使用相同的OBJECT库。
OBJECT 库决定了源文件的编译方式。
但是对于静态库和共享库,源文件应该以不同的方式编译。
Documentation for GenerateExportHeader 描述了生成的同一个 header 文件如何用于静态库和共享库:
add_library(shared_variant SHARED ${lib_SRCS})
add_library(static_variant ${lib_SRCS})
generate_export_header(shared_variant BASE_NAME libshared_and_static)
set_target_properties(static_variant PROPERTIES
COMPILE_FLAGS -DLIBSHARED_AND_STATIC_STATIC_DEFINE)
此处为共享库生成导出header,静态库有额外的编译定义以供重用header。
再次注意,用于共享库和静态库的不同编译选项意味着您不能在这些库之间重用 object 文件。您只能重复使用 sources.
背景
我使用 cmake 构建了一个 open-source library。
项目设置为执行以下操作:
- 构建一个名为
gpds-objs
的 cmake - 从
gpds-objs
构建一个名为 - 从
gpds-objs
构建一个名为
OBJECT
库
gpds-static
的 STATIC
库
gpds-shared
的 SHARED
库
此外,我正在使用 cmake 的 generate_export_header()
生成必要的导出宏。
cmake 脚本的相关部分如下所示:
# Set project information
project(gpds
VERSION 1.0.0
LANGUAGES CXX
HOMEPAGE_URL "https://gpds.simulton.com"
)
# Some bacis cmake configuration
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)
# List of private source files
set(SOURCES_PRIVATE
# ...
)
# List of private header files
set(HEADERS_PRIVATE
# ...
)
# List of public header files
set(HEADERS_PUBLIC
# ...
)
# Define targets
set(NAME gpds)
set(TARGET-OBJS ${NAME}-objs)
set(TARGET-STATIC ${NAME}-static)
set(TARGET-SHARED ${NAME}-shared)
################################################################################
# Object library #
################################################################################
add_library(${TARGET-OBJS} OBJECT)
target_compile_features(
gpds-objs
PUBLIC
cxx_std_17
)
target_sources(
${TARGET-OBJS}
PRIVATE
${SOURCES_PRIVATE}
${HEADERS_PRIVATE}
)
target_include_directories(
${TARGET-OBJS}
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>
PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include/gpds>
)
################################################################################
# Shared library #
################################################################################
add_library(${TARGET-SHARED} SHARED)
target_link_libraries(
${TARGET-SHARED}
PUBLIC
gpds-objs
)
target_compile_definitions(
${TARGET-SHARED}
PRIVATE
gpds_shared_EXPORTS # We're building this library!
)
################################################################################
# Static library #
################################################################################
add_library(${TARGET-STATIC} STATIC)
target_link_libraries(
${TARGET-STATIC}
PUBLIC
gpds-objs
)
target_compile_definitions(
${TARGET-STATIC}
PUBLIC
GPDS_STATIC_DEFINE
)
# Common library properties
set_target_properties(
${TARGET-OBJS}
${TARGET-STATIC}
${TARGET-SHARED}
PROPERTIES
OUTPUT_NAME "gpds"
ARCHIVE_OUTPUT_NAME "gpds"
VERSION ${PROJECT_VERSION}
POSITION_INDEPENDENT_CODE 1
)
################################################################################
# Export header #
################################################################################
include(GenerateExportHeader)
generate_export_header(
${TARGET-SHARED}
BASE_NAME gpds
EXPORT_FILE_NAME gpds_export.hpp
DEPRECATED_MACRO_NAME "GPDS_DEPRECATED"
NO_DEPRECATED_MACRO_NAME "GPDS_NO_DEPRECATED"
EXPORT_MACRO_NAME "GPDS_EXPORT"
NO_EXPORT_MACRO_NAME "GPDS_NO_EXPORT"
STATIC_DEFINE "GPDS_STATIC_DEFINE"
DEFINE_NO_DEPRECATED
)
库中的 class 定义如下所示:
#include "gpds_export.hpp"
namespace gpds
{
class GPDS_EXPORT container
{
// ...
};
}
为了完整起见,这里是 gpds_export.hpp
的相关部分,由 cmake 的 generate_export_header()
:
#ifdef GPDS_STATIC_DEFINE
# define GPDS_EXPORT
# define GPDS_NO_EXPORT
#else
# ifndef GPDS_EXPORT
# ifdef gpds_shared_EXPORTS
/* We are building this library */
# define GPDS_EXPORT __declspec(dllexport)
# else
/* We are using this library */
# define GPDS_EXPORT __declspec(dllimport)
# endif
# endif
# ifndef GPDS_NO_EXPORT
# define GPDS_NO_EXPORT
# endif
#endif
问题
我 运行 遇到的问题是在构建 gpds-objs
时,我收到以下消息:
C:\Users\joel\Documents\projects\gpds\lib\src\value.cpp:7:1: warning: 'gpds::value::value(const gpds::value&)' redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]
7 | value::value(const value& other) :
| ^~~~~
C:\Users\joel\Documents\projects\gpds\lib\src\value.cpp:17:1: warning: 'gpds::value::value(gpds::value&&)' redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]
17 | value::value(value&& other) :
| ^~~~~
C:\Users\joel\Documents\projects\gpds\lib\src\value.cpp:25:1: warning: 'virtual gpds::value::~value()' redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]
25 | value::~value() noexcept
| ^~~~~
C:\Users\joel\Documents\projects\gpds\lib\src\value.cpp:33:6: warning: 'void gpds::value::from_string(std::string&&)' redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]
33 | void value::from_string(std::string&& string)
我不确定如何正确解决这个问题。
据我了解,问题是构建 gpds-objs
目标没有定义任何相关的导出宏(GPDS_STATIC_DEFINE
和 gpds_shared_EXPORTS
都没有)。因此,GPDS_EXPORT
被定义为 __declspec(dllimport)
,这是不正确的,因为我们没有使用 gpds-objs
目标构建共享库。
我能想到的一个解决方案是在 gpds-objs
目标而不是 gpds-shared
目标上定义 gpds_shared_EXPORTS
。但是,这意味着它也在构建 gpds-static
目标时定义。只要 gpds-static
定义 GPDS_STATIC_DEFINE
.
正确的处理方法是什么?
您不能对共享库和静态库使用相同的OBJECT库。
OBJECT 库决定了源文件的编译方式。 但是对于静态库和共享库,源文件应该以不同的方式编译。
Documentation for GenerateExportHeader 描述了生成的同一个 header 文件如何用于静态库和共享库:
add_library(shared_variant SHARED ${lib_SRCS})
add_library(static_variant ${lib_SRCS})
generate_export_header(shared_variant BASE_NAME libshared_and_static)
set_target_properties(static_variant PROPERTIES
COMPILE_FLAGS -DLIBSHARED_AND_STATIC_STATIC_DEFINE)
此处为共享库生成导出header,静态库有额外的编译定义以供重用header。 再次注意,用于共享库和静态库的不同编译选项意味着您不能在这些库之间重用 object 文件。您只能重复使用 sources.