# 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包之间的链接参数的冲突）。

<!-- pkg-config 自定义的命令 -->

## 同时打包C源码

## 打包巨量C源码的问题

TODO

<!--
可移植的cgo

小的cgo包直接内置c/c++代码

1. copy到本目录

2. 创建 include 文件

很大的c包，全部内置会导致构建复杂化

分2步，1 go generate 进行构建
2 lib 包含

-->
