cgo–在Go中链接外部C库

虽然我们可以在Go源码文件中直接定义C类型、变量和C函数,但从代码结构上来讲,在Go源文件中大量编写C代码并不是Go推荐的惯用法。那么如何将C函数和变量定义从Go源码中分离出去单独定义呢?我们很容易想到将C的代码以共享库的形式提供给Go源码

Go提供了#cgo指示符,可以用它指定Go源码在编译后与哪些共享库进行链接。我们来看一下例子:

// go-cgo/foo.go
package main

// #cgo CFLAGS: -I${SRCDIR}
// #cgo LDFLAGS: -L${SRCDIR} -lfoo
// #include <stdio.h>
// #include <stdlib.h>
// #include "foo.h"
import "C"
import "fmt"

func main() {
    fmt.Println(C.count)
    C.foo()
}

我们看到在上面的例子中,通过#cgo指示符告诉Go编译器在当前源码目录(${SRCDIR}会在编译过程中自动转换为当前源码所在目录的绝对路径)下查找头文件foo.h,并链接当前源码目录下的libfoo共享库。C.count变量和C.foo函数的定义都在libfoo共享库中。

我们来创建这个共享库:

// chapter9/sources/go-cgo/foo.h

extern int count;
void foo();
// chapter9/sources/go-cgo/foo.c

#include "foo.h"

int count = 6;
void foo() {
    printf("I am foo!\n");
}
$gcc -c foo.c
$ar rv libfoo.a foo.o

我们用ar工具成功创建了一个静态共享库文件libfoo.a

接下来构建并运行foo.go:

$go build foo.go
$./foo
6
I am foo!

我们看到foo.go成功链接到libfoo.a并生成最终的二进制文件foo。

Go同样支持链接动态共享库,我们用下面的命令将上面的foo.c编译为一个动态共享库:

$gcc -c foo.c
//$gcc -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o (在linux上)
$gcc -shared -o libfoo.so  foo.o

重新编译foo.go,并查看(在Linux上可以使用ldd,在macOS上使用otool)重新生成的二进制文件foo的动态共享库依赖情况:

$> go build foo.go
$otool -L foo
foo:
    libfoo.so (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.250.1)

有一点值得注意的是,Go支持多返回值,而C并不支持,因此当将C函数用在多返回值的Go调用中时,C的errno将作为函数返回值列表中最后那个error返回值返回。下面是个例子:

// chapter9/sources/go-cgo/c_errno.go

package main

// #include <stdlib.h>
// #include <stdio.h>
// #include <errno.h>
// int foo(int i) {
//     errno = 0;
//     if (i > 5) {
//         errno = 8;
//         return i - 5;
//     } else {
//         return i;
//     }
//}
import "C"
import "fmt"

func main() {
    i, err := C.foo(C.int(8))
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(i)
    }
}

运行这个例子:

$go run c_errno.go
exec format error

exec format error就是errno为8时的错误描述信息。我们可以在C运行时库的errno.h中找到errno=8与这段描述信息的联系:

#define ENOEXEC      8  /* Exec format error */

请登录后发表评论

    没有回复内容