Go 博客
C? Go? Cgo!
引言
Cgo 允许 Go 包调用 C 代码。给定一个带有特殊功能的 Go 源文件,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
返回 C 类型 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 函数 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 代码。#cgo
指令用于在构建包的 C 部分时为编译器和链接器提供标志。
有一个限制:如果你的程序使用了任何 //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))
}
C 代码进行的内存分配对于 Go 的内存管理器来说是未知的。当你使用 C.CString
(或任何 C 内存分配)创建一个 C 字符串时,你必须记住在使用完毕后通过调用 C.free
来释放内存。
调用 C.CString
会返回一个指向字符数组开头的指针,因此在函数退出之前,我们将其转换为 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 文件开头的注释。
下一篇文章:Gobs of data
上一篇文章:Go 变得更稳定
博客索引