1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-23 20:02:22 +00:00

Merge pull request #52 from lewgun/patch-16

fix typo in ch2.6
This commit is contained in:
chai2010 2018-01-15 14:49:12 +08:00 committed by GitHub
commit acec0483ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,10 +1,10 @@
# 2.6. 静态库和动态库
CGO在使用C/C++资源的时候一般有三种形式:直接使用源码;链接静态库;链接动态库。直接使用源码就是在`import "C"`之前的注释部分包含C代码或者在当前包中包含C/C++源文件。链接静态库和动态库的方式比较类似都是通过在LDFLAGS选项指定要链接的库方式链接。本节我们主要关注CGO中如何处理静态库和动态库相关的问题。
CGO在使用C/C++资源的时候一般有三种形式:直接使用源码;链接静态库;链接动态库。直接使用源码就是在`import "C"`之前的注释部分包含C代码或者在当前包中包含C/C++源文件。链接静态库和动态库的方式比较类似都是通过在LDFLAGS选项指定要链接的库方式链接。本节我们主要关注在CGO中如何使用静态库和动态库相关的问题。
## 使用C静态库
如果CGO中引入的C/C++资源有代码而且代码规模也比较小,直接使用源码是最理想的方式。很多时候我们并没有源代码或者从C/C++源代码的构建过程异常复杂这种时候使用C静态库也是一个不错的选择。静态库因为是静态链接最终的目标程序并不会产生额外的运行时依赖而且也不会出现动态库特有的跨运行时资源管理的错误。不过静态库对链接阶段会有一定要求:静态库一般包含了全部的代码,里面会有大量的符号,如果不同静态库之间出现符号冲突会导致链接的失败。
如果CGO中引入的C/C++资源有代码而且代码规模也比较小,直接使用源码是最理想的方式但很多时候我们并没有源代码或者从C/C++源代码开始构建的过程异常复杂这种时候使用C静态库也是一个不错的选择。静态库因为是静态链接最终的目标程序并不会产生额外的运行时依赖也不会出现动态库特有的跨运行时资源管理的错误。不过静态库对链接阶段会有一定要求静态库一般包含了全部的代码里面会有大量的符号如果不同静态库之间出现符号冲突会导致链接的失败。
我们先用纯C语言构造一个简单的静态库。我们要构造的静态库名叫number库中只有一个number_add_mod函数用于表示数论中的模加法运算。number库的文件都在number目录下。
@ -26,7 +26,7 @@ int number_add_mod(int a, int b, int mod) {
因为CGO使用的是GCC命令来编译和链接C和Go桥接的代码。因此静态库也必须是GCC兼容的格式。
通过以下命令可以生成一个libnumber.a的静态库
通过以下命令可以生成一个libnumber.a的静态库
```
$ cd ./number
@ -53,11 +53,11 @@ func main() {
}
```
其中有两个#cgo命令分别是编译和链接参数。CFLAGS通过`-I./number`将number库对应头文件所在的目录加入头文件检索路径。LDFLAGS通过`-L${SRCDIR}/number`将编译后number静态库所在目录加为链接库检索路径`-lnumber`表示链接libnumber.a静态库。需要注意的是在链接部分的检索路径不能使用相对路径这是由于C/C++代码的链接程序所限制我们必须通过cgo特有的`${SRCDIR}`变量将源文件对应的当前目录路径展开为绝对路径因此在windows平台中绝对路径不能有空白符号
其中有两个#cgo命令分别是编译和链接参数。CFLAGS通过`-I./number`将number库对应头文件所在的目录加入头文件检索路径。LDFLAGS通过`-L${SRCDIR}/number`将编译后number静态库所在目录加为链接库检索路径`-lnumber`表示链接libnumber.a静态库。需要注意的是在链接部分的检索路径不能使用相对路径C/C++代码的链接程序所限制我们必须通过cgo特有的`${SRCDIR}`变量将源文件对应的当前目录路径展开为绝对路径因此在windows平台中绝对路径不能有空白符号
因为我们有number库的全部代码我们可以用go generate工具来生成静态库或者是通过Makefile来构建静态库。因此发布CGO源码包时我们并不需要提前构建C静态库。
因为我们有number库的全部代码所以我们可以用go generate工具来生成静态库或者是通过Makefile来构建静态库。因此发布CGO源码包时我们并不需要提前构建C静态库。
因为多了一个静态库的构建步骤这种使用了自定义静态库并已经包含了静态库全部代码的Go包无法直接用go get安装。不过我们依然可以通过go get下载然后用go generate触发静态库构建最后才是go install安装来完成。
因为多了一个静态库的构建步骤这种使用了自定义静态库并已经包含了静态库全部代码的Go包无法直接用go get安装。不过我们依然可以通过go get下载然后用go generate触发静态库构建最后才是go install来完成安装
为了支持go get命令直接下载并安装我们C语言的`#include`语法可以将number库的源文件链接到当前的包。
@ -71,11 +71,11 @@ func main() {
如果使用的是第三方的静态库,我们需要先下载安装静态库到合适的位置。然后在#cgo命令中通过CFLAGS和LDFLAGS来指定头文件和库的位置。对于不同的操作系统甚至同一种操作系统的不同版本来说,这些库的安装路径可能都是不同的,那么如何在代码中指定这些可能变化的参数呢?
在Linux环境一个pkg-config命令可以查询要使用某个静态库或动态库时的编译和链接参数。我们可以在#cgo命令中直接使用pkg-config命令来生成编译和链接参数。而且还可以通过PKG_CONFIG环境变量订制pkg-config命令。因为不同的操作系统对pkg-config命令的支持不尽相同通过该方式很难兼容不同的操作系统下的构建参数。不过对于Linux等特定的系统pkg-config命令确实可以简化构建参数的管理。关于pkg-config的使用细节我们不深入展开,大家可以自行参考相关文档。
在Linux环境有一个pkg-config命令可以查询要使用某个静态库或动态库时的编译和链接参数。我们可以在#cgo命令中直接使用pkg-config命令来生成编译和链接参数。而且还可以通过PKG_CONFIG环境变量订制pkg-config命令。因为不同的操作系统对pkg-config命令的支持不尽相同通过该方式很难兼容不同的操作系统下的构建参数。不过对于Linux等特定的系统pkg-config命令确实可以简化构建参数的管理。关于pkg-config的使用细节在此我们不深入展开,大家可以自行参考相关文档。
## 使用C动态库
动态库出现的初衷是对于相同的库,多个进程可以共享一个动态库以节省内存和磁盘资源。但是在磁盘和内存已经白菜价的今天,动态库能节省的空间已经微不足道了,那么动态库还有哪些存在的价值呢从库开发角度来说动态库可以隔离不同动态库之间的关系减少链接时出现符号冲突的风险。而且对于windows等平台动态库是跨越VC和GCC不编译器平台的唯一的可行方式。
动态库出现的初衷是对于相同的库,多个进程可以共享一个,以节省内存和磁盘资源。但是在磁盘和内存已经白菜价的今天,这两个作用已经显得微不足道了,那么除此之外动态库还有哪些存在的价值呢从库开发角度来说动态库可以隔离不同动态库之间的关系减少链接时出现符号冲突的风险。而且对于windows等平台动态库是跨越VC和GCC不编译器平台的唯一的可行方式。
对于CGO来说使用动态库和静态库是一样的因为动态库也必须要有一个小的静态导出库用于链接动态库Linux下可以直接链接so文件但是在Windows下必须为dll创建一个`.a`文件用于链接。我们还是以前面的number库为例来说明如何以动态库方式使用。
@ -118,7 +118,7 @@ number_add_mod
其中第一行的LIBRARY指明动态库的文件名然后的EXPORTS语句之后是要导出的符号名列表。
现在我们可以用以下命令来创建动态库需要进入VC对的x64命令行环境
现在我们可以用以下命令来创建动态库需要进入VC对的x64命令行环境
```
$ cl /c number.c
@ -139,7 +139,7 @@ $ dlltool -dllname number.dll --def number.def --output-lib libnumber.a
## 导出C静态库
CGO不仅可以使用C静态库也可以将Go实现的函数导出为C静态库。我们现在用Go实现前面的number库的加法函数。
CGO不仅可以使用C静态库也可以将Go实现的函数导出为C静态库。我们现在用Go实现前面的number库的加法函数。
创建number.go内容如下
@ -180,7 +180,7 @@ extern int number_add_mod(int p0, int p1, int p2);
其中`extern "C"`部分的语法是为了同时适配C和C++两种语言。核心内容是声明了要导出的number_add_mod函数。
然后我们创建一个`_test_main.c`的C文件用于测试生成的C静态库用下划线作为前著名是让go build构建C静态库时忽略这个文件
然后我们创建一个`_test_main.c`的C文件用于测试生成的C静态库用下划线作为前缀名是让为了让go build构建C静态库时忽略这个文件
```c
#include "number.h"
@ -241,11 +241,11 @@ $ ./a.out
Requires exactly one main package to be listed.
```
文档说明导出的C函数必须是在main包导出然后才能在生成的头文件包含声明的语句。但是很多时候我们可能更希望将不类型的导出函数组织到不同的Go包中然后统一导出为一个静态库或动态库。
文档说明导出的C函数必须是在main包导出然后才能在生成的头文件包含声明的语句。但是很多时候我们可能更希望将不类型的导出函数组织到不同的Go包中然后统一导出为一个静态库或动态库。
要实现从是从非main包导出C函数或者是多个包导出C函数因为只能有一个main包我们需要自己提供导出C函数对应的头文件因为CGO无法为非main包的导出函数生成头文件
假设我们先创建一个number子包用于提供加法函数:
假设我们先创建一个number子包用于提供加法函数:
```go
package number