C? Go? Cgo!

未匹配的标注

本文为官方 Go Blog 的中文翻译,详见 翻译说明

Andrew Gerrand
2011 年 3 月 17 日

介绍

Cgo 让 Go 程序包可以调用 C 代码. 给定具有某些特殊功能的 Go 源文件, 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.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.GoStringC.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 buildgo install 即可. go 工具可以识别特殊的 "C" 导入并自动将 cgo 用于这些文件.

更多 cgo 资源

cgo 命令 文档提供了有关 C 伪程序包和构建过程的更多内容. Go 树中的 cgo 示例 展示了更高级的概念.

最后, 如果您对所有这些在内部如何运行感到好奇, 可参阅运行时程序包的 cgocall.go 的介绍注释.

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/go-blog/c-go-cg...

译文地址:https://learnku.com/docs/go-blog/c-go-cg...

上一篇 下一篇
Summer
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~