调用C库函数

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

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

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

被调用库函数

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

/_src/practices/cgo/callee.h

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

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

/_src/practices/cgo/callee.c

  1. /**
  2. * FileName: callee.c
  3. * Author: Fasion Chan
  4. * @contact: fasionchan@gmail.com
  5. * @version: $Id$
  6. *
  7. * Description:
  8. *
  9. * Changelog:
  10. *
  11. **/
  12.  
  13. #include <stdio.h>
  14. #include "callee.h"
  15.  
  16. void SayHello() {
  17. printf("Hello, world!\n");
  18. }

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

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

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

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

调用方

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

/_src/practices/cgo/caller.go

  1. /**
  2. * FileName: caller.go
  3. * Author: Fasion Chan
  4. * @contact: fasionchan@gmail.com
  5. * @version: $Id$
  6. *
  7. * Description:
  8. *
  9. * Changelog:
  10. *
  11. **/
  12.  
  13. package main
  14.  
  15. /*
  16. #cgo CFLAGS: -I.
  17. #cgo LDFLAGS: -L. -lcallee
  18. #include "callee.h"
  19. */
  20. import "C"
  21.  
  22. import (
  23. "fmt"
  24. )
  25.  
  26. func main() {
  27. C.SayHello();
  28. fmt.Println("Success!")
  29. }

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

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

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

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

警告

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

接下来,编译整个程序:

  1. $ go build caller.go
  2. $ ls
  3. Makefile callee.c callee.h callee.o caller caller.go libcallee.so
  4. $ ./caller
  5. Hello, world!
  6. Success!

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

自动化构建

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

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

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

/_src/practices/cgo/Makefile

  1. # FileName: Makefile
  2. # Author: Fasion Chan
  3. # @contact: fasionchan@gmail.com
  4. # @version: $Id$
  5. #
  6. # Description:
  7. #
  8. # Changelog:
  9. #
  10.  
  11. .DEFAULT_GOAL := run
  12.  
  13. callee.o: callee.c
  14. gcc -fPIC -c callee.c
  15.  
  16. libcallee.so: callee.o
  17. gcc -shared -o $@ $^
  18.  
  19. caller: caller.go libcallee.so
  20. go build caller.go
  21.  
  22. clean:
  23. rm -f caller libcallee.so callee.o
  24.  
  25. run: caller
  26. ./caller

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

  1. $ make
  2. gcc -fPIC -c callee.c
  3. gcc -shared -o libcallee.so callee.o
  4. go build caller.go
  5. ./caller
  6. Hello, world!
  7. Success!

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

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

下一步

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

../_images/wechat-mp-qrcode.png小菜学编程

微信打赏