g++ 编译器创建静态和动态链接库(Linux 系统)

                     

贡献者: addis

  • 本文处于草稿阶段。
预备知识 C/C++ 多文件编译

   参考这篇文章这篇文章

   事实上,ubuntu 系统中用 apt 安装 lib*-dev 就是在系统的默认搜索路径添加头文件和 lib*.alib*.so 文件,以及它依赖的 package 中的这些文件。这些文件在同一系统版本和同一 cpu 架构都是通用的(运气好的话也可能在不同系统中通用)。

   在 C/C++ 中,如果一个函数声明时前面使用了 static,那么它将只在当前 translation unit 中可见,也就是无法被的文件在 link 时使用。

1. 静态链接库

   .a 文件是 static library, 在编译的时候一起编入可执行文件. 下面举一个例子

// lib1.cpp
#include <iostream>
using namespace std;
int f1() { cout << "In library 1" << endl; }

   再编一个主文件

// main.cpp
#include <iostream>
using namespace std;
void f1();
int main()
{
  f1();
  cout << "In main" << endl;
}

   如果将这两个文件用 g++ 正常编译 g++ main.cpp lib1.cpp 执行结果为

In library 1
In main
但现在我们把 lib1.cpp 先编译成 .o 文件

g++ -static -c -o lib1.o lib1.cpp

   (其实 -o lib1.o 可以省略)(-static 用于静态编译), 再从 .o 文件生成 .a 文件 .a 文件的命名规则一般是前面加 (lib*.a)

ar rcs lib1.a lib1.o
可以将多个 .o 文件封装到 .a 里面, 在后面添加 lib2.o, lib3.o 等即可。.a 就是 .o 的压缩文件(archive),artar 差不多。(其中 rcs 选项中的 r 选项是添加并替换旧文件(如果有同名), c 选项是 create archive, s 选项是 write out an index, 虽然还是不明白什么意思)。若要打印文件内容,用 ar p lib1.a 或者 ar pv lib1.a(verbose)。若要取出所有文件,用 ar x lib1.a [文件1] [文件2]

   再来将 lib1.a 和主程序文件一块编译

g++ main.cpp -o main.x -L./ -l 1

   其中 -o 的作用是给生成的可执行文件命名, -L 的作用是声明 .a 所在的目录, -l 是指明所用的 .a 文件, (将 lib*.a 写成 * 即可). 现在就可以运行 main.x 了 ./main.x

2. 动态链接库 (*.so)

  1创建动态库:

g++ -shared -fPIC -o lib1.so lib1.cpp

   使用方法: 把 cpp 编译成 .o 文件时不需要声明动态链接库和所在目录, -c 选项(不链接)普通编译即可.

g++ -c main.cpp
.o 文件链接成可执行文件时, 在最后 (注意必须是在最后) 加上 -Wl,-rpath,<library path> -L<library path> -l <libname1> -l <libname2> 其中 <library path> 可以是多个路径,如 path1:path2:... 其中 -Wl,aaa,bbb 命令是将 aaa bbb 选项传给 linker,剩下的 -L<library path> -l <libname1> -l <libname2> 的用法和上述 .a 中的一样.

g++ -o main.x main.o -l1 -L./ -Wl,-rpath,./

   Linux 程序运行时搜索动态链接库的顺序(详见这里以及 man8):

  1. 可执行文件的 rpath(一般不推荐)
  2. 环境变量 LD_LIBRARY_PATH
  3. 可执行文件的 runpath
  4. /lib//usr/lib/ 中的,以及 /etc/ld.so.conf 中的文件。在 ubuntu 中,该文件实际上 include/etc/ld.so.conf.d 路径中的所有 *.conf 文件。

   可以用 ldd main.x 查看动态链接库,会发现其中有 lib1.so。如果不用 rpath,也可以在执行可执行文件以前把路径加入到环境变量 LD_LIBRARY_PATH 中。rpathLD_LIBRARY_PATH 可以是相对于可执行文件的相对路径,也可以是绝对路径。也可以用 $ORIGIN 表示可执行文件的路径

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/your/custom/path/

   其他笔记:

3. 多个动态库

   动态库本身也可以依赖于其他动态库,例如再添加一个程序

// lib0.cpp
#include <iostream>
using namespace std;
void f0()
{
  cout << "In library 0" << endl;
}
然后修改 lib1
// lib1.cpp
#include <iostream>
using namespace std;
void f1()
{
  cout << "In library 1" << endl;
  void f0();
  f0();
}
制作库
g++ -c lib0.cpp lib1.cpp // 生成 lib0.o lib1.o
g++ -shared -fPIC -o lib0.so lib0.cpp
g++ -shared -fPIC -o lib1.so lib1.cpp -l0 -L./ -Wl,-rpath,./
编译主程序,使用库,注意只需要链接 lib1
g++ -c main.cpp
g++ -o main.x main.o -l1 -L./ -Wl,-rpath,./
ldd main.x 检查所有依赖的动态库,会发现 lib0, lib1 都在。

代码 1:make.sh(注意要 source make.sh 而不是 ./make.sh)
export LD_LIBRARY_PATH=$PWD
g++ -shared -fPIC -o libbase.so.1 libbase.1.cpp
g++ -shared -fPIC -o libbase.so.2 libbase.2.cpp
g++ -shared -fPIC -o libtest1.so libtest1.cpp -L . -l:libbase.so.1
g++ -shared -fPIC -o libtest2.so libtest2.cpp -L . -l:libbase.so.2
g++ -o main.x main.cpp -L . -l:libtest1.so -l:libtest2.so
代码 2:main.cpp
void test1(); void test2();
void conflict();
int main() { test1(); test2(); conflict(); }
代码 3:libtest1.cpp
#include <iostream>
using namespace std;
void base();
void test1() { base(); }
void conflict() { cout << "conflict() in libtest1.cpp" << endl; }
代码 4:libtest2.cpp
#include <iostream>
using namespace std;
void base();
void test2() { base(); }
void conflict() { cout << "conflict() in libtest2.cpp" << endl; }
代码 5:libtest1.cpp
#include <iostream>
using namespace std;
void base() { cout << "base() in libbase.1" << endl; }
void conflict() { cout << "conflict() in libbase1" << endl; }
代码 6:libtest2.cpp
#include <iostream>
using namespace std;
void base() { cout << "base() in libbase.2" << endl; }
void conflict() { cout << "conflict() in libbase2" << endl; }

4. 运行时动态链接(dlopen)

#include <iostream>
#include <dlfcn.h>  // Required for dlopen, dlsym, and dlclose
#include <cstdlib>  // For exit()

typedef int (*processFunc)(double, const char*);

int main() {
    // Load the shared library
    void* handle = dlopen("./libexample.so", RTLD_LAZY);
    if (!handle) {
        std::cerr << "Error: " << dlerror() << std::endl;
        exit(EXIT_FAILURE);
    }

    // Reset errors
    dlerror();

    // Get the function symbol from the library
    processFunc process = (processFunc) dlsym(handle, "process");
    const char* dlsym_error = dlerror();
    if (dlsym_error) {
        std::cerr << "Error: " << dlsym_error << std::endl;
        dlclose(handle);
        exit(EXIT_FAILURE);
    }

    // Call the function with sample arguments
    double num = 42.5;
    const char* str = "Hello, world!";
    int result = process(num, str);
    
    std::cout << "Result: " << result << std::endl;

    // Close the library
    dlclose(handle);

    return 0;
}

5. 如何把一个动态程序变得 portable


1. ^ 部分参考这个教程


致读者: 小时百科一直以来坚持所有内容免费无广告,这导致我们处于严重的亏损状态。 长此以往很可能会最终导致我们不得不选择大量广告以及内容付费等。 因此,我们请求广大读者热心打赏 ,使网站得以健康发展。 如果看到这条信息的每位读者能慷慨打赏 20 元,我们一周就能脱离亏损, 并在接下来的一年里向所有读者继续免费提供优质内容。 但遗憾的是只有不到 1% 的读者愿意捐款, 他们的付出帮助了 99% 的读者免费获取知识, 我们在此表示感谢。

                     

友情链接: 超理论坛 | ©小时科技 保留一切权利