需要帮助理解与 cmake 的链接

Need help understanding linking with cmake

需要一些帮助来理解库和 link使用 cmake。对不起文字墙。

我创建了两个静态库,foobarbar依赖于foobar 只是 foo 的延伸。然后我在某个地方安装 bar 供另一个项目使用。当我开始编译这个其他项目时,我 运行 遇到了 linker 问题。问题是对函数的未定义引用。在 foo 中定义但在 bar

中根本没有使用的函数

我有一个基本的理解,图书馆是 objects/symbols 的集合,所以我假设通过 linking foobarbar 也会有那些 objects/symbols,但事实并非如此,所以我想知道我是否遗漏了什么或者我做错了什么。

这个问题很有帮助,有点类似:CMake: how create a single shared library from all static libraries of subprojects?

就像 link 建议的那样,我尝试像这样添加 -Wl,--whole-archive 标志

set_target_properties(bar PROPERTIES LINK_FLAGS "-Wl,--whole-archive")

但是设置那些 link 标志没有用。

我不想使用 SHARED 库,我更愿意使用 STATIC 库,我怎样才能让我的库保留所有 objects/symbols?我是否被迫 link 同时 barfoo

或者是使用自定义命令组合库的唯一解决方案,就像这个问题中建议的那样:Combining several static libraries into one using CMake


这里我试着举例说明一下:

我有一个结构如下的项目:

src/
|
|- foo/
|   | - foo.h
|   | - foo.c
|   | - CMakeLists.txt
|- bar/
|   | - bar.h
|   | - bar.c
|   | - CMakeLists.txt

这是foo

下文件的源代码
////////////////// foo.c

#include <stdio.h>

#include <Python.h>

#include "foo.h"

static PyObject * module_global;

void init_interpreter()
{
  printf("Initializing interpreter\n");
  Py_Initialize();
}

void fin_interpreter()
{
  printf("Finalizing interpreter\n");
  Py_FinalizeEx();
}

void import_module(const char* module_name)
{
  if(module_name)
  {
    module_global = PyImport_ImportModule(module_name);
  }
  else
  {
    printf("No good module provided\n");
  }
}


////////////////// foo.h

#ifndef FOO_H
#define FOO_H

void init_interpreter();
void fin_interpreter();
void import_module(const char* module_name);

#endif // FOO_H

/////////////////// foo/CMakeLists.txt

find_package (Python REQUIRED COMPONENTS Development)

add_library(foo STATIC
  foo.c
)

target_link_libraries(foo PUBLIC
  ${Python_LIBRARIES}
)

target_include_directories(foo PUBLIC
  ${Python_INCLUDE_DIRS}
  ${CMAKE_CURRENT_SOURCE_DIR}
)

这是bar

下文件的源代码
////////////////// bar.c

#include <stdio.h>

#include "foo.h"

void pretty_init_interpreter()
{
  printf("This is a very pretty call!!\n");
  init_interpreter();
}

////////////////// bar.h

#ifndef BAR_H
#define BAR_H

void pretty_init_interpreter();

#endif // BAR_H

/////////////////// bar/CMakeLists.txt

add_library(bar STATIC
  bar.c
)

target_link_libraries(bar PUBLIC
  foo
)

target_include_directories(bar PUBLIC
  $<TARGET_PROPERTY:foo,INCLUDE_DIRECTORIES>
  ${CMAKE_CURRENT_SOURCE_DIR}
)

我构建了这些库并将库 bar.a 安装到另一个项目,如下所示:

src/
|
|- main.c
|- CMakeLists.txt
|- install/
|   | - lib
|   |   | - libbar.a
|   | - include
|   |   | - foo.h
|   |   | - bar.h

本项目源代码:

/////////////// main.c
#include <stdio.h>
#include "foo.h"
#include "bar.h"

int main(int argc, char** argv)
{
  pretty_init_interpreter();

  import_module(NULL);

  fin_interpreter();

  return 0;
}
//////////////// CMakeLists.txt

cmake_minimum_required(VERSION 3.20)

set (CMAKE_C_STANDAR 99)

project(foo-project LANGUAGES C)

find_package (Python REQUIRED COMPONENTS Development)

add_executable(foo-exec main.c)

find_library(BAR_LIB
NAMES
  bar
HINTS
  ${PROJECT_SOURCE_DIR}/install/lib/
NO_DEFAULT_PATH
)

target_link_libraries(foo-exec PUBLIC
  ${BAR_LIB}
  ${Python_LIBRARIES}
)

target_include_directories(foo-exec PUBLIC
  ${Python_INCLUDE_DIRS}
  ${PROJECT_SOURCE_DIR}/install/include/
)

用mingw编译可执行文件,出现如下错误:

c:/mingw/bin/../lib/gcc/mingw32/9.2.0/../../../../mingw32/bin/ld.exe: CMakeFiles\foo-exec.dir/objects.a(main.c.obj): in function `main':
main.c:9: undefined reference to `import_module'
c:/mingw/bin/../lib/gcc/mingw32/9.2.0/../../../../mingw32/bin/ld.exe: main.c:11: undefined reference to `fin_interpreter'
c:/mingw/bin/../lib/gcc/mingw32/9.2.0/../../../../mingw32/bin/ld.exe: ../../install/lib/libbar.a(bar.c.obj): in function `pretty_init_interpreter':
bar/bar.c:8: undefined reference to `init_interpreter'
collect2.exe: error: ld returned 1 exit status

nmbar的符号,发现缺少函数

$ nm install/lib/libbar.a

bar.c.obj:
00000000 b .bss
00000000 d .data
00000000 N .debug_abbrev
00000000 N .debug_aranges
00000000 N .debug_info
00000000 N .debug_line
00000000 r .eh_frame
00000000 r .rdata
00000000 r .rdata$zzz
00000000 t .text
         U _init_interpreter
00000000 T _pretty_init_interpreter
         U _puts

I had a basic understanding that libraries were a collection of objects/symbols, so I had assumed that by linking foo to bar, bar would also have those objects/symbols, but that is not the case, so I am wondering if I am missing something or if I am doing something wrong.

确实情况并非如此。 C/C++ 语言标准没有定义静态库的内容,但在大多数平台上它们是 包含特定目标文件的档案 。如果这些目标文件共同依赖于外部符号,则该静态库依赖于某些 other 库或目标文件,并且 all 必须链接到最终文件中可执行文件。

在 CMake 中将一个静态库“链接”到另一个静态库只是声明它们之间的依赖关系。没有 platform-independent 合并它们的机制,在 CMake 中也没有迫切需要这样做。

I do not want to use SHARED libraries, I would prefer to stay with STATIC libraries, how can I have my libraries keep all the objects/symbols? Am I forced to link both bar and foo?

Whosebug 上有许多 个问题和答案,详细说明了如何使用 OBJECT 个库来完成此任务。比如说,这里有五个:

然而,我要说所有这些问题都是错误的。 CMake 可以很好地处理链接库依赖项。

恐怕您考虑的细节太多了。 CMake 正在为您处理链接器标志。

您想定义两个库目标,foo 和 bar,对吗? @Tsyvarev 是绝对正确的,静态库不涉及链接,但更多的是创建存档(在 Linux ar 中使用)。只有当你创建一个二进制文件,一个可执行文件——无论是一个带有主函数的真正的可执行文件还是一个dll/shared对象库——你都会有一个链接过程。

但是 CMake 中的依赖关系即使对于静态库也是有意义的,因为 CMake 不仅传播实际的库,还包括您传递给目标的目录和其他属性 public。

因此,您希望在您的项目中使用可执行文件:您所有库的 CMake 目标,可以将其导入其他项目。这意味着导出 CMake-file 定义你的库目标

add_library(foo STATIC IMPORTED)
add_library(bar STATIC IMPORTED)
target_link_libraries(bar PUBLIC foo)

(https://cmake.org/cmake/help/latest/command/add_library.html?highlight=add_library#imported-libraries) 您可能想要填充一些目标属性,例如包含目录。 附加 public compiler/linker 标志等

许多外部库将它们的 CMake-config 文件安装到默认位置之一(在 Linux 上,例如在 /usr/lib/cmake/ 中,或者如果您使用的是 Qt,您可以找到Qt 安装目录 Windows 上的 CMake 配置文件位于静态库旁边的 cmake 目录中;搜索有疑问的 *.cmake 文件)。您可以将自己的 CMake-config 文件放在那里,或者让自己从其他组件的工作方式中获得灵感。

这里有一些链接,您可能会觉得有趣: