我的项目目标如下:
从我的主可执行文件中,我希望加载一个库(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
C可以成功地执行dlopen("libfoo.so", RTLD_LAZY)
为此,我在编译main.c的target_link_directories(main PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libs)
中添加了一个CMakeLists.txt语句。
这似乎产生了在主可执行文件中添加RPATH的效果,如所需:
$ objdump -x ./main | grep PATH
RUNPATH /home/user/code/scratch/3/build/libs
但是foo.c执行dlopen("libbar.so", RTLD_LAZY)
并不成功,即使我在它的CMakeLists.txt中添加了一个target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
语句。
我注意到,尽管添加了target_link_directories
语句,libfoo.so仍然没有RPATH。
$ objdump -x ./libs/libfoo.so | grep PATH
$
Things我试过
看起来,RPATH不会添加到共享库中,除非至少有一个target_link_libraries
语句--即使它是一个“不必要的”库。
也就是说,如果我将libfoo.so链接到libbar.so,那么libfoo.so具有所需的RPATH。
# Linking libbar works, but I'd prefer not to do this:
target_link_libraries(foo bar)
...results:
$ objdump -x ./libs/libfoo.so | grep PATH
RUNPATH /home/user/code/scratch/3/build/libs
...also --如果我将一个“不必要的”共享库与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})
发布于 2022-08-07 02:03:27
这是一个有用的建筑。您只需适当地调整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
和朋友。第二行确保包含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!
回答以下评论中的一些后续问题:
$<TARGET_FILE_DIR:foo>
是什么?TARGET_FILE_DIR
是CMake变量(在CMakeLists.txts中可见)吗?
它不是一个变量,而是一个。这些表达式的值是在执行整个配置步骤之后确定的。通过这种方式,我们可以确保这个表达式将扩展到包含libfoo.so
的实际目录,而不仅仅是我们所期望的包含它的目录。
通常,我更喜欢使用生成器表达式,而不是尽可能地使用变量。他们倾向于为CMake编程提供更多声明性的感觉,而不是命令性的感觉,并且有更少的边缘情况。例如,用户可能会将CMAKE_RUNTIME_OUTPUT_DIRECTORY
的值设置为意想不到的值。如果您从RPATH
或其他方面计算CMAKE_CURRENT_BINARY_DIR
,则会破坏您的构建。
你能谈谈
target_link_libraries(main dl)
(我的版本)和target_link_libraries(main PRIVATE ${CMAKE_DL_LIBS})
(你的版本)之间的区别吗?
这里有两个不同之处,两者都很重要:
使用没有可见性说明符的
PRIVATE
,这取决于策略设置以及对target_link_libraries
的其他调用是否有可见性说明符。为了清晰起见,为了避免这些缺陷,您应该始终PUBLIC
.PRIVATE
、INTERFACE
或PRIVATE
CMAKE_DL_LIBS
is 中的一个--链接到包含dlopen
系列库函数的哪个库。你知道HP使用-ldld
吗?或者AIX使用-lld
?还是BSD(包括macOS)没有单独的库?那么,在这些平台上传递的-ldl
就坏了。在现代(post CMake ~3.5)时代,使用没有可见性说明符的target_link_libraries
或传递原始链接标志都是严重的代码气味。尽量避开他们,当你认为你不能问的时候,问一些问题。
https://stackoverflow.com/questions/73263834
复制相似问题