【Go语言】动态库和静态库详解
前言
首先,引用七牛云存储团队在《Go语言编程》的观点(第7章,第9节):
就目前而言,以二进制方式分发Go包并不是很现实。由于Go语言对于兼容性控制的非常严格,任何一个版本号的不同都将导致无法链接包。因此,如果你使用Go语言开发了一个库,那么最合适的库分发方式是直接打包源代码包并进行分发,由使用者自行编译。
但是,笔者认为Go语言的编译和C语言一样,有汇编、链接阶段。所以,有必要了解、学习Go语言的动态库和静态库,也有助于以后分发包。
示例工程
一个标准的Golang工程包含3个目录
- bin 放可执行文件
- pkg 放静态库
- src 放源代码
[user@host goprj]$ ls
bin pkg src
[user@host goprj]$ tree
.
├── bin
├── pkg
│ ├── gccgo_linux_amd64
│ └── linux_amd64
└── src
├── calc
│ ├── fibonacci
│ │ ├── fibonacci.go
│ │ └── fibonacci_test.go
│ └── calc.go
└── simplemath
├── add.go
└── sqrt.go
6 directories, 5 files
[user@host goprj]$
pkg
等下我既使用google的gc工具链,也使用gnu的gccgo工具链,所以pkg下面有两个文件夹
- gccgo_linux_amd64 放gccgo生成的静态库
- linux_amd64 放gc生成的静态库
src
代码有2个包和1个可执行文件
- calc 主程序
- calc/fibonacci 提供斐波那契数相关函数
- simplemath 提供基础的数学运算
src/calc/calc.go
package main
import "fmt"
import "calc/fibonacci"
func main() {
var res int64
var err error
res, err = fibonacci.Fibonacci(30)
if err != nil {
panic(err.Error())
} else {
fmt.Println("Result:", res)
}
res, err = fibonacci.Fibonacci_r(30)
if err != nil {
panic(err.Error())
} else {
fmt.Println("Result:", res)
}
}
src/calc/calc/fibonacci/fibonacci.go
package fibonacci
import "simplemath"
import "errors"
func Fibonacci(n int64) (int64, error) {
if n < 1 {
err := errors.New("Should be greater than 0!")
return 0, err
} else if n > 92 {
err := errors.New("Should be less than 93!")
return 0, err
}
var res int64 = 0
var tmp int64 = 1
var idx int64 = 0
for ; idx < n; idx++ {
res = simplemath.Add(res, tmp)
res, tmp = tmp, res
}
return res, nil
}
func Fibonacci_r(n int64) (int64, error) {
if n < 1 {
err := errors.New("Should be greater than 0!")
return 0, err
} else if n < 3 {
return 1, nil
} else if n > 92 {
err := errors.New("Should be less than 93!")
return 0, err
}
lhs, _ := Fibonacci_r(n - 1)
rhs, _ := Fibonacci_r(n - 2)
ret := simplemath.Add(lhs, rhs)
return ret, nil
}
src/simplemath/add.go
package simplemath
func Add(a int, b int) int {
return a + b
}
src/simplemath/sqrt.go
package simplemath
import "math"
func Sqrt(i int) int {
v := math.Sqrt(float64(i))
return int(v)
}
一键编译
首先,将工程路径添加到环境变量。
然后,用go install
命令一键编译
- gc
go install ./...
- gccgo
go install -compiler gccgo ./...
gc编译静态库
- 用
go tool compile
编译出二进制文件 - 用
go tool pack
打包成静态库
注意事项:
- 同一个包要编译成同一个
.o
文件 - gc静态库的命名规则是
gopackage.a
- 以后使用这个静态库需要用到-I和-L参数
[user@host goprj]$ go tool compile -o simplemath.o src/simplemath/add.go src/simplemath/sqrt.go
[user@host goprj]$ go tool pack c pkg/linux_amd64/simplemath.a simplemath.o
[user@host goprj]$
[user@host goprj]$ go tool compile -o fibonacci.o -I pkg/linux_amd64 src/calc/fibonacci/fibonacci.go
[user@host goprj]$ go tool pack c pkg/linux_amd64/calc/fibonacci.a fibonacci.o
[user@host goprj]$
[user@host goprj]$ go tool compile -o calc.o -I pkg/linux_amd64 src/calc/calc.go
[user@host goprj]$ go tool link -o bin/calc -L pkg/linux_amd64 calc.o
[user@host goprj]$
gccgo编译静态库
- 用
gccgo
编译出二进制文件 - 用
ar
打包成静态库
注意事项:
- 同一个包要编译成同一个
.o
文件 - gccgo静态库的命名规则是
libgopackage.a
[user@host goprj]$ gccgo -c -o simplemath.o src/simplemath/add.go src/simplemath/sqrt.go
[user@host goprj]$ ar rv pkg/gccgo_linux_amd64/libsimplemath.a simplemath.o
ar: creating pkg/gccgo_linux_amd64/libsimplemath.a
a - simplemath.o
[user@host goprj]$
[user@host goprj]$ gccgo -c -o fibonacci.o -I pkg/gccgo_linux_amd64 src/calc/fibonacci/fibonacci.go
[user@host goprj]$ ar rv pkg/gccgo_linux_amd64/calc/libfibonacci.a fibonacci.o
ar: creating pkg/gccgo_linux_amd64/calc/libfibonacci.a
a - fibonacci.o
[user@host goprj]$
[user@host goprj]$ gccgo -c -o calc.o -I pkg/gccgo_linux_amd64 src/calc/calc.go
[user@host goprj]$ gccgo -o bin/calc \
> -L pkg/gccgo_linux_amd64 \
> -L pkg/gccgo_linux_amd64/calc \
> calc.o \
> -lfibonacci -lsimplemath
[user@host goprj]$
gccgo编译动态库
gcc有着丰富的编译经验,还提供了编译go动态库的工具。
最简单的方法是用-shared
编译选项
[user@host goprj]$ gccgo -shared -fPIC \
> -o pkg/gccgo_linux_amd64/libsimplemath.so \
> src/simplemath/add.go \
> src/simplemath/sqrt.go
[user@host goprj]$ gccgo -shared -fPIC \
> -o pkg/gccgo_linux_amd64/calc/libfibonacci.so \
> -I pkg/gccgo_linux_amd64 \
> src/calc/fibonacci/fibonacci.go
[user@host goprj]$
总结
总的来说,go tool非常方便,而手动编译更加灵活。