diff --git a/SUMMARY.md b/SUMMARY.md index 8365580..b4cf6bc 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -15,7 +15,7 @@ * [2.3. CGO编程基础](ch2-cgo/ch2-03-basic.md) * [2.4. CGO内存模型](ch2-cgo/ch2-04-memory.md) * [2.5. C++类包装](ch2-cgo/ch2-05-class.md) - * [2.6. CGO包的可移植性(TODO)](ch2-cgo/ch2-06-go-get-friendly.md) + * [2.6. CGO包的组织(Doing)](ch2-cgo/ch2-06-go-get-friendly.md) * [2.7. Go实现Python模块(TODO)](ch2-cgo/ch2-07-py-module.md) * [2.8. SWIG(TODO)](ch2-cgo/ch2-08-swig.md) * [2.9. 补充说明(TODO)](ch2-cgo/ch2-09-faq.md) diff --git a/ch2-cgo/ch2-06-go-get-friendly.md b/ch2-cgo/ch2-06-go-get-friendly.md index 7f9525a..a98265e 100644 --- a/ch2-cgo/ch2-06-go-get-friendly.md +++ b/ch2-cgo/ch2-06-go-get-friendly.md @@ -1,4 +1,66 @@ -# 2.6. CGO包的可移植性(TODO) +# 2.6. CGO包的组织(Doing) + +凡事都有两面性,CGO虽然是继承了C/C++庞大的生态资源,同时也带来了C/C++语言的诸多问题。第一个遇到的重要问题是如何打包CGO中对用到的C/C++库或代码的依赖。很多用户对Go语言的第一映像是构建和运行都非常快速,甚至可以当中一个脚本语言来使用。但是这种映像的前提是程序要能够正常构建,如何正确构建一个使用了CGO特性的Go语言包对很多用户是一个挑战。 + +## 常见的C和C++编译问题 + +在真实世界中C/C++一般是混合存在的。编译这类C/C++混合代码遇到的问题也是CGO经常需要解决的问题。混合C/C++代码的构建和组织的有两个原则:一是C/C++头文件最小化;二是C/C++编译参数和头文件分离。 + +原本的C语言世界是简单的,头文件也是简单的。当C++引入了函数重载(一个函数名有多个实现)后头文件也变得复杂起来。主要的原因是C++为了支持多个有着不同参数类型的同名函数,在生成目标文件时要对应不同的链接符号。简言之,C++中在编译阶段默认采用和C语言不同的名字修饰规则,同时支持C语言采用的名字修饰规则。 + +因为cgo只支持C语言语法,因此cgo也只能包含C语言的头文件。如果这个C语言头文件没有针对C++做过特殊的处理,那么在被其它的C++代码包含时需要放到`extern "C" { ... }`括号中(这是C++针对兼容C语言而增加的语法)。 + +编译C/C++源文件时,`.c`后缀名的对应是C语言代码,其它的一般是C++代码。C和C++代码编译时有着不同的编译选项,在`#cgo`指令中,CFLAGS对应C语言的编译选项,CPPFLAGS对应C和C++共有的编译选项,CXXFLAGS则对于C++特有的编译参数。C/C++源文件编译后成为一个个目标文件,C/C++的目标文件没有区别,共同使用LDFALAGS表示链接选项。 + +需要说明的是,如果C++代码中使用了C++11或更新的特性,需要在C++编译选项中指明,否则会导致编译错误。 + + + +## 依赖二进制库 + +最简单的CGO程序是没有任何的依赖,仅仅只是通过`import "C"`语句表示启用CGO特性: + +```go +// hello.go +package main + +import "C" + +func main() { + println("hello cgo") +} +``` + +这种程序最为简单,而且又是单个文件,我们可以通过`go run hello.go`命令来直接运行。但是这个程序虽然简单,但是依然会出发cgo命令行工具,依然会触发C语言代码的编译链接的过程。最终我们的得到的可执行程序会依赖一个底层的运行时库。 + +在不同的操作系统下看可执行程序有哪些依赖有着不同的工具。Linux系统是ldd命令,macOS系统是otool命令,Windows下则有带节目的Depends依赖检查工具。下面是这个例子在macOS系统下默认生成的可执行程序的依赖: + +``` +$ go build -o a.out +$ otool -L a.out +a.out: + /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.0.0) +``` + +对于macOS系统,`libSystem.B.dylib`一般会包含C语言运行时库的实现。对于Linux和Windows环境应该有类似的C语言运行时库。当然普通用户很少需要关心这些细节。 + +但是随着Docker等容器技术的流行,如何打包一个最小化Go写的程序容器成了某些用户的追求。Go语言之所以会随着容器技术会大势流行,这是一个非常重要的因素:我们甚至在没有C语言运行时库的环境正常运行Go语言写的程序。下面是在Linux环境打包静态库版本的C语言运行时库: + +``` +$ go build --ldflags '-w -s -extldflags "-static"' hello.go +``` + +如果现在再用Linux的ldd命令查看将不会有任何的依赖,这是一个绝对绿色的程序。 + +更实用的CGO包一般会依赖第三方的C/C++库。如果本地操作系统中已经安装了依赖的第三方库,那么这种情况就和依赖标准的C/C++库差不多了。但是在发布时,需要确保运行程序的目标系统也包含一致的第三方C/C++共享库。 + +需要注意的是,释放采用静态库版本的C/C++运行时库是最终构建用户的选择,每个cgo包本书不应该过多设置最终的构建选项(因为这可能导致不同cgo包之间的链接参数的冲突)。 + + + +## 同时打包C源码 + +## 打包巨量C源码的问题 TODO diff --git a/ch2-cgo/ch2-07-py-module.md b/ch2-cgo/ch2-07-py-module.md index 65abd8c..086c1c3 100644 --- a/ch2-cgo/ch2-07-py-module.md +++ b/ch2-cgo/ch2-07-py-module.md @@ -5,5 +5,7 @@ TODO diff --git a/examples/ch2-06/hello-01/hello.go b/examples/ch2-06/hello-01/hello.go new file mode 100644 index 0000000..c3ad81a --- /dev/null +++ b/examples/ch2-06/hello-01/hello.go @@ -0,0 +1,7 @@ +package main + +import "C" + +func main() { + println("hello cgo") +}