Golang | cmd - go build 构建 C 的静态 + 动态链接库、Go 的动态链接库
Contents
- Go 中如何构建 C 的静态链接库
- Go 中如何构建 C 的动态链接库
- Go 中如何构建 Go 的动态链接库
c-archive
这里构建的是供 C 程序调用的库。更准确一些的说,这里是把 Go 程序构建为 archive(.a)
文件,这样 C 类的程序可以静态链接 .a 文件,并调用其中的代码。
Example Project:
|
|
然后我们构建这个工程:
1
|
go build -x -v -buildmode=c-archive . |
.
表示在当前路径下编译该工程,编译生成的文件名将以该工程的包名命名:即上面命令执行后,生成如下文件: - 一个是静态库文件: c-archive.a - 一个是 C 的头文件: c-archive.h
1 2 |
c-archive.a: current ar archive random library c-archive.h: c program text, ASCII text |
在所生成的 c-archive.h 的头文件中,我们可以看到 Go 的 Hello() 函数的定义:
|
|
然后我们在 c-archive.c 中引入头文件 c-archive.h, 并使用 Go 编译的静态库:
|
|
然后,我们构建 C 程序:
1
|
gcc c-archive.a c-archive.c -o hello.exe |
踩坑一:gcc 构建 C 程序失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
> Issue: 表面意思就是找不到 Hello 这个函数 > > $ gcc c-archive.a c-archive.c -lWinMM -lntdll -lWS2_32 - > o hello.exe > C:\Users\zhe\AppData\Local\Temp\ccEfOZGq.o:c-archive.c:(.text+0xe): undefined reference to `Hello' > collect2.exe: error: ld returned 1 exit status > > Reason: > >>> undefined reference to `Hello' 错误是发生在链接过程中 > >>> GCC在链接时对依赖库的顺序是敏感的,被依赖的库必须放在后面。GCC链接规定,链接时,若A和B同时需要链接,不论A/B是目标文件还是库文件,若A中引用了B的符号,例如函数或者全局变量,则在链接时,必须将A写在B前面;因为,链接时从左向右搜索外部符号。 > >>> c-archive.a 和 c-archive.c 的顺序不对 > > Resolve: 改变 c-archive.a 和 c-archive.c 的顺序 > >>> gcc c-archive.a c-archive.c -lWinMM -lntdll -lWS2_32 - > o hello.exe > ``` 最后,执行 ./hello.exe ./hello.exe hello, world! # c-shared 和前面例子不同的是,这将用 Go 代码创建一个动态链接库(Unix: .so & Win: .dll), 然后用 C 语言程序动态加载执行。 Go 和 C 语言的代码和上面例子是一样的,但是构建过程不同: ```bash $ go build -buildmode=c-shared -o c-shared.so main.go
这里我们使用了 c-shared
以构建 C 的动态链接库。
注:需要注意的是,这里明确指定了 -o hello.so,这里我和演讲者不同,如果不指定输出文件名,那么默认会使用 hello 作为文件名,导致后续的操作找不到 hello.so 文件。
这次也生成了两个文件,一个是 hello.so,一个是 hello.h:
|
|
然后,编译对应的 C 程序:
|
|
对比 c-archive 和 c-shared 例子中的 hello.exe 二进制可执行文件的大小,就要发现 c-shared 例子中的 hello.exe 要小很多:
|
|
这是因为前者,将 Go 的代码静态编译进了 C 的程序中;而后者,则是动态链接,C 的可执行文件内不包含我们写的 Go 的代码,所有这部分的函数都在动态链接库中 hello.so 中了:
|
|
因此,执行的时候我们除了需要 hello.exe 这个可执行文件外,还需要 hello.so 这个动态链接库。如果默认的 LD_LIBRARY_PATH
包含了当前目录,并且 hello.so 就在当前目录,则可以直接:
|
|
否则,如果提示找不到 hello.so, 如:
|
|
那,可以手动指定 LD_LIBRARY_PATH
变量,告诉操作系统到哪里找动态链接库:
|
|
为什么会需要动态链接库
从开始使用 Go 我们就反反复复的听到人说 Go 的静态链接如何方便,既然如此,那么我们为什么需要动态链接?
因为动态链接在运行时,且在需要的时候,由程序决定加载,也可以在不需要的时候卸载,这样可以节约内存资源。
最后,附上编译所用的 Makefile:
|
|
shared
shared 模式和 c-shared 模式有点相似,都是构建一个动态链接库,以便在运行时加载。所不同的是 shared 并非构建 C 语言的动态链接库,而是专门为 Go 可执行文件构建动态链接库。
这次还是 hello.go, 不过稍有不同:
|
|
这里就是独立的一个文件,一个 main(),执行后打印 Hello, World。我们可以像以前一样用 exe 模式构建,然后执行。不过这次我们用一种不同的方式构建。
1 2 |
go install -buildmode=shared std go build -linkshared hello.go |
这里,我们首先把标准库 std
构建并安装到 $GOPATH/pkg
下,然后使用 -linkshared
标记来构建 hello.go。
执行结果和前面一样,但是如果仔细观察生成的文件,就会发先和前面很不同。
1 2 |
$ ls -l hello -rwxr-xr-x 1 root root 16032 Oct 3 13:27 hello |
可以看到这个 Hello World 程序只有十几KB大小。对于 C 程序员来说,这没啥惊讶的,因为就应该这么大啊。但是对于 Go 程序员来说,这就是很奇怪了,因为一般不都得 7~8MB 么?
其原因就是使用了动态链接库
,所有标准库部分,都用动态链接的办法来调用,构建的二进制可执行文件中只包含了程序部分。C 程序构建的 Hello World 之所以小,也是因为动态链接的原因。
如果我们查阅程序所调用的库就可以看到具体情况:
|
|
如果我们进一步去查看 libstd.so
,就会看到一个巨大的动态链接库,这就是 Go 的标准库:
|
|
当然,要使用这个模式需要很多准备工作,所有的动态链接库都需要在指定的位置,版本都必须兼容等等,所以我们一般不常用这个模式。
Click here to checkout the Repo
See Also
Thanks to the authors 🙂