C? Go? Cgo!
Andrew Gerrand
2011 年 3 月 17 日
介绍
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 中的字符串由以零结尾的 char 数组表示.
Go 和 C 字符串之间的转换是通过 C.CString
, C.GoString
和 C.GoStringN
函数来完成的. 这些转换将复制字符串数据.
下一个示例实现了 Print
函数, 该函数使用来自 stdio
库的 C 的 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 的介绍注释.
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。