CMake多子目录项目配置SHARED库实现单例日志的方案及LNK1181报错解决
- 构建环境:采用CMake构建、VS2010编译C/C++解决方案,面向ADTF框架开发,依赖ADTF API,项目由多个独立功能模块组成,每个模块自带独立CMakeLists.txt配置文件
- 核心需求:StatisticsLogger基于单例模式实现,需被所有模块访问,且所有模块必须访问同一个库实例,不允许存在多份库副本,实现全局日志数据统一归集、日志文件统一管理
- 初始问题:默认静态库配置下,框架运行时每个模块都会单独触发StatsLogger构造函数生成独立实例,无法汇总全链路日志数据,文件处理复杂度高
最初的静态库CMake配置如下:
add_library(loggerModule ${DSTD_DIR}/dstdfloat.h ${DSTD_DIR}/dstdint.h ${DSTD_DIR}/dstdbool.h ${SUPT_DIR}/logg/statslogger.h ${SUPT_DIR}/logg/statslogger_c_connector.h ${SUPT_DIR}/logg/statslogger_c_connector.cpp ${SUPT_DIR}/logg/statslogger.cpp ) #set_target_properties(loggerModule PROPERTIES VERSION ${PROJECT_VERSION}) #set_target_properties(loggerModule PROPERTIES PUBLIC_HEADER include/mylib.h) link_libraries(loggerModule)
- 初步思路:在
add_library命令中添加SHARED属性将库编译为动态共享库,实现单实例全局共享 - 遇到的问题:配置后出现多处链接错误,无法正常编译
项目根目录CMakeLists.txt配置如下:
# CMAKE for test filter build cmake_minimum_required(VERSION 2.8.4 FATAL_ERROR) # Set default install prefix set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}) # Set project name project(${PROJECT_NAME}) set_property(GLOBAL PROPERTY USE_FOLDERS ON) # Environment checks find_package(ADTF 2.14.2 REQUIRED) IF (NOT DEFINED ADTF_FOUND) MESSAGE( FATAL_ERROR "ADTF was NOT found!!!") ENDIF() find_package(ADTF_DISPLAY_TOOLBOX REQUIRED) IF (NOT DEFINED ADTF_DISPLAY_TOOLBOX_FOUND) MESSAGE( FATAL_ERROR "ADTF_DISPLAY_TOOLBOX was NOT found!!!") ENDIF() set(COMPLETE_PROJECT_BINARY_OUTPUT_DIR ${CMAKE_SOURCE_DIR}/${PROJECT_BINARY_OUTPUT_DIRECTORY}) # Set path to sw module dependencies set(COMMON_DIR ${CMAKE_SOURCE_DIR}/common) set(DSTD_DIR ${CMAKE_SOURCE_DIR}/common/dstd) set(INTF_DIR ${CMAKE_SOURCE_DIR}/common/intf) set(SUPT_DIR ${CMAKE_SOURCE_DIR}/common/supt) #set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) #option(BUILD_SHARED_LIBS "Build using shared libraries" ON) include_directories(${DSTD_DIR}) include_directories(${SUPT_DIR}/logg) add_library(loggerModule SHARED ${DSTD_DIR}/dstdfloat.h ${DSTD_DIR}/dstdint.h ${DSTD_DIR}/dstdbool.h ${SUPT_DIR}/logg/statslogger.h ${SUPT_DIR}/logg/statslogger_c_connector.h ${SUPT_DIR}/logg/statslogger_c_connector.cpp ${SUPT_DIR}/logg/statslogger.cpp ) #set_target_properties(loggerModule PROPERTIES VERSION ${PROJECT_VERSION}) #set_target_properties(loggerModule PROPERTIES PUBLIC_HEADER include/mylib.h) link_libraries(loggerModule) # Set commands for BB nodes generation # ...and dependencies for generation # Go into sub-directory with filter sources add_subdirectory(${CMAKE_SOURCE_DIR}/tool/adtf/af_acca) #[33 other add_subdirectories commands]
某一子目录的CMakeLists.txt配置示例:
# External required components have to be provided with path variables # Internal required components have to be connected to the source file list if(NOT DEFINED DSTD_DIR) message( FATAL_ERROR "AF_CDAS requires DSTD_DIR" ) endif() if(NOT DEFINED INTF_DIR) message( FATAL_ERROR "AF_CDAS requires INTF_DIR" ) endif() if(NOT DEFINED SUPT_DIR) message( FATAL_ERROR "AF_CDAS requires SUPT_DIR" ) endif() set(CDAS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../) include_directories(${COMMON_DIR}) [some more includes] # # CDAS FILES # set(CDAS_FILES ${CDAS_DIR}/src/cdas/cdas.c ${CDAS_DIR}/src/cdas/cdas.h) source_group("cdas\\files" FILES ${CDAS_FILES}) # # CDAS ADTF WRAPPER # set(CDAS_ADTF_WRAPPER ${CDAS_DIR}/tool/adtf/af_cdas/af_cdas_conf.h ${CDAS_DIR}/tool/adtf/af_cdas/af_cdas.h ${CDAS_DIR}/tool/adtf/af_cdas/af_cdas.cpp) source_group("cdas\\adtf" FILES ${CDAS_ADTF_WRAPPER}) adtf_add_filter(CDAS # List source and header files of your filter and its required components # ${MAIN_FILES} ${CDAS_FILES} ${CDAS_ADTF_WRAPPER} ) # stdafx.h workaround if (MSVC) set_target_properties(CDAS PROPERTIES COMPILE_FLAGS "/Y-") endif(MSVC) # MANDATORY PATH SETTING !!! install (TARGETS CDAS DESTINATION ${COMPLETE_PROJECT_BINARY_OUTPUT_DIR}/debug CONFIGURATIONS Debug) install (TARGETS CDAS DESTINATION ${COMPLETE_PROJECT_BINARY_OUTPUT_DIR}/release CONFIGURATIONS Release) adtf_set_folder(CDAS filter)
编译时出现的错误:
LINK : fatal error LNK1181: cannot open input file '......\Release\loggerModule.lib' [C:\Users\inno\Desktop\radar_processing\test\adtf\build\win64_vc100\tool\adtf\af_aoca\AOCA.vcxproj]
现象排查结果:Release目录下仅生成loggerModule.dll文件,不存在loggerModule.lib导入库文件;Debug目录下虽生成了对应.lib文件,但复制到Release目录无法解决问题。
- 将日志库配置为SHARED共享库是否是实现全局单例访问的正确方案?
- 当前配置存在哪些问题导致LNK1181链接报错?
- 如何正确配置实现全局单实例日志库的需求?
共享库方案的正确性
是正确方案。Windows平台下静态库会被直接链接到每个调用模块(ADTF的每个filter本质是独立dll)中,每个模块都会持有一份静态库的代码和全局变量副本,单例自然会生成多份。编译为SHARED动态库后,所有模块在运行时加载同一份dll,全局单例只会存在一个实例,完全匹配需求。
LNK1181报错的核心原因
报错本质是Release配置下没有生成loggerModule.lib导入库。MSVC编译动态库时,只有库中存在显式导出的符号才会生成对应的.lib导入库,当前代码没有任何符号导出声明,VS默认不会导出任何符号;Debug配置下因为默认开启增量链接、调试信息生成等选项,可能偶然生成lib,但Release配置下开启优化后,没有导出符号就不会生成lib,自然会报找不到文件的错误。
另外全局link_libraries(loggerModule)的写法不规范,该命令只对在它之后声明的目标生效,而ADTF的adtf_add_filter是宏函数生成目标,很容易出现链接顺序、链接范围异常的问题。
正确配置步骤
添加动态库符号导出声明
在statslogger.h中添加Windows平台导出宏,所有需要被外部模块调用的类、函数都要加上导出宏:#ifdef _WIN32 # ifdef LOGGERMODULE_EXPORTS # define LOGGER_API __declspec(dllexport) # else # define LOGGER_API __declspec(dllimport) # endif #else # define LOGGER_API #endif // 单例类加上导出宏 class LOGGER_API StatsLogger { public: static StatsLogger& GetInstance(); // 其余对外成员函数 private: StatsLogger(); ~StatsLogger(); // 其余私有成员 }; // 全局C接口也要加导出宏 LOGGER_API void stats_logger_write(const char* msg);注意:单例的实例实现必须放在
statslogger.cpp中,不要在头文件的类内直接写静态局部变量或全局静态实例,否则会触发ODR违规导致多实例。正确实现示例:// statslogger.cpp中 StatsLogger& StatsLogger::GetInstance() { static StatsLogger instance; return instance; }修正loggerModule的CMake配置
给loggerModule添加导出宏定义,同时统一配置输出目录,保证不同配置下dll和lib都生成到固定位置,避免路径查找错误:add_library(loggerModule SHARED ${DSTD_DIR}/dstdfloat.h ${DSTD_DIR}/dstdint.h ${DSTD_DIR}/dstdbool.h ${SUPT_DIR}/logg/statslogger.h ${SUPT_DIR}/logg/statslogger_c_connector.h ${SUPT_DIR}/logg/statslogger_c_connector.cpp ${SUPT_DIR}/logg/statslogger.cpp ) # 添加导出宏定义,编译dll时自动触发dllexport target_compile_definitions(loggerModule PRIVATE LOGGERMODULE_EXPORTS) # 统一输出目录,所有配置的lib/dll都生成到对应目录 set_target_properties(loggerModule PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${COMPLETE_PROJECT_BINARY_OUTPUT_DIR}/lib LIBRARY_OUTPUT_DIRECTORY ${COMPLETE_PROJECT_BINARY_OUTPUT_DIR}/lib RUNTIME_OUTPUT_DIRECTORY ${COMPLETE_PROJECT_BINARY_OUTPUT_DIR}/bin ) # 删除根目录下全局的link_libraries(loggerModule)配置修正子模块的链接配置
废弃全局链接写法,在每个子模块的CMakeLists中,调用adtf_add_filter之后,用target_link_libraries显式链接loggerModule,同时安装阶段把loggerModule也复制到filter同目录,保证运行时能找到dll:adtf_add_filter(CDAS ${CDAS_FILES} ${CDAS_ADTF_WRAPPER} ) # 显式链接日志库 target_link_libraries(CDAS PRIVATE loggerModule) # 原有编译配置保留 if (MSVC) set_target_properties(CDAS PROPERTIES COMPILE_FLAGS "/Y-") endif(MSVC) # 安装时把loggerModule也复制到filter同目录 install (TARGETS CDAS loggerModule DESTINATION ${COMPLETE_PROJECT_BINARY_OUTPUT_DIR}/debug CONFIGURATIONS Debug) install (TARGETS CDAS loggerModule DESTINATION ${COMPLETE_PROJECT_BINARY_OUTPUT_DIR}/release CONFIGURATIONS Release)全量清理后重新编译
删除原有build目录重新生成CMake工程,编译后Release和Debug目录下都会生成对应的lib和dll文件,链接错误消失。运行ADTF时所有filter加载同一份loggerModule.dll,单例全局唯一,日志可以正常统一归集。
内容的提问来源于stack exchange,提问作者polPolpol

