CMake同时编译cpp和cu文件,并且生成动态链接库 前言 最近碰到一个项目需求,在ubuntu22的环境下,使用TensorRT框架推理优化神经网络模型,使用cuda并行处理多路输入,并将这部分功能封装为动态链接库,方便主程序调用。
这个需求主要涉及到cmake编译生成动态链接库,以及cpp和cu文件如何同时编译。
cmake生成链接库及调用 动态库 假如有三个cpp文件,main.cpp,a.cpp, b.pp,项目文件目录如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 . ├── bin ├── build ├── CMakeLists.txt ├── include │ ├── a.h │ └── b.h ├── lib ├── README.md └── src ├── A │ ├── a.cpp # 生成动态库A │ └── CMakeLists.txt ├── B │ ├── b.cpp # 生成动态库B │ └── CMakeLists.txt └── Main ├── CMakeLists.txt # 主程序 └── main.cpp
其中调用关系为:主程序调用库A,A依赖于库B
项目目录下的CMakeLists
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 cmake_minimum_required (VERSION 3.10 )project (Test )set (LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR} /lib) set (BIN_PATH ${CMAKE_CURRENT_SOURCE_DIR} /bin) set (HEAD_PATH ${CMAKE_CURRENT_SOURCE_DIR} /include ) set (PRO ${CMAKE_CURRENT_SOURCE_DIR} ) add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR} /src/A) add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR} /src/B) add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR} /src/Main)
Main程序目录下的CMakeLists.txt
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 cmake_minimum_required (VERSION 3.10 )project (Main)aux_source_directory (${CMAKE_CURRENT_SOURCE_DIR} SRC)set (EXECUTABLE_OUTPUT_PATH ${BIN_PATH} )add_executable (Main ${SRC} )target_include_directories (Main PRIVATE ${HEAD_PATH} )target_include_directories (Main PRIVATE ${LIB_SRC_PATH} /include )target_link_directories (Main PRIVATE ${LIB_PATH} )target_link_libraries (Main PRIVATE A)
A库目录下的CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 cmake_minimum_required (VERSION 3.10 )project (A)aux_source_directory (${CMAKE_CURRENT_SOURCE_DIR} SRC)set (LIBRARY_OUTPUT_PATH ${LIB_PATH} )add_library (A SHARED ${SRC} )target_include_directories (A PRIVATE ${HEAD_PATH} )target_include_directories (A PRIVATE ${LIB_SRC_PATH} /include )target_link_directories (A PRIVATE ${LIB_PATH} )target_link_libraries (A PRIVATE B)
B目录下的CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 cmake_minimum_required (VERSION 3.10 )project (B)aux_source_directory (${CMAKE_CURRENT_SOURCE_DIR} SRC)set (LIBRARY_OUTPUT_PATH ${LIB_PATH} )add_library (B SHARED ${SRC} )target_include_directories (B PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /header)target_include_directories (B PRIVATE ${LIB_SRC_PATH} /include )
cpp和.h文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 void this_a () ;void this_b () ;#include <iostream> #include <b.h> void this_a () { std::cout<<"This is A!" <<std::endl; this_b (); } #include <iostream> void this_b () { std::cout<<"This is B!" <<std::endl; } #include <iostream> #include "a.h" int main () { std::cout << "this is Main!" << std::endl; this_a (); }
输出结果:
1 2 3 this is Main! This is A! This is B!
疑问? 为什么这里exe调用的时候只指定了A的库位置,但是没影响输出?
静态库本身不携带依赖的库代码 ,因此:当可执行文件链接静态库时,必须显式链接静态库的所有依赖库(包括直接/间接依赖)。
动态库默认不自动传递依赖 ,如果希望可执行文件自动获取其依赖,需通过 target_link_libraries
传递依赖链 ,这里使用了target_link_libraries
自动传递了依赖,
若出现链接错误(如 undefined reference
),通常是因为未正确传递依赖链。可使用以下命令观察实际链接参数:
关于private和public的不同
在 CMake 中,PUBLIC
和 PRIVATE
是用于控制目标(如库或可执行文件)属性传递性 的关键字,它们的核心区别在于:PRIVATE
定义的属性仅作用于当前目标,而 PUBLIC
定义的属性会同时作用于当前目标和依赖它的其他目标 。
关键字
作用范围
典型场景
PRIVATE
仅当前目标
内部实现依赖(如仅自己需要的头文件、编译选项、库)
PUBLIC
当前目标 + 依赖它的其他目标
接口依赖(如头文件暴露给使用者、需要传递的链接库)
静态库 将上面的两个动态库改为静态库后,注意,生成两个库的静态库后,这里不执行两个库的CMake文件,会出现链接错误:
1 undefined reference to `this_b()'
这就对应上述所说的:当可执行文件链接静态库时,必须显式链接静态库的所有依赖库(包括直接/间接依赖)。
因此,这个时候我们显式链接静态库的所有依赖库。在./src/Main/CMakeLists.txt
中添加如下内容:
1 2 target_link_directories (Main PRIVATE ${PRO} /temp)target_link_libraries (Main PRIVATE B)
将TensorRT推理后的模型封装为库 项目cmake代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 cmake_minimum_required (VERSION 3.10 )set (CUDA_TOOLKIT_ROOT_DIR cuda路径) set (TRT_ROOT tensorRT路径) set (CMAKE_CUDA_COMPILER ${CUDA_TOOLKIT_ROOT_DIR} /bin/nvcc) set (CMAKE_CUDA_ARCHITECTURES OFF ) project (TRT)set (LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR} /lib) set (BIN_PATH ${CMAKE_CURRENT_SOURCE_DIR} /bin) set (HEAD_PATH ${CMAKE_CURRENT_SOURCE_DIR} /include ) set (LIB_SRC_PATH ${CMAKE_CURRENT_SOURCE_DIR} /src/FreqBandRecognitionTRT) set (EXE_TRT TestExe) set (SO_TRT TestDll) include_directories (${HEAD_PATH} ) include_directories (${CUDA_TOOLKIT_ROOT_DIR} /include ) include_directories (${TRT_ROOT} /include ) link_directories (${TENSORRT_ROOT} /lib) set (ENV{PATH} "tensorRT路径/bin:$ENV{PATH}" )set (ENV{LD_LIBRARY_PATH} "tensorRT路径/lib:$ENV{LD_LIBRARY_PATH}" )if (CMAKE_CUDA_COMPILER) enable_language (CUDA) message (STATUS "CUDA support enabled." ) if (POLICY CMP0146) cmake_policy (SET CMP0146 OLD) endif () include (FindCUDA) set (CUDA_ARCH_LIST Auto CACHE STRING "List of CUDA architectures (e.g. Pascal, Volta, etc)\ or compute capability version (6.1, 7.0, etc) to generate code for. Set to Auto for \ automatic detection (default)." ) cuda_select_nvcc_arch_flags(CUDA_ARC_FLAGS ${CUDA_ARC_LIST} ) list (APPEND CUDA_NVCC_FLAGS ${CUDA_ARCH_FLAGS} ) else () message (WARNING "CUDA Support disable." ) endif ()add_compile_options (-Wno-deprecated-declarations)add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR} /src/TestExe)
封装成库的cmake代码 1 2 3 4 5 6 7 8 9 10 11 12 13 cmake_minimum_required(VERSION 3.10) project(TestDll VERSION 1.0 LANGUAGES CXX CUDA) # 允许cuda编译 aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC) set(LIBRARY_OUTPUT_PATH ${LIB_PATH}) add_library(${SO_TRT} STATIC ${SRC}) target_include_directories(${SO_TRT} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/header) # 链接内部头文件 target_include_directories(${SO_TRT} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) # 外部引用头文件 set_target_properties(${SO_TRT} PROPERTIES CUDA_SEPARABLE_COMPILATION ON) # 使用cuda编译 target_link_libraries(${SO_TRT} PUBLIC nvinfer nvinfer_plugin nvonnxparser) # 链接tensorRT库 target_link_directories(${SO_TRT} PUBLIC ${TRT_ROOT}/lib) target_link_libraries(${SO_TRT} PUBLIC cufft) # 链接cuda库 target_link_directories(${SO_TRT} PUBLIC ${CUDA_TOOLKIT_ROOT_DIR}/lib)
只需要更改此处的add_library
参数,即可控制生成的是动态库还是静态库
动态库调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 cmake_minimum_required (VERSION 3.10 )project (TestExe VERSION 1.0 LANGUAGES CXX CUDA)aux_source_directory (${CMAKE_CURRENT_SOURCE_DIR} /src SRC)set (EXECUTABLE_OUTPUT_PATH ${BIN_PATH} )add_executable (${EXE_TRT} ${SRC} )target_include_directories (${EXE_TRT} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /header)target_include_directories (${EXE_TRT} PRIVATE ${LIB_SRC_PATH} /include )target_link_directories (${EXE_TRT} PRIVATE ${LIB_PATH} )target_link_libraries (${EXE_TRT} ${SO_TRT} )target_link_libraries (${EXE_TRT} ${CUDA_LIBRARIES} )set_target_properties (${EXE_TRT} PROPERTIES CUDA_SEPARABLE_COMPILATION ON ) target_link_libraries (${EXE_TRT} nvinfer nvinfer_plugin nvonnxparser) target_link_directories (${EXE_TRT} PUBLIC ${TRT_ROOT} /lib)target_link_libraries (${EXE_TRT} cufft) target_link_directories (${EXE_TRT} PUBLIC ${CUDA_TOOLKIT_ROOT_DIR} /lib)
调用动态库时,可以不显式说明调用的库的位置,但是为了安全起见,此处最好说明
静态库调用 静态库调用一定要加上上述的要调用的库的链接库文件,否则会出现链接错误。
踩坑记录
在cpp文件中使用cuda的库使用cmake编译时,会导致链接错误
原因:没有使用nvcc自动分离cuda代码和cpp代码。
解决方案:将cpp文件改为cu结尾
在链接库时,若没有指定,而目录下同时有静态库和动态库,会优先调用哪一个?
动态库