我的项目目标如下:
我想从我的主可执行文件中加载一个库 (libfoo.so),该库 (libfoo.so) 会加载第二个库 (libbar.so)。
我不想在任何 filename
中指定相对或绝对路径我传递给 dlopen
的参数:即我希望我的代码读取“dlopen("libfoo.so", RTLD_LAZY)
”而不是 "/path/to/libfoo.so"
或"../to/libfoo.so"
.
我的理解是,查找共享库的方式(通过 libdl)是 1) 环境变量 LD_LIBRARY_PATH
的值,2)在二进制文件中“嵌入”RPATH,3)libdl已知的某些标准目录。
我的项目的目录结构是这样的:
.
├── CMakeLists.txt
├── build # this directory exists to perform an "out-of-source" build with "cmake .."
├── libs
│ ├── CMakeLists.txt
│ ├── bar.c
│ └── foo.c
└── main.c
main.c 可以成功执行dlopen("libfoo.so", RTLD_LAZY)
我通过添加 target_link_directories(main PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libs)
来做到这一点CMakeLists.txt 中编译 main.c 的语句。
这似乎具有根据需要在 main 可执行文件中添加 RPATH 的效果:
$ objdump -x ./main | grep PATH
RUNPATH /home/user/code/scratch/3/build/libs
但是 foo.c 执行 dlopen("libbar.so", RTLD_LAZY)
失败即使我添加了 target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
CMakeLists.txt 中的声明。
我注意到尽管添加了 target_link_directories
声明,libfoo.so 没有 RPATH:
$ objdump -x ./libs/libfoo.so | grep PATH
$
我尝试过的事情
看起来,RPATH 不会添加到共享库,除非至少有一个 target_link_libraries
声明——即使它是一个“不必要的”库。
IE。如果我将 libfoo.so 与 libbar.so 链接,则 libfoo.so 具有所需的 RPATH:
# Linking libbar works, but I'd prefer not to do this:
target_link_libraries(foo bar)
...结果:
$ objdump -x ./libs/libfoo.so | grep PATH
RUNPATH /home/user/code/scratch/3/build/libs
...如果我将“不必要的”共享库与 target_link_directories
一起链接,语句,那么 libfoo.so 也具有所需的 RPATH:
# Linking libbar works, but I'd prefer not to do this:
# target_link_libraries(foo bar)
# Linking an unnecessary library, then doing target_link_directories also works:
target_link_libraries(foo dl)
target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
$ objdump -x ./libs/libfoo.so | grep PATH
RUNPATH /home/user/code/scratch/3/build/libs
问题
我是否正确理解CMake的行为:target_link_directories
如果至少有一个target_link_library
,语句只会在共享库中产生相应的RPATH条目。共享库的声明(即使是“不必要的”库)?
如果这是正确的,有人可以解释一下理由吗?
是否有另一种“更干净”的方法将RPATH添加到共享库(最好使用 target_link_directories
),而无需任何“不必要的”语句(例如将 target_link_library
添加到不必要的库)?
代码/文件:
// main.c
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
void* handle = dlopen("libfoo.so", RTLD_LAZY);
if (!handle) {
printf("dlopen error: %s\n", dlerror());
return EXIT_FAILURE;
}
{
void (*fptr)() = dlsym(handle, "func");
if (fptr) { fptr(); }
}
dlclose(handle);
return EXIT_SUCCESS;
}
// libs/foo.c
#include <dlfcn.h>
#include <stdio.h>
void func() {
void* handle = dlopen("libbar.so", RTLD_LAZY);
printf("here in libfoo!\n");
if (!handle) {
printf("dlopen error: %s\n", dlerror());
return;
}
{
void (*fptr)() = dlsym(handle, "func");
if (fptr) { fptr(); }
}
dlclose(handle);
}
// libs/bar.c
#include <stdio.h>
void func() {
printf("here in libbar!\n");
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(my_prj)
add_subdirectory(libs)
add_executable(main main.c)
target_link_libraries(main dl)
target_link_directories(main PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libs)
# libs/CMakeLists.txt
add_library(bar SHARED bar.c)
add_library(foo SHARED foo.c)
# This is what I want, but it doesn't work:
target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
# Linking libbar works, but I'd prefer not to do this:
# target_link_libraries(foo bar)
# Linking an unnecessary library, then doing target_link_directories also works:
# target_link_libraries(foo dl)
# target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
最佳答案
这是一个工作版本。您只需调整 BUILD_RPATH
属性适当,如果你写 install()
规则,调整你的INSTALL_RPATH
类似于我在this answer中写的内容。以下构建非常稳健,并调整 BUILD_RPATH
:
cmake_minimum_required(VERSION 3.23)
project(test)
add_library(bar SHARED libs/bar.c)
add_library(foo MODULE libs/foo.c)
target_link_libraries(foo PRIVATE bar)
add_executable(main main.c)
target_link_libraries(main PRIVATE ${CMAKE_DL_LIBS})
set_property(TARGET main APPEND PROPERTY BUILD_RPATH "$<TARGET_FILE_DIR:foo>")
最后两行很重要。您必须链接到CMAKE_DL_LIBS
可移植调用dlopen
和 friend 。第二行确保目录包含 libfoo
,你知道main
将加载,位于 RPATH
.
这是控制台输出:
$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alex/test/build
$ cmake --build build/ --verbose
[1/6] /usr/bin/cc -Dbar_EXPORTS -O2 -g -DNDEBUG -fPIC -MD -MT CMakeFiles/bar.dir/libs/bar.c.o -MF CMakeFiles/bar.dir/libs/bar.c.o.d -o CMakeFiles/bar.dir/libs/bar.c.o -c /home/alex/test/libs/bar.c
[2/6] /usr/bin/cc -Dfoo_EXPORTS -O2 -g -DNDEBUG -fPIC -MD -MT CMakeFiles/foo.dir/libs/foo.c.o -MF CMakeFiles/foo.dir/libs/foo.c.o.d -o CMakeFiles/foo.dir/libs/foo.c.o -c /home/alex/test/libs/foo.c
[3/6] /usr/bin/cc -O2 -g -DNDEBUG -MD -MT CMakeFiles/main.dir/main.c.o -MF CMakeFiles/main.dir/main.c.o.d -o CMakeFiles/main.dir/main.c.o -c /home/alex/test/main.c
[4/6] : && /usr/bin/cc -fPIC -O2 -g -DNDEBUG -shared -Wl,-soname,libbar.so -o libbar.so CMakeFiles/bar.dir/libs/bar.c.o && :
[5/6] : && /usr/bin/cc -O2 -g -DNDEBUG CMakeFiles/main.dir/main.c.o -o main -Wl,-rpath,/home/alex/test/build -ldl && :
[6/6] : && /usr/bin/cc -fPIC -O2 -g -DNDEBUG -shared -o libfoo.so CMakeFiles/foo.dir/libs/foo.c.o -Wl,-rpath,/home/alex/test/build libbar.so && :
$ ./build/main
here in libfoo!
here in libbar!
回答评论中的一些后续问题:
[W]hat is
$<TARGET_FILE_DIR:foo>
? IsTARGET_FILE_DIR
a CMake variable (visible in CMakeLists.txts)?
它不是一个变量,它是一个generator expression 。这些表达式的值是在执行整个配置步骤后确定的。这样,我们就可以确定这个表达式将扩展到包含 libfoo.so
的实际目录。 ,而不仅仅是我们期望包含它的那个。
一般来说,只要有可能,我更喜欢使用生成器表达式而不是变量。它们倾向于使 CMake 编程更具声明性,而不是命令性,并且边缘情况较少。例如,用户可能设置值 CMAKE_RUNTIME_OUTPUT_DIRECTORY
到一些意想不到的事情。如果您计算RPATH
,这会破坏您的构建来自CMAKE_CURRENT_BINARY_DIR
或其他什么。
[C]an you speak to the difference between
target_link_libraries(main dl)
(my version) andtarget_link_libraries(main PRIVATE ${CMAKE_DL_LIBS})
(your version)?
这里有两个区别,都很重要:
- 使用
target_link_libraries
没有可见性说明符会将其置于一种奇怪的边缘状态,有点类似于PRIVATE
,也许,取决于策略设置以及是否有任何其他调用target_link_libraries
有一个可见性说明符。为了清楚起见并避免这些陷阱,您应该始终指定PRIVATE
之一,INTERFACE
,或PUBLIC
. - 使用
CMAKE_DL_LIBS
是链接到包含dlopen
的库的正确方法。库函数族。您知道 HP-UX 使用-ldld
?或者 AIX 使用-lld
?或者 BSD(包括 macOS)没有为此提供单独的库?嗯,路过-ldl
这些平台已损坏。
使用target_link_libraries
在现代(后 CMake ~3.5)时代,没有可见性说明符或传递原始链接标志都是严重的代码味道。尽量避免他们,并在您认为无法做到时提出问题。
关于cmake:如何在只有 target_link_directories (没有 target_link_libraries)的共享库中设置 rpath?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73263834/