CMake同时编译cpp和cu文件,并且生成动态链接库
前言
最近碰到一个项目需求,在ubuntu22的环境下,使用TensorRT框架推理优化神经网络模型,使用cuda并行处理多路输入,并将这部分功能封装为动态链接库,方便主程序调用。
这个需求主要涉及到cmake编译生成动态链接库,以及cpp和cu文件如何同时编译。
cmake生成链接库及调用
动态库
假如有三个cpp文件,main.cpp,a.cpp, b.pp,项目文件目录如下
.
├── 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:
# ./CMakeLists.txt
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}) # 自定义库路径
# include_directories(${HEAD_PATH}) # 链接外部头文件
# 添加子目录
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:
# ./src/Main/CMakeLists.txt
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
# ./src/A/CMakeLists.txt
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)
# 链接库文件B
target_link_directories(A PRIVATE ${LIB_PATH})
target_link_libraries(A PRIVATE B)
B目录下的CMakeLists.txt
# ./src/B/CMakeLists.txt
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文件
// a.h
void this_a();
// b.h
void this_b();
// a.cpp
#include <iostream>
#include <b.h>
void this_a(){
std::cout<<"This is A!"<<std::endl;
this_b();
}
// b.cpp
#include <iostream>
void this_b(){
std::cout<<"This is B!"<<std::endl;
}
// main.cpp
#include <iostream>
#include "a.h"
int main(){
std::cout << "this is Main!" << std::endl;
this_a();
}
输出结果:
this is Main!
This is A!
This is B!
疑问? 为什么这里exe调用的时候只指定了A的库位置,但是没影响输出?
静态库本身不携带依赖的库代码,因此:当可执行文件链接静态库时,必须显式链接静态库的所有依赖库(包括直接/间接依赖)。
动态库默认不自动传递依赖,如果希望可执行文件自动获取其依赖,需通过
target_link_libraries传递依赖链,这里使用了target_link_libraries自动传递了依赖,若出现链接错误(如
undefined reference),通常是因为未正确传递依赖链。可使用以下命令观察实际链接参数:make VERBOSE=1
关于private和public的不同
在 CMake 中,PUBLIC 和 PRIVATE 是用于控制目标(如库或可执行文件)属性传递性的关键字,它们的核心区别在于:
PRIVATE 定义的属性仅作用于当前目标,而 PUBLIC 定义的属性会同时作用于当前目标和依赖它的其他目标。
| 关键字 | 作用范围 | 典型场景 |
|---|---|---|
PRIVATE |
仅当前目标 | 内部实现依赖(如仅自己需要的头文件、编译选项、库) |
PUBLIC |
当前目标 + 依赖它的其他目标 | 接口依赖(如头文件暴露给使用者、需要传递的链接库) |
静态库
将上面的两个动态库改为静态库后,注意,生成两个库的静态库后,这里不执行两个库的CMake文件,会出现链接错误:
undefined reference to `this_b()'
这就对应上述所说的:当可执行文件链接静态库时,必须显式链接静态库的所有依赖库(包括直接/间接依赖)。
因此,这个时候我们显式链接静态库的所有依赖库。在./src/Main/CMakeLists.txt中添加如下内容:
target_link_directories(Main PRIVATE ${PRO}/temp)
target_link_libraries(Main PRIVATE B)
将TensorRT推理后的模型封装为库
项目cmake代码
cmake_minimum_required(VERSION 3.10)
# 设置程序依赖路径,此处需要根据实际生产环境进行修改
set(CUDA_TOOLKIT_ROOT_DIR cuda路径) # cuda安装路径
set(TRT_ROOT tensorRT路径) # tensorRT路径
# 设置cuda编译选项
set(CMAKE_CUDA_COMPILER ${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc) # 显式的指定cuda编译器
set(CMAKE_CUDA_ARCHITECTURES OFF) # 让CMake和nvcc自动检测当前可用的GPU架构
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) # cuda头文件
include_directories(${TRT_ROOT}/include) # tensorRT头文件
link_directories(${TENSORRT_ROOT}/lib) # tensorRT静态库
# 设置环境变量,确保运行时可以找到TensorRT库
set(ENV{PATH} "tensorRT路径/bin:$ENV{PATH}")
set(ENV{LD_LIBRARY_PATH} "tensorRT路径/lib:$ENV{LD_LIBRARY_PATH}")
# 启动cuda编译
if (CMAKE_CUDA_COMPILER)
enable_language(CUDA) # 告诉系统要使用CUDA语言,绝不可以省略
message(STATUS "CUDA support enabled.")
if (POLICY CMP0146) # 忽略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 ()
# 添加编译选项 -Wno-deprecated-declarations
add_compile_options(-Wno-deprecated-declarations)
# 添加子目录
# add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/TestDll) # 动态链接库目录
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/TestExe) # 测试文件目录
封装成库的cmake代码
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参数,即可控制生成的是动态库还是静态库
动态库调用
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) # 使用cuda编译
target_link_libraries(${EXE_TRT} nvinfer nvinfer_plugin nvonnxparser) # 链接tensorRT库
target_link_directories(${EXE_TRT} PUBLIC ${TRT_ROOT}/lib)
target_link_libraries(${EXE_TRT} cufft) # 链接cuda静态库
target_link_directories(${EXE_TRT} PUBLIC ${CUDA_TOOLKIT_ROOT_DIR}/lib)
调用动态库时,可以不显式说明调用的库的位置,但是为了安全起见,此处最好说明
静态库调用
静态库调用一定要加上上述的要调用的库的链接库文件,否则会出现链接错误。
踩坑记录
在cpp文件中使用cuda的库使用cmake编译时,会导致链接错误
原因:没有使用nvcc自动分离cuda代码和cpp代码。
解决方案:将cpp文件改为cu结尾
在链接库时,若没有指定,而目录下同时有静态库和动态库,会优先调用哪一个?
动态库