Cgo允许在Go包中调用C代码。如果Go代码含有特殊的cgo语法,可以通过cgo生成相应的Go和C文件,它们可以被编译到一个Go包中。

以一个例子开始,下面的Go包提供了 RandomSeed 两个函数,它们是基于C语言的 randomsrandom 函数的实现。

package rand

/*
#include <stdlib.h>
*/
import "C"

func Random() int {
    return int(C.random())
}

func Seed(i int) {
    C.srandom(C.uint(i))
}

我们从 import 语句开始,讲解相关的代码。

rand 包导入了一个 "C" 包,但是这个包并不是由Go标准库提供。因为 C 包是Cgo工具生成的一个虚拟包,它映射到C语言的名字空间。

rand 包中有几个地方使用了 C 包: C.randomC.srandomC.uint(i)import 语句。

Random 函数调用C语言标准库中的 random 函数,然后返回结果。 在C语言中,random 返回的结果为 long 类型,对应cgo生成的 C.long。在函数返回前我们必须将C类型转换为Go类型。

func Random() int {
    return int(C.random())
}

下面是一个等价的实现,为了更好说明类型转换的使用,这里使用了一个临时变量。

func Random() int {
    var r C.long = C.random()
    return int(r)
}

Seed 函数进行相反的类型转换。它将传入的Go的 int 类型变量转换为C语言的 unsigned int,然后传入C语言的 srandom 函数。

func Seed(i int) {
    C.srandom(C.uint(i))
}

Cgo能够知道 unsigned int 对应 C.uint 类型。关于数值类型的详细说明可以参考Cgo文档

到此为止,只有 import 语句的注释还没有解释。

/*
#include <stdlib.h>
*/
import "C"

Cgo可以识别这个注释。注释中,任意以 #cgo 开头的行会被忽略,它们是cgo的扩展命令。 剩余的行在编译包的C代码时时,将被当作头文件处理。在这个例子中,虽然只有一个 #include 语句,但是可以包含任意的C语言代码。在构建包中C代码时,#cgo 规则可以指定用于编译和连接的选项。

有一点要注意:如果使用了 //export 规则,那么注释中的C代码将只能包含对应函数的声明(extern int f();), 而不能是对应函数的定义(int f() { return 1; })。使用 //export 规则,可以使Go函数被C语言函数调用。

关于 #cgo//export 的用法在 cgo文档 中有详细说明。

字符串相关

和Go语言不同,C语言没有明确的字符串类型。在C语言中,字符串表现为以 NULL 结尾的 char数组。

Go语言和C语言字符串之间的转换由以下函数完成:C.CStringC.GoStringC.GoStringN。这些函数函数在转换时均构造了一个字符串的副本。

这里是 Print 函数的另一个实现,它通过C语言的 stdio 标准库函数 fputs 将字符串写到标准输出:

package print

// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"

func Print(s string) {
    cs := C.CString(s)
    C.fputs(cs, (*C.FILE)(C.stdout))
    C.free(unsafe.Pointer(cs))
}

Go语言的GC并不能管理C语言函数分配的内存。当使用 C.CString(使用C函数分配了内存)返回C字符串时, 必须要记得在完成后用 C.free 释放对应的内存。

C.CString 返回C语言字符串起始地址,因此在函数返回时将它转换为 unsafe.Pointer 类型,然后用 C.free释放对应内存空间。cgo中的一个常用习惯是在创建新内存后使用 defer 释放对应的内存 (特别是在后面代码很复杂时),下面是重写的 Print 函数:

func Print(s string) {
    cs := C.CString(s)
    defer C.free(unsafe.Pointer(cs))
    C.fputs(cs, (*C.FILE)(C.stdout))
}

构建cgo包

要构建cgo包,只要直接简单执行 “go build”“go install” 命令。 go命令可以识别 "C" 虚拟包的语法,并且可以自动调用cgo生成相应的中间代码文件。

更多的Cgo资源

cgo命令 文档中有C包的更多的细节说明和构建的详细流程。 在Go目录树中的 cgo例子 演示了更全面的用法。

如果是简单的Cgo例子,可以参考 Russ Coxgosqlite 项目。 在 Go Project Dashboard 列表中有很多基于cgo的项目。

最后,如果想了解Cgo的工作原理,可以查看runtime包中的 cgocall.c 代码。