将静态库链接到其他静态库

176

我有一小段依赖于多个静态库(a_1-a_n)的代码。我想把这些代码打包成一个静态库并提供给其他人使用。

我的静态库,我们称其为 X,编译没问题。

我创建了一个简单的示例程序,使用了 X 中的一个函数,但是当我试图将其链接到 X 时,会出现许多缺少来自 a_1 - a_n 库的符号的错误。

是否有一种方法可以创建一个新的静态库 Y,该库包含 X 和 X 所需的所有功能(从 a_1 - a_n 中选择的部分),以便我可以仅向用户分发 Y,让他们将其链接到他们的程序中?


更新:

我尝试过用 ar 将所有内容都编译到一个大型库中,但是那样会包含许多不必要的符号(所有 .o 文件大约为 700 MB,但静态链接可执行文件只有 7 MB)。是否有一种好的方法只包含实际需要的内容?


这看起来与如何将多个 C/C++ 库组合成一个库密切相关。

6个回答

102

静态库不能与其他静态库链接。唯一的方法是使用您的图书管理员/存档工具(例如Linux上的ar)通过连接多个库来创建一个新的单一静态库。

编辑:针对您的更新,我所知道的仅有的选择所需符号的方法是手动从包含它们的.o文件子集中创建库。这很困难、耗时且容易出错。我不知道是否有任何工具可以帮助做到这一点(并不是说它们不存在),但制作这样一个工具将会非常有趣。


嗨,尼尔,我更新了问题 - 你知道有什么方法只包括必要的.o文件吗? - Jason Sundram
更新--如果你发现自己想要做这件事,请退后一步,追求其他事情(除非像John Knoeller指出的那样,你正在使用Visual Studio)。我花了很多时间来尝试这种方法,但没有得到任何有用的东西。 - Jason Sundram
GNU ld 工具提供了一个选项 -r,使用它可以将输出作为 ld 的输入。如果您只链接一次,应该会得到一个可重定位的文件,可以代替您的库。(虽然我尝试过这样做)。 - harper

53

如果您正在使用Visual Studio,则可以这样做。

Visual Studio提供的库生成工具允许您在命令行上合并库。但是,我不知道如何在可视化编辑器中完成此操作。

lib.exe /OUT:compositelib.lib  lib1.lib lib2.lib

6
在VS2008中,如果你在compositelib的项目属性下选择Librarian/General,勾选[x]链接库依赖项,当lib1和lib2是compositelib的依赖项时,它将为你执行此操作。这似乎有些漏洞,我建议在每个构建配置中单独设置复选框,而不是在“所有配置”中一次性设置。 - Spike0xff
2
在VS2015 IDE中,您不使用“库管理员/常规”下的“附加依赖项”将其他库直接链接到您的项目正在构建的库中吗? - davidbak
1
@davidbak 我最近几天一直在尝试解决这个问题,但显然这个选项已经过时了,没有任何作用? - Montaldo

27

在Linux或MingW上,使用GNU工具链:

ar -M <<EOM
    CREATE libab.a
    ADDLIB liba.a
    ADDLIB libb.a
    SAVE
    END
EOM
ranlib libab.a

如果您不删除liba.alibb.a,则可以创建一个"瘦"归档文件:

ar crsT libab.a liba.a libb.a

在 Windows 平台上,使用 MSVC 工具链:

lib.exe /OUT:libab.lib liba.lib libb.lib

很高兴知道,但并不能真正解决OP的问题,因为你将libb.a中的所有内容都包含在联合库中,如果你只需要从libb中使用几个模块,那么联合库可能会变得非常大。 - Elmar Zander
1
@ElmarZander 但是你可以使用 ar crsT,它类似于“符号链接”,而不是复制,然后你只需要运输一份数据的副本。 - Star Brilliant
薄型归档在Linux内核v4.19上得到了显着应用:https://unix.stackexchange.com/questions/5518/what-is-the-difference-between-the-following-kernel-makefile-terms-vmlinux-vml/482978#482978 - Ciro Santilli OurBigBook.com

17

静态库只是一个.o目标文件的归档集合。在Unix系统中,使用ar命令进行提取并重新打包为一个大型库。


10
在阅读以下内容之前,请注意:此处显示的shell脚本肯定不安全使用且未经充分测试。请自行承担风险!
我编写了一个bash脚本来完成这个任务。假设您的库是lib1,而您需要从中包含一些符号的库是lib2。现在该脚本在循环中运行,首先检查哪些未定义的符号可以在lib2中找到。然后,它使用 ar 从lib2中提取相应的目标文件,稍微重命名一下,并将它们放入lib1中。现在可能会有更多缺失的符号,因为您从lib2中包含的东西需要lib2的其他东西,我们尚未包含,因此循环需要再次运行。如果经过几次循环后不再有任何更改,即没有从lib2添加到lib1的目标文件,则循环可以停止。
请注意,所包含的符号仍然被 nm 报告为未定义,因此我跟踪了已添加到lib1中的目标文件本身,以确定是否可以停止循环。
#! /bin/bash

lib1="$1"
lib2="$2"

if [ ! -e $lib1.backup ]; then
    echo backing up
    cp $lib1 $lib1.backup
fi

remove_later=""

new_tmp_file() {
    file=$(mktemp)
    remove_later="$remove_later $file"
    eval $1=$file
}
remove_tmp_files() {
    rm $remove_later
}
trap remove_tmp_files EXIT

find_symbols() {
    nm $1 $2 | cut -c20- | sort | uniq 
}

new_tmp_file lib2symbols
new_tmp_file currsymbols

nm $lib2 -s --defined-only > $lib2symbols

prefix="xyz_import_"
pass=0
while true; do
    ((pass++))
    echo "Starting pass #$pass"
    curr=$lib1
    find_symbols $curr "--undefined-only" > $currsymbols
    changed=0
    for sym in $(cat $currsymbols); do
        for obj in $(egrep "^$sym in .*\.o" $lib2symbols | cut -d" " -f3); do
            echo "  Found $sym in $obj."
            if [ -e "$prefix$obj" ]; then continue; fi
            echo "    -> Adding $obj to $lib1"
            ar x $lib2 $obj
            mv $obj "$prefix$obj"
            ar -r -s $lib1 "$prefix$obj"
            remove_later="$remove_later $prefix$obj"
            ((changed=changed+1))
        done
    done
    echo "Found $changed changes in pass #$pass"

    if [[ $changed == 0 ]]; then break; fi
done

我将那个脚本命名为libcomp,所以你可以通过以下方式调用它:
./libcomp libmylib.a libwhatever.a

Libwhatever是你想要包含符号的库。然而,我认为最安全的方法是先将所有内容复制到一个单独的目录中。我不太相信我的脚本(尽管它对我有效);我可以使用它将libgsl.a包含到我的数值库中,并省略-lgsl编译器开关。


这太棒了。对于一个大项目(原始库中>40k对象中的>500k符号,其中我需要约1000个符号),这花费了近一个小时的时间,而gcc可以在几秒钟内使用动态链接实现基本相同的功能。是否有一些根本性的原因,为什么这不容易做,并由编译器工具公开呢? - ZachB

7

除了在项目属性中使用链接库依赖项的方法之外,Visual Studio 中还有另一种链接库的方法。

  1. 打开您想要与其他库(X)合并的库(X)的项目。
  2. 添加您想要与X合并的其他库(右键单击,选择添加现有项...)。
  3. 进入它们的属性,并确保项类型

这将包含其他库在 X 中,就像运行了以下命令一样:

lib /out:X.lib X.lib other1.lib other2.lib

你好,我正在使用Intel IPP,并且我已经构建了自己的函数,希望将它们全部封装到一个(静态)库中。但是,当我创建该库并将项目发送到其他计算机时,我想只使用我创建的库来编译该项目,但是出现错误,说需要Intel IPP库的 .h 文件。有什么想法吗? - Royi
通常仅有lib文件是不够的。如果您想使用它,还需要包含头文件。但这与本主题无关,因为该线程讨论的是链接,而您的问题在编译阶段。 - evpo
请提供更多信息以帮助您解决问题。查看构建输出或日志,看看缺少的.h文件是哪个部分报错。我认为这可能是cl.exe引起的。如果是这种情况,它会告诉您使用该头文件的已编译.cpp/.cc/.c文件的名称。那个.cpp文件的名称是什么,它属于哪个项目? - evpo
我很困惑。在我读这篇文章之前,它似乎从未起作用过(因为我的项目已经配置好了)。但是现在我已经阅读并重置了我的项目,它显然正在工作。真是让人费解!:( - Mordachai
1
@Mordachai,下面这首俳句完美地描述了你的经历:昨天它能用 今天它不行了 Windows就是这样 - evpo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接