调用C库函数

相对而言,Go语言 还是一门非常年轻的语言。

虽然发展迅猛,局限性也有——类库不是特别丰富。 有些开源项目,例如 x264 ,只有 C 语言版本, Go 重写一遍也不太现实。

好在, Go 提供了一种调用 C 函数的机制—— cgo 。 本节以具体的例子演示,如何使用 cgo 调用 C 函数:

被调用库函数

为了演示需要,虚设一个名为 callee 的函数库。 函数库只提供一个名为 sayHello 的函数,接口如头文件:

/_src/practices/cgo/callee.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/**
 * FileName:   callee.h
 * Author:     Fasion Chan
 * @contact:   fasionchan@gmail.com
 * @version:   $Id$
 *
 * Description:
 *
 * Changelog:
 *
 **/

void SayHello();

sayHello 函数只是简单输出 Hello, world! ,实现如下:

/_src/practices/cgo/callee.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/**
 * FileName:   callee.c
 * Author:     Fasion Chan
 * @contact:   fasionchan@gmail.com
 * @version:   $Id$
 *
 * Description:
 *
 * Changelog:
 *
 **/

#include <stdio.h>
#include "callee.h"

void SayHello() {
    printf("Hello, world!\n");
}

接下来,将上述代码编译成动态库:

$ gcc -fPIC -c callee.c
$ ls
Makefile  callee.c  callee.h  callee.o  caller.go

这个命令将 callee.c 源码编译成 callee.o 目标文件。 接下来,将目标文件转成成 libcallee.so 动态库文件:

$ gcc -shared -o libcallee.so callee.o
$ ls
Makefile  callee.c  callee.h  callee.o  caller.go  libcallee.so

调用方

接下来看看如何在 Go 中调用这个动态库。代码如下:

/_src/practices/cgo/caller.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
 * FileName:   caller.go
 * Author:     Fasion Chan
 * @contact:   fasionchan@gmail.com
 * @version:   $Id$
 *
 * Description:
 *
 * Changelog:
 *
 **/

package main

/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -L. -lcallee
#include "callee.h"
*/
import "C"

import (
    "fmt"
)

func main() {
    C.SayHello();
    fmt.Println("Success!")
}

16 行通过 -I 选项指定头文件搜索路径,编译器据此发现头文件 callee.h 。 这里 . 表示当前目录,可以根据项目情况设置为其他目录。

17 行通过 -L 选项指定动态库搜索路径,编译器据此发现 libcallee.so 。 接着,通过 -l 参数链接到该动态库。

18 行则是引入头文件,据此编译器知晓 C 函数接口。

20 通过 import 关键字引入一个特殊的模块 C ,之后便可访问到所用链接的 C 库函数。 第 27 行,调用 sayHello 函数。

警告

第19行与20行之间不能留空行,不然构建失败!

接下来,编译整个程序:

$ go build caller.go
$ ls
Makefile  callee.c  callee.h  callee.o  caller  caller.go  libcallee.so
$ ./caller
Hello, world!
Success!

看到没有,成功调用 sayHello 函数并输出 Hello, world! !

自动化构建

以上例子包含多个编译步骤,需要执行多个命令。

在程序开发中,经常也是如此。 如果每次修改代码后,都需要手工执行这么多命令,那么效率和质量将深受拖累。

我们可以用更自动化的手段进行构建,以 Makefile 为例:

/_src/practices/cgo/Makefile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# FileName:   Makefile
# Author:     Fasion Chan
# @contact:   fasionchan@gmail.com
# @version:   $Id$
#
# Description:
#
# Changelog:
#

.DEFAULT_GOAL := run

callee.o: callee.c
	gcc -fPIC -c callee.c

libcallee.so: callee.o
	gcc -shared -o $@ $^

caller: caller.go libcallee.so
	go build caller.go

clean:
	rm -f caller libcallee.so callee.o

run: caller
	./caller

在源码目录准备以上 Makefile ,之后运行:

$ make
gcc -fPIC -c callee.c
gcc -shared -o libcallee.so callee.o
go build caller.go
./caller
Hello, world!
Success!

可以看到, make 命令根据 Makefile 定义自动执行编译命令并执行目标程序。

Makefile 更深入的使用方法不在本节的讨论范畴,有兴趣的童鞋自行 Google

下一步

订阅更新,获取更多学习资料,请关注我们的 微信公众号

../_images/wechat-mp-qrcode.png

小菜学编程

微信打赏