>[info]原文链接:https://golang.org/cmd/cgo/
[TOC]
Cgo可以创建出能够调用C代码的Go包。
#### 在go命令行中使用cgo
使用cgo书写普通的Go代码,导入一个伪包“C”。Go代码就可以关联到如C.size_t的类型,如C.stdout的变量,或者如C.putchar的方法。
如果“C”包的导入紧接在注释之后,那个这个注释,被称为前导码【preamble】,就会作为编译Go包中的C语言部分的头文件。例如:
~~~
// #include <stdio.h>
// #include <errno.h>
import "C"
~~~
前导码【preamble】可以包含任何C语言代码,包括方法和变量声明及定义。这些代码后续可能会由Go代码引用,就像它们定义在了名为“C”的Go包中。所有在前导码中声明的名字都可能被使用,即使它们以小写字母开头。有一个例外:前导码中的static变量不能被Go代码引用;而static方法则可以被Go引用。
可以查看`$GOROOT/misc/cgo/stdio`和`$GOROOT/misc/cgo/gmp`中的例子。也可以在https://golang.org/doc/articles/c_go_cgo.html 中了解使用cgo的介绍。
`CFLAGS`, `CPPFLAGS`, `CXXFLAGS`, `FFLAGS`,和`LDFLAGS`可以在注释区域中,使用#cgo伪指令来定义,以改变C,C++,或Fortran编译器的行为。被多个指令定义的值会被联系在一起。指令还可以包含一个构建约束的列表,将其对系统的影响限制为满足其中一个约束条件。可以查看https://golang.org/pkg/go/build/#hdr-Build_Constraints 了解约束语法的详情。例如:
~~~
// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo amd64 386 CFLAGS: -DX86=1
// #cgo LDFLAGS: -lpng
// #include <png.h>
import "C"
~~~
或者,`CPPFLAGS`和`LDFLAGS`也可通过`pkg-config`工具获取,使用`#cgo pkg-config:`指令,后面跟上包名即可。比如:
~~~
// #cgo pkg-config: png cairo
// #include <png.h>
import "C"
~~~
默认的`pkg-config`工具可以通过设置`PKG_CONFIG`环境变量来改变。
当编译时,`CGO_CFLAGS`,`CGO_CPPFLAGS`,`CGO_CXXFLAGS`,`CGO_FFLAGS`和`CGO_LDFLAGS`这些环境变量都会从指令中提取出来,并加入到flags中。包特定的flags需要使用指令来设置,而不是通过环境变量,所以这些构建可以在未更改的环境中也能正常运行。
一个包中的所有cgo CPPFLAGS和CFLAGS指令会被连接起来,并用来编译包中的C文件。一个包中的所有CPPFLAGS和CXXFLAGS指令会被连接起来,并用来编译包中的C++文件。一个包中的所有CPPFLAGS和FFLAGS指令会被连接起来,并用来编译包中的Fortran文件。在这个程序中任何包内的所有LDFLAGS指令会被连接起来,并在链接时使用。所有的pkg-config指令会被连接起来,并同时发送给pkg-config,以添加到每个适当的命令行标志集中。
当cgo指令被转化【parse】时,任何出现${SRCDIR}字符串的地方,都会被替换为包含源文件的目录的绝对路径。这就允许预编译的静态库包含在包目录中,并能够正确的链接。例如,如果foo包在/go/src/foo目录下:
~~~
// #cgo LDFLAGS: -L${SRCDIR}/libs -lfoo
~~~
就会被展开为:
~~~
// #cgo LDFLAGS: -L/go/src/foo/libs -lfoo
~~~
>[info]心得: `// #cgo LDFLAGS:` 可用来链接静态库。`-L`指定静态库所在目录,`-l`指定静态库文件名,注意静态库文件名必须有`lib`前缀,但是这里不需要写,比如上面的-lfoo实际找的是libfoo.a文件
当Go tool发现一个或多个Go文件使用了特殊导入“C”包,它就会在目录中寻找其它非Go文件,并将其编译为Go包的一部分。任何.c, .s, 或者.S文件会被C编译器编译。任何.cc, .cpp, 或者.cxx文件会被C++编译器编译。任何.f, .F, .for或者.f90文件会被fortran编译器编译。任何.h, .hh, .hpp或者.hxx文件不会被分开编译,但是,如果这些头文件修改了,那么C和C++文件就会被重新编译。默认的C和C++编译器有可能会分别被CC和CXX环境变量改变,那些环境变量可能包含命令行选项。
cgo tool对于本地构建默认是启用的。而在交叉编译时默认是禁用的。你可以在运行go tool时,通过设置CGO_ENABLED环境变量来控制它:设为1表示启用cgo, 设为0关闭它。如果cgo启用,go tool将会设置构建约束“cgo”。
在交叉编译时,你必须为cgo指定一个C语言交叉编译器。你可以在使用make.bash构建工具链时,设置CC_FOR_TARGET环境变量来指定,或者是在你运行go tool时设置CC环境变量来指定。与此相似的还有作用于C++代码的CXX_FOR_TARGET和CXX环境变量。
#### Go引用C代码
在Go文件中,C的结构体属性名是Go的关键字,可以加上下划线前缀来访问它们:如果x指向一个拥有属性名"type"的C结构体,那么就可以用`x._type`来访问这个属性。对于那些不能在Go中表达的C结构体字段(例如位字段或者未对齐的数据),会在Go结构体中被省略,而替换为适当的填充,以到达下一个属性,或者结构体的结尾。
标准C数字类型,可以通过如下名字访问:
~~~
C.char,
C.schar(signed char),
C.uchar(unsigned char),
C.short,
C.ushort(unsigned short),
C.int,
C.uint(unsigned int),
C.long,
C.ulong(unsigned long),
C.longlong(long long),
C.ulonglong(unsigned long long),
C.float,
C.double,
C.complexfloat(complex float),
C.complexdouble(complex double)
~~~
C的`void*`类型由Go的`unsafe.Pointer`表示。C的`__int128_t`和`__uint128_t`由[16]byte表示。
如果想直接访问一个结构体,联合体,或者枚举类型,请加上如下前缀:`struct_`, `union_`, 或者`enum_`。比如`C.struct_stat`。
>[warning]心得:但是如果C结构体用了typedef struct设置了别名,则就不需要加上前缀,可以直接C.alias访问该类型。
任何一个C类型T的尺寸大小,都可以通过`C.sizeof_T`获取,比如`C.sizeof_struct_stat`。
因为在通常情况下Go并不支持C的联合体类型【union type】,所以C的联合体类型,由一个等长的Go byte数组来表示。
Go结构体不能嵌入具有C类型的字段。
Go代码不能引用发生在非空C结构体末尾的零尺寸字段。如果要获取这个字段的地址(这也是对于零大小字段唯一能做的操作),你必须传入结构体的地址,并加上结构体的大小,才能算出这个零大小字段的地址。
cgo会将C类型转换为对应的,非导出的的Go类型。因为转换是非导出的,一个Go包就不应该在它的导出API中暴露C的类型:在一个Go包中使用的C类型,不同于在其它包中使用的同样C类型。
可以在多个赋值语境中,调用任何C函数(甚至是void函数),来获取返回值(如果有的话),以及C errno变量作为Go error(如果方法返回void,则使用 _ 来跳过返回值)。例如:
~~~
n, err = C.sqrt(-1)
_, err := C.voidFunc()
var n, err = C.sqrt(1)
~~~
调用C的方法指针目前还不支持,然而你可以声明Go变量来引用C的方法指针,然后在Go和C之间来回传递它。C代码可以调用来自Go的方法指针。例如:
~~~
package main
// typedef int (*intFunc) ();
//
// int
// bridge_int_func(intFunc f)
// {
// return f();
// }
//
// int fortytwo()
// {
// return 42;
// }
import "C"
import "fmt"
func main() {
f := C.intFunc(C.fortytwo)
fmt.Println(int(C.bridge_int_func(f)))
// Output: 42
}
~~~
在C中,一个方法参数被写为一个固定大小的数组,实际上需要的是一个指向数组第一个元素的指针。C编译器很清楚这个调用习惯,并相应的调整这个调用,但是Go不能这样做。在Go中,你必须显式的传入指向第一个元素的指针:C.f(&C.x[0])。
在Go和C类型之间,通过拷贝数据,还有一些特殊的方法转换。用Go伪代码定义如下:
~~~
// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CString(string) *C.char
// Go []byte slice to C array
// The C array is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CBytes([]byte) unsafe.Pointer
// C string to Go string
func C.GoString(*C.char) string
// C data with explicit length to Go string
func C.GoStringN(*C.char, C.int) string
// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte
~~~
作为一个特殊例子,C.malloc并不是直接调用C的库函数malloc,而是调用一个Go的帮助函数【helper function】,该函数包装了C的库函数malloc,并且保证不会返回nil。如果C的malloc指示内存溢出,这个帮助函数会崩溃掉程序,就像Go自己运行时内存溢出一样。因为C.malloc从不失败,所以它不会返回包含errno的2值格式。