You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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是宏函数生成目标,很容易出现链接顺序、链接范围异常的问题。

正确配置步骤

  1. 添加动态库符号导出声明
    在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;
    }
    
  2. 修正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)配置
    
  3. 修正子模块的链接配置
    废弃全局链接写法,在每个子模块的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)
    
  4. 全量清理后重新编译
    删除原有build目录重新生成CMake工程,编译后Release和Debug目录下都会生成对应的lib和dll文件,链接错误消失。运行ADTF时所有filter加载同一份loggerModule.dll,单例全局唯一,日志可以正常统一归集。


内容的提问来源于stack exchange,提问作者polPolpol

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 23:57:14