Go命令行—cgo

cgo支持创建调用C代码的Go包。

使用 cgo

编写前需要导入伪包 C。之后就可以引用诸如C.size_t之类的类型,C.stdout之类的变量或C.putchar之类的函数。

如果在 C 包导入之前有注释,则在编译包的C部分时,将该注释(称为前导 preamble)用作头部。

// #include <stdio.h>
// #include <errno.h>
import "C"

前导码可以包含任何C代码,包括函数和变量声明和定义。这些可以从Go代码中引用,就好像它们在包 C 中定义的一样。 所有在前导中声明的命名都可以使用,即使它们是以小写字母开头。例外:前导码中的静态变量可能不会被Go代码引用;静态函数是可以的。

详细示例见 $GOROOT/misc/cgo/stdio and $GOROOT/misc/cgo/gmp,点击查看使用介绍:https://golang.org/doc/articles/c_go_cgo.html.

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环境变量将添加到从这些指令派生的标志中。应该使用指令非修改环境变量来设置特定于包的标志,以便编译可以在未修改的环境中正常工作。

一个包中的所有cgo CPPFLAGS和CFLAGS指令被连接起来,用来编译该包中的C文件。 包中的所有CPPFLAGS和CXXFLAGS指令都被连接起来,用来编译该包中的C ++文件。 包中的所有CPPFLAGS和FFLAGS指令都被连接起来,用来编译该包中的Fortran文件。 程序中任何程序包中的所有LDFLAGS指令都在链接时连接在一起并使用。 所有的pkg-config指令都被连接在一起并同时发送到pkg-config,以添加到每个适当的命令行标志集。

在解析cgo指令时,任何出现的字符串${SRCDIR}都将被包含源文件目录的绝对路径替换。 这使得预编译的静态库被包含在软件包目录中并正确链接。例如,如果包foo位于/go/src/foo目录:

// #cgo LDFLAGS: -L${SRCDIR}/libs -lfoo

将被解析为

// #cgo LDFLAGS: -L/go/src/foo/libs -lfoo

当Go工具发现一个或多个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工具默认情况下启用本机构建。交叉编译默认是禁用的。 可以在运行go工具时设置CGO_ENABLED环境变量来控制:置为1以启用cgo,使用0禁用。 如果启用cgo,则go工具将使用“cgo”的构建约束。

在交叉编译时,必须指定一个供Cgo使用的C交叉编译器。 可以通过在使用make.bash构建工具链时设置CC_FOR_TARGET环境变量, 或者在运行go工具时设置CC环境变量来执行此操作。 CXX_FOR_TARGET和CXX环境变量对于C++工作方式类似。

Go引用C

在Go文件中,如果C的结构字段名称是Go中的关键字,可以通过用下划线作为前缀来访问: 例如x指向名为“type”的字段的C结构,则x._type访问该字段。 在Go中无法表述的C结构字段,比如位字段或未对齐的数据,在Go中被省略,用适当的填充代替,以达到下一个字段或结构的结尾。原文是:C struct fields that cannot be expressed in Go, such as bit fields or misaligned data, are omitted in the Go struct, replaced by appropriate padding to reach the next field or the end of the struct.

以下标准的C数字类型是可用的:C.char,C.schar(signed char),C.uchar(unsigned char),C.short,C.ushort(unsigned short),C.int,C.uint (unsigned short),C.long,C.ulong(unsigned long),C.longlong(long long),C.longlong(unsigned long long),C.float,C.double,C.complexfloat(complex float) C.complexdouble(complex double)。 C的void *是由Go的不安全的指针表示的。 C类型__int128_t和__int128_t由[16]byte类型表示。

要直接访问struct,union或enum类型,请使用struct_,union_或enum_作为前缀,如C.struct_stat。

任何C类型的T的大小都可以用C.sizeof_T,如C.sizeof_struct_stat。

由于Go不支持C的联合类型,所以C的联合类型被表示为具有相同长度的Go字节数组。

Go结构不能嵌入C类型的字段。

Go代码不能引用在非空C结构中出现的零大小的字段。要获得这样一个字段的地址(这是唯一的操作,你可以建立一个零大小的属性),你必须采取结构的地址,并添加结构的大小。

Cgo将C类型转换为等价的未导出的Go类型。由于转换是未导出的,Go包不应在其导出的API中公开C类型:一个Go包中使用的C类型与另一个中使用的C类型不同(即使类型名称相同)。原文是:Cgo translates C types into equivalent unexported Go types. Because the translations are unexported, a Go package should not expose C types in its exported API: a C type used in one Go package is different from the same C type used in another.

任何C函数(甚至是void函数)都可以在多个赋值语境中被调用来检索返回值(如果有)和C errno变量作为错误(如果函数返回void,则使用_来跳过结果值)。

n, err = C.sqrt(-1)
_, err := C.voidFunc()
var n, err = C.sqrt(1)

目前不支持调用C函数指针,但是可以声明包含C函数指针的Go变量,并将它们在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 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,而是调用一个包装C库malloc的Go助手函数,但保证永不返回nil。如果C的malloc指示内存不足,那么助手函数就会使程序崩溃,就像Go本身耗尽内存一样。由于C.malloc无法失败,所以不会返回errno的两个结果形式。

C调用Go

Go函数可以通过以下方式导出供C代码使用:

//export MyFunction
func MyFunction(arg1, arg2 int, arg3 string) int64 {...}

//export MyFunction2
func MyFunction2(arg1, arg2 int, arg3 string) (int64, *C.char) {...}

它们转换成这样的C代码:

extern int64 MyFunction(int arg1, int arg2, GoString arg3);
extern struct MyFunction2_return MyFunction2(int arg1, int arg2, GoString arg3);

以上转换代码,可以在cgo输入文件复制的任何前导后,由_cgo_export.h生成的头文件中找到。具有多个返回值的函数被转换为返回结构——并非所有的Go类型都可以映射到C类型。

在文件中使用// export会对前导码造成限制:因为它被复制到两个不同的C输出文件中,所以它不能包含任何定义,只能包含声明。果一个文件同时包含定义和声明,那么这两个输出文件将产生重复的符号,链接器将失败。为避免这种情况,必须将定义放在其他文件的前导码或C源文件中。

传递指针

Go是带有GC的语言,垃圾收集器需要知道Go内存的每个指针的位置。因此,在Go和C之间传递指针是有限制的。

在本节中,Go指针是指由Go分配的内存指针(例如通过使用&运算符或调用预定义的新函数),并且术语C指针指向由C分配的内存的指针(例如通过调用C.malloc)。一个指针是一个Go指针还是一个C指针是一个动态属性,由内存如何分配决定;它与指针的类型无关。

Go代码可以将Go指针传递给C,只要它指向的Go内存不包含任何Go指针。C代码必须保留这个属性:它不能在Go内存中存储任何Go指针,即使是暂时的。

将指针传递给结构中的字段时,所涉及的Go内存是该字段占用的内存,而不是整个结构。将指针传递给数组或片段中的元素时,所涉及的Go内存是整个数组或整个片段的后备数组。

C代码在调用返回后可能不保留Go指针的副本。

由C代码调用的Go函数可能不会返回Go指针。 由C代码调用的Go函数可能将C指针作为参数,并可能通过这些指针存储非指针或C指针数据,但是它可能不会将Go指针存储在由C指针指向的内存中。 C代码调用的Go函数可能会将Go指针作为参数,但它必须保留指向的Go内存不包含任何Go指针的属性。

Go代码可能不会在C内存中存储Go指针。C代码可能会在C内存中存储Go指针,这取决于上面的规则:当C函数返回时,它必须停止存储Go指针。

这些规则在运行时动态检查。 检查由GODEBUG环境变量的cgocheck设置控制。 默认设置是GODEBUG=cgocheck= 1,它实现合理的动态检查且开销较小。这些检查可能完全禁用使用GODEBUG=cgocheck=0。 通过GODEBUG=cgocheck=2可以完成指针处理的完整检查,但运行时间有一定的代价。

通过使用 unsafe 包可以禁止这种强制执行,当然没有什么能阻止C代码做任何事情。但是,违反这些规则的程序可能会以意想不到的方式失败。

直接使用 cgo

go tool cgo [cgo options] [-- compiler options] gofiles...

cgo将指定的输入Go源文件转换为多个输出Go和C的源文件。

当调用C编译器来编译包的C部分时,编译器选项是通过未解释的方式传递的。

当直接运行cgo时,有以下选项:

-dynimport file
	写入由文件导入的符号列表。写入-dynout参数或标准输出。编译cgo包时会执行go build。
-dynout file
	将 -dynimport 输出到文件。
-dynpackage package
	为-dynimport输出设置Go包。
-dynlinker
	将动态链接器写入-dynimport输出的一部分。
-godefs
	用Go语法替换C包名。用于在系统调用软件包时生成文件以进行引导。
-srcdir directory
    在命令行中列出目录下的Go输出文件。
-objdir directory
	将所有生成的文件放在目录中。
-importpath string
	Go包的导入路径。可选; 用于在生成的文件中更好的注释。
-exportheader file
	如果有导出的函数,写入生成导出声明到文件中。
	C代码可以使用#include来查看声明。.
-gccgo
	为gccgo编译器生成输出,而不是 gc编译器。
-gccgoprefix prefix
	用于gccgo的-fgo-prefix选项。
-gccgopkgpath path
	用于gccgo的-fgo-pkgpath选项。
-import_runtime_cgo
	设置后(默认)会在生成的输出中导入 runtime/cgo。
-import_syscall
	设置后(默认)会在生成的输出中导入 syscall。
-debug-define
	调试选项。打印#defines。
-debug-gcc
	调试选项。跟踪C编译器的执行和输出。