从对象库和 DLL 导出的 CMake 共享库

CMake shared library from object library & DLL exports

背景

我使用 cmake 构建了一个 open-source library

项目设置为执行以下操作:

此外,我正在使用 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_DEFINEgpds_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.