首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >cmake:如何在只有target_link_directories (没有target_link_libraries)的共享库中设置rpath?

cmake:如何在只有target_link_directories (没有target_link_libraries)的共享库中设置rpath?
EN

Stack Overflow用户
提问于 2022-08-06 23:09:11
回答 1查看 325关注 0票数 1

我的项目目标如下:

从我的主可执行文件中,我希望加载一个库(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已知的某些标准目录。

我的项目的目录结构如下:

代码语言:javascript
运行
复制
.
├── 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的效果,如所需:

代码语言:javascript
运行
复制
$ 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。

代码语言:javascript
运行
复制
$ objdump -x ./libs/libfoo.so | grep PATH
$

Things我试过

看起来,RPATH不会添加到共享库中,除非至少有一个target_link_libraries语句--即使它是一个“不必要的”库。

也就是说,如果我将libfoo.so链接到libbar.so,那么libfoo.so具有所需的RPATH。

代码语言:javascript
运行
复制
# Linking libbar works, but I'd prefer not to do this:
target_link_libraries(foo bar)

...results:

代码语言:javascript
运行
复制
$ objdump -x ./libs/libfoo.so | grep PATH
  RUNPATH              /home/user/code/scratch/3/build/libs

...also --如果我将一个“不必要的”共享库与target_link_directories语句链接起来,那么libfoo.so也有所需的RPATH:

代码语言:javascript
运行
复制
# 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})
代码语言:javascript
运行
复制
$ 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到不必要的库)?

代码/文件:

代码语言:javascript
运行
复制
// 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;
}
代码语言:javascript
运行
复制
// 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);
}
代码语言:javascript
运行
复制
// libs/bar.c
#include <stdio.h>

void func() {
  printf("here in libbar!\n");
}
代码语言:javascript
运行
复制
# 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)
代码语言:javascript
运行
复制
# 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})
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-08-07 02:03:27

这是一个有用的建筑。您只需适当地调整BUILD_RPATH属性,并且,如果您编写install()规则,则调整您的INSTALL_RPATH以类似于我用this answer编写的内容。下面的构建是健壮的,并调整了BUILD_RPATH

代码语言:javascript
运行
复制
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中。

以下是控制台输出:

代码语言:javascript
运行
复制
$ 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}) (你的版本)之间的区别吗?

这里有两个不同之处,两者都很重要:

使用没有可见性说明符的

  1. 将其置于一个奇怪的模糊状态,这种状态可能类似于PRIVATE,这取决于策略设置以及对target_link_libraries的其他调用是否有可见性说明符。为了清晰起见,为了避免这些缺陷,您应该始终PUBLIC.
  2. Using 指定PRIVATEINTERFACEPRIVATE CMAKE_DL_LIBS is 中的一个--链接到包含dlopen系列库函数的哪个库。你知道HP使用-ldld吗?或者AIX使用-lld?还是BSD(包括macOS)没有单独的库?那么,在这些平台上传递的-ldl就坏了。

在现代(post CMake ~3.5)时代,使用没有可见性说明符的target_link_libraries或传递原始链接标志都是严重的代码气味。尽量避开他们,当你认为你不能问的时候,问一些问题。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73263834

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档