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. ^ 部分参考这个教程

                     

© 小时科技 保留一切权利