动态链接和静态链接

cgo项目一般会使用到c/c++源码文件,动态链接库,静态链接库这三种资源文件。 c/c++源码一般放在go的源码目录下,在go代码文件include相应的头文件,go编译的时候会扫描目录下c/c++源码文件进行编译。用c/c++源码编译是最好的情况,但很多情况是没有c/c++源码的。

我们接下来通过编译一个使用了gorocksdb的go程序来了解动态链接库编译和使用静态链接库编译。

新建一个项目rocksdbtest,目录文件如下:

$ tree
.
├── go.mod
├── go.sum
└── main.go

main.go内容:

package main

import "C"
import (
	"fmt"
	rockdb "github.com/tecbot/gorocksdb"
	"log"
)

const (
	FORMAT = "key%08d"
)

var (
	wo   = rockdb.NewDefaultWriteOptions()
	ro   = rockdb.NewDefaultReadOptions()
)

func main() {
	dir := "/tmp/gorocksdb-TestDBMultiGet"
	opts := rockdb.NewDefaultOptions()
	opts.SetCreateIfMissing(true)
	db, err := rockdb.OpenDb(opts, dir)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
	db.Put(wo, []byte(fmt.Sprintf(FORMAT, 1)), []byte("test data"))
	a, err := db.Get(ro, []byte(fmt.Sprintf(FORMAT, 1)))
	defer a.Free()
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(a.Data()))
}

要使用gorocksdb首先需要编译rocksdb,我们参照rocksdb INSTALL中的描述的安装方式安装。我们以ubuntu安装为例:

先安装依赖

  • gcc/g++至少要4.8以上以支持c++11
  • 安装gflagssudo apt-get install libgflags-dev
  • 安装snappysudo apt-get install libsnappy-dev
  • 安装zlib sudo apt-get install zlib1g-dev
  • 安装bzip2 sudo apt-get install libbz2-dev
  • 安装lz4 sudo apt-get install liblz4-dev
  • 安装lz4 sudo apt-get install libzstd-dev

编译rocksdb

$ git clone https://github.com/facebook/rocksdb.git
$ cd rocksdb
$ git checkout v5.18.3  #切换到稳定分支版本
$ make static_lib       #编译的静态链接库    
$ make shared_lib       #编译的动态链接库
$ ll librocksdb*
-rw-r--r-- 1 wida wida 418296002 11月 21 14:31 librocksdb.a  #这是编译后的静态链接库
lrwxrwxrwx 1 wida wida        20 7月  29 09:35 librocksdb.so -> librocksdb.so.5.18.3
lrwxrwxrwx 1 wida wida        20 7月  29 09:35 librocksdb.so.5 -> librocksdb.so.5.18.3
lrwxrwxrwx 1 wida wida        20 7月  29 09:35 librocksdb.so.5.18 -> librocksdb.so.5.18.3
-rwxr-xr-x 1 wida wida 121896448 7月  29 09:35 librocksdb.so.5.18.3 #这是编译后的动态链接库

使用动态链接库

我们先编译使用动态链接库的版本。

$ cd rocksdbtest
$ CGO_CFLAGS="-I/home/wida/cppworkspace/rocksdb/include" CGO_LDFLAGS="-L/home/wida/cppworkspace/rocksdb -lrocksdb -lstdc++ -lm -lz -lbz2 -lsnappy -llz4 -lzstd" go build
$ ll -h rocksdbtest   #看下编译后的程序大小
-rwxr-xr-x 1 wida wida 2.3M 11月 21 14:34 rocksdbtest
$  ./rocksdbtest 
test data
$ ldd rocksdbtest    #看依赖的动态库
        linux-vdso.so.1 (0x00007ffcef59b000)
        librocksdb.so.5.18 => /usr/local/lib/librocksdb.so.5.18 (0x00007f006c187000)
        libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f006be06000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f006ba73000)
        libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f006b855000)
        libbz2.so.1.0 => /lib/x86_64-linux-gnu/libbz2.so.1.0 (0x00007f006b645000)
        libsnappy.so.1 => /usr/lib/x86_64-linux-gnu/libsnappy.so.1 (0x00007f006b43d000)
        liblz4.so.1 => /usr/lib/x86_64-linux-gnu/liblz4.so.1 (0x00007f006b220000)
        libzstd.so.1 => /usr/lib/x86_64-linux-gnu/libzstd.so.1 (0x00007f006af9c000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f006ad7e000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f006ab7a000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f006a7c0000)
        librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f006a5b8000)
        libgflags.so.2.2 => /usr/lib/x86_64-linux-gnu/libgflags.so.2.2 (0x00007f006a393000)
        libnuma.so.1 => /usr/lib/x86_64-linux-gnu/libnuma.so.1 (0x00007f006a188000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f0069f70000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f006c925000)

从上面的编译结果来看,使用动态链接库编译出来的可执行文件比较小,但是依赖的动态库比较多,这样子带发布程序的时候你需要同时发布依赖的动态库。而且动态库还要保证版本兼容性,碰到不兼容的情况处理起来就相当棘手。

使用静态链接库

使用静态链接库编译也是比较理想的cgo编译选项。编译后的可执行文件也只有一个,不会依赖其他动态链接库,这样对程序部署或者容器化非常有帮助。

使用全静态编译:

$ cd rocksdbtest
$ CGO_CFLAGS="-I/home/wida/cppworkspace/rocksdb/include" CGO_LDFLAGS="-L/home/wida/cppworkspace/rocksdb -lrocksdb -lstdc++ -lm -lz -lbz2 -lsnappy -llz4 -lzstd -linkmode "external" -extldflags "-static"" go build
$ ll -h rocksdbtest 
-rwxr-xr-x 1 wida wida 42M 11月 21 14:44 rocksdbtest #这个演示的程序比较小,有时候编译出来可能有上百M
$  ./rocksdbtest 
test data
$ ldd rocksdbtest  #看依赖的动态库
        不是动态可执行文件

编译后的可执行文件只有一个rocksdbtest,ldd命令看它的依赖发现不是动态可执行文件也就是说没有依赖动态链接库。

总结

本小节介绍了cgo程序编译使用动态链接库和静态链接库的使用。最优先的情况是使用源码编译,静态链接库次之,最后是使用动态链接库。

参考资料