C? Go? Cgo! | Go主题月

546 阅读3分钟

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。 注意,cgo将unsigned int类型识别为C.uint。 有关这些数字类型名称的完整列表,请参见cgo文档。

我们尚未检查的该示例的一个细节是import语句上方的注释。


/*
#include <stdlib.h>
*/
import "C"

Cgo删除任何以#cgo开头后跟空格的行。其余的行在编译包的C部分时用作头文件。在本例中,这些行只是一条#include语句,但它们几乎可以是任何C代码。#cgo指令用于在构建包的C部分时为编译器和链接器提供标志。

有一个限制:如果你的程序使用任何//export指令,那么注释中的C代码只能包含声明(extern int f();),而不能包含定义(int f() {return 1;})。你可以使用//export指令让C代码可以访问Go函数。

#cgo和//export指令记录在cgo文档中。

字符串和事情 与Go不同,C没有显式的字符串类型。在C语言中,字符串由一个以零结尾的字符数组表示。

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.CString的调用返回一个指向char数组开头的指针,因此在函数退出之前,我们将它转换为一个不安全的指针,并使用C.free释放内存分配。在cgo程序中,一个常见的习惯用法是在分配后立即延迟释放(特别是当后面的代码比单个函数调用更复杂时),如下面的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伪程序包和构建过程的更多详细信息。

最后,如果您对所有这些在内部如何起作用感到好奇,请查看运行时包cgocall.go的介绍性注释。

GOlang外刊翻译计划blog.golang.org/cgo