本文翻译自《C? Go? Cgo!》。
Andrew Gerrand
2011/03/17
介绍
Cgo可以让Go包调用C代码。给定一个使用一些特殊特性编写的Go源文件,cgo输出Go和C文件,它们可以组合成单个Go包。
举个例子,这里有一个Go包,它提供了两个函数——Random
和Seed
——用来包装C的random
函数和srrandom
函数。
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
是一个“伪包(pseudo-package)”,一个被cgo解释为引用C命名空间的特殊名称。
rand
包包含对C
包的四个引用:对C.random
和C.srrandom
的调用、C.uint(i)
和import "C"
语句。
Random
函数调用标准C库的random
函数并返回结果。在C语言中,random
返回一个long
类型的值,cgo将其表示为C.long
类型。必须将其转换为Go类型,才能由该包外的Go代码使用,使用普通的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函数srrandom
。
func Seed(i int) {
C.srandom(C.uint(i))
}
注意,cgo知道C语言的unsigned int
类型用C.uint
来表示;有关这些数值类型的名称的完整列表,请参阅cgo文档。
我们还没有研究这个例子的一个细节是import
语句上方的注释。
/*
#include <stdlib.h>
*/
import "C"
Cgo认识这一注释。任何以#cgo
开头、后跟一个空格字符的行都将被删除;这些都成为cgo的指令。在编译Go包的C语言部分代码时,剩余的行用作C语言头部。在上例中,这些行只是一个#include
语句,但它们几乎可以是任何C代码。在构建Go包的C语言部分时,#cgo
指令用于为编译器和链接器提供标志。
有一个限制:如果你的程序使用任何//export
指令,那么注释中的C代码可能只包括这种声明(extern int f();
),而不是定义(int f() { return 1; }
)。你可以使用//export
指令使Go函数可被C语言代码访问。
#cgo
和//export
指令记录在cgo文档中。
字符串相关
与Go不同,C没有显式的字符串类型。C中的字符串由一个以零结尾的字符数组表示。
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的内存管理器看不见由C代码进行的内存分配。因此当你使用C.CString
(或任何C内存分配)创建一个C字符串时,你必须记住在完成调用后使用C.free
释放内存。
对C.CString
的调用返回一个指向char数组头部的指针,我们将其转换为一个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示例演示了更高级的概念。
最后,如果你想知道在内部这一切是如何工作的,请查看运行时包的cgocall.go的介绍性注释。