- 原文: http://golang.org/doc/articles/c_go_cgo.html
- 中文: http://zh-golang.appsp0t.com/doc/articles/c_go_cgo.html
Cgo允许在Go包中调用C代码。如果Go代码含有特殊的cgo语法,可以通过cgo生成相应的Go和C文件,它们可以被编译到一个Go包中。
以一个例子开始,下面的Go包提供了 Random
和 Seed
两个函数,它们是基于C语言的 random
和 srandom
函数的实现。
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.random
、C.srandom
、C.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.CString
、C.GoString
和 C.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 Cox 的 gosqlite 项目。 在 Go Project Dashboard 列表中有很多基于cgo的项目。
最后,如果想了解Cgo的工作原理,可以查看runtime包中的 cgocall.c 代码。