mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 04:22:22 +00:00
fix typo in ch2.6
This commit is contained in:
parent
c834b0631e
commit
90ccf739c0
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user