要解决的问题

  • 复用其他人的工作
  • 不需要运行时支持动态链接库
  • 不共享源代码的前提下复用

解决方案

静态链接库 linker 在构建的时期,把 object 文件和 static library 预先合并成一个完整的 executable。 object 与 static library 彼此知道自己的链接关系。

构成

构成 解释
object 第一方开发的可执行代码
static library 第三方开发的可执行代码
static library linker 把 object 和 static library 链接成 executable
executable 给 executor 执行用的可执行文件

衍生的问题

解决方案案例

browserify

browserify 把多个 CJS 格式的静态链接库链接成一个完整自包含的executable。

main.js

// 引用的 uniq 包会被链接到最终的 executable 里
var unique = require('uniq');
var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];
console.log(unique(data));

build.sh

browserify main.js -o bundle.js
构成 解释
object main.js
static library uniq
static library linker browserify
executable bundle.js

webpack

webpack 是一个更全面的静态链接库的链接器,是 browserify 的改进。

src/main.js

// 引用的 uniq 包会被链接到最终的 executable 里
var unique = require('uniq');
var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];
console.log(unique(data));

build.sh

webpack --mode development --entry ./src/main.js --output ./dist/bundle.js
构成 解释
object src/main.js
static library uniq
static library linker webpack
executable dist/bundle.js

rollup

rollup 的出发点是改进静态链接库的格式。 采用 ES6 Module 的导入导出语法,从而实现 tree-shaking 来减少静态链接后的文件尺寸。

src/main.js

// 引用的 uniq 包会被链接到最终的 executable 里
import unique from 'uniq'
var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];
console.log(unique(data));

rollup.config.js

import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';

export default {
    input: 'src/main.js',
    output: {
        file: 'dist/bundle.js',
        format: 'iife', // immediately-invoked function expression — suitable for <script> tags
        sourcemap: true
    },
    plugins: [
        resolve(), // tells Rollup how to find date-fns in node_modules
        commonjs(), // converts date-fns to ES modules
    ]
};

build.sh

rollup -c
构成 解释
object src/main.js
static library uniq
static library linker rollup
executable dist/bundle.js

gcc

ar 把多个 linux 下用 c 编译出来的 .o 文件,合并成 .a 静态链接文件

gcc 把 .o 文件和 .a 文件静态链接成 executable

file1.c

int hello() {
    return 1;
}

file2.c

int world() {
    return 2;
}

build1.sh

gcc -c file1.c # 创建 file1.o 提供 hello
gcc -c file2.c # 创建 file2.o 提供 world
ar -r lib12.a *.o # 把两个object文件合并成一个静态链接库

演示静态链接库如何产生出来

main.c

int hello();
int world();

int main() {
    return hello() + world();
}

build2.sh

gcc -c main.c # 创建 main.o,引用 hello / world 这两个符号
gcc -o main main.o lib12.a # 把静态链接库链接成 executable

./main # 返回值应该是 1 + 2 = 3
echo $?

演示静态链接库如何被使用

构成 解释
object file1.o file2.o main.o
static library lib12.a
static library linker gcc
executable ./main

rustc

rustc 可以编译 .a 文件,也可以把多个 .a 文件链接成可执行文件

file1.rs

#![crate_type = "staticlib"]

#[no_mangle]
pub fn hello() -> i32 {
    return 1;
}

file2.rs

#![crate_type = "staticlib"]

#[no_mangle]
pub fn world() -> i32 {
    return 2;
}

build1.sh

rustc file1.rs # 创建 libfile1.a
rustc file2.rs # 创建 libfile2.a

演示静态链接库如何产生出来

main.rs

#[link(name = "file1")]
extern "C" {
    fn hello() -> i32;
}

#[link(name = "file2")]
extern "C" {
    fn world() -> i32;
}

fn main() {
    let r = unsafe { hello() + world() };
    ::std::process::exit(r)
}

build2.sh

rustc main.rs -L . # 链接当前目录下的 libfile1.a libfile2.a

./main # 返回值应该是 1 + 2 = 3
echo $?

演示静态链接库如何被使用

构成 解释
object 没有
static library libfile1.a libfile2.a
static library linker rustc
executable ./main