跳转至

Static and dynamic libraries

webpage

不过我感觉官网这份讲的有点太冗长了,而且缺乏实例,因此我个人提炼总结一下,写一份更加客制化的笔记。

简单类比:

  1. 静态库 就像每个办公室都配一台打印机
  2. 动态库 就像共享一个打印室

在我自己日常的开发过程中,用到的基本都是静态库,但是实际生活中(CLI包管理器)一般都是动态库

Static Library

文件组成

静态库在编译时会被直接链接到你的程序中,成为可执行文件的一部分。

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// mathlib.h
#ifndef MATHLIB_H
#define MATHLIB_H

namespace MathLib {
    int add(int a, int b);
    int multiply(int a, int b);
}

#endif
C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// mathlib.cpp
#include "mathlib.h"

namespace MathLib {
    int add(int a, int b) {
        return a + b;
    }

    int multiply(int a, int b) {
        return a * b;
    }
}

编译成静态库后(Windows上是.lib,Linux上是.a),使用方式:

C++
1
2
3
4
5
6
7
8
9
// main.cpp
#include <iostream>
#include "mathlib.h"

int main() {
    int result = MathLib::add(5, 3);
    std::cout << "5 + 3 = " << result << std::endl;
    return 0;
}

工程过程

项目结构:

Bash
1
2
3
4
5
6
7
mathlib_project/
├── mathlib.h
├── mathlib.cpp
├── main.cpp
└── lib/
    ├── mathlib.o
    └── libmathlib.a

1) 创建项目专有文件夹

Bash
1
2
3
mkdir mathlib_project
cd mathlib_project
mkdir lib

在这个/lib下面创建上述提到的三个文件:

  • mathlib.h
  • mathlib.cpp
  • main.cpp

alt text

2) 编译静态库

首先,将 mathlib.cpp 编译为目标文件:

Bash
1
g++ -c mathlib.cpp -o lib/mathlib.o

然后,使用 ar 命令创建静态库(在macOS上扩展名也是.a):

Bash
1
ar rcs lib/libmathlib.a lib/mathlib.o

这里的参数说明:

  • r:替换库中已有的目标文件
  • c:创建库(如果库不存在)
  • s:创建目标文件索引

alt text

3) 编译主程序

使用静态库编译 main.cpp:

Bash
1
g++ main.cpp -L./lib -lmathlib -I. -o main

参数说明:

  • -L./lib:指定库文件搜索路径
  • -lmathlib:链接 libmathlib.a(注意去掉lib前缀和.a后缀)
  • -I.:指定头文件搜索路径(当前目录)
  • -o main:指定输出文件名

4) 运行可执行程序

alt text

Bash
1
./main
what is ar command

ar 命令是一个用于创建、修改和提取归档文件的工具,主要用于以下用途:

常用命令选项

  • r:替换或添加文件到归档文件中
  • t:显示归档文件中的内容列表
  • p:显示归档文件中成员的内容
  • d:从归档文件中删除成员
  • c:创建新的归档文件
  • s:创建归档文件的索引,用于加快访问速度

最常见的用途是在 软件开发中创建静态库

  1. 将编译后的目标文件(.o文件)组合成静态库
  2. 创建程序链接时需要的库文件
  3. 生成可供链接器快速定位符号的索引

在复盘的过程中,我们可能会好奇,为什么要把 lib/mathlib.o 变成 lib/libmathlib.a?

.o 文件(目标文件)

  • 是单个源文件编译后的中间产物
  • 包含机器码,但还不能直接执行
  • 不能直接被其他程序使用

.a 文件(静态库文件)

  • 是多个 .o 文件的集合
  • 可以被其他程序链接使用
  • 是最终的库文件形式

这就像是把散落的零件(.o 文件)组装成一个完整的工具包(.a 文件),其他程序就可以直接使用这个工具包了。

一个很简单的检验方式

ar -t lib/libmathlib.a

使用这条指令可以查看库的详细信息

Bash
1
2
3
 ar -t lib/libmathlib.a
__.SYMDEF SORTED
mathlib.o

小结(静态库实现与使用):

  1. 将源文件放在项目根目录,新建 lib 文件夹
  2. 用与库函数相关的.h and .cpp文件,编译生成.o文件并放在/lib
  3. ar命令将.o文件打包成.a文件 (库函数包文件)
  4. 编译main.cpp,使用上述提供的.a库函数包文件

Dynamic Library

使用dynamic library的优点是可复用性很强:在全局视角下,每个功能都有自己相应的lib,供全局范围内其他所有文件引用使用

只需要修改math.h,剩余内容/布局和在上面完全一致

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// mathlib.h
#ifndef MATHLIB_H
#define MATHLIB_H

#ifdef __APPLE__
    #define EXPORT __attribute__((visibility("default")))
#else
    #define EXPORT
#endif

namespace MathLib {
    EXPORT int add(int a, int b);
    EXPORT int multiply(int a, int b);
}
#endif

1) 编译动态库

首先,将 mathlib.cpp 编译为目标文件,需要使用 -fPIC 选项(Position Independent Code):

Bash
1
g++ -c -fPIC mathlib.cpp -o lib/mathlib.o

创建动态库(在macOS上是.dylib, linux上是.dll):

C++
1
g++ -shared -o lib/libmathlib.dylib lib/mathlib.o

2) 编译主程序

Bash
1
g++ main.cpp -L./lib -lmathlib -I. -o main

3) 运行程序

PS: 在运行之前,需要设置动态库搜索路径

Bash
1
2
export DYLD_LIBRARY_PATH=./lib:$DYLD_LIBRARY_PATH
./main

alt text

在复盘的时候,我们可能会考虑,每次都要人为隐式地告诉链接路径,是不是很麻烦?

因此我们在这里介绍 方式2(显式加载)在macOS上需要使用 dlopen():

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// main.cpp - macOS dynamic loading version
#include <iostream>
#include <dlfcn.h>

typedef int (*AddFunc)(int, int);

int main() {
    void* handle = dlopen("./lib/libmathlib.dylib", RTLD_LAZY); // where to go
    if (handle != NULL) {
        AddFunc add = (AddFunc)dlsym(handle, "_ZN8MathLib3addEii");
        if (add != NULL) {
            int result = add(5, 3);
            std::cout << "5 + 3 = " << result << std::endl;
        }
        dlclose(handle);
    }
    return 0;
}

在这种条件下,我们只需要修改动态编译主函数main.cpp的步骤:

Bash
1
g++ main.cpp -ldl -o main

其余保持不变即可