这一期我们讲一讲 Go 语言高级部分的 CGO 编程入门。

想必学 Go 语言的人都知道,Go 语言创作者之一的 Ken Thompson 曾与 Dennis Ritchie 一起共同发明了 C 语言。Go 语言又被称之为 21 世纪的 C 语言。C 语言作为一个通用语言,很多库会选择提供一个 C 兼容的 API ,然后用其他不同的编程语言实现。Go 语言通过自带的一个叫 CGO 的工具来支持 C 语言函数调用,同时我们可以用 Go 语言导出 C 动态库接口给其它语言使用。

下面是几个可能用到 CGO 的场景:

  1. 通过 OpenGL 或 OpenCL 使用显示卡的计算能力
  2. 通过 OpenCV 来进行图像分析
  3. 通过 Go 编写 Python 扩展
  4. 通过 Go 编写移动应用

Go 语言系列40:CGO 编程入门(一)_回收程序

第一个 CGO 程序

Go 语言系列40:CGO 编程入门(一)_回收程序_02


真实的 CGO 程序一般都比较复杂。下面是一个最简的 CGO 程序例子:

package main

import "C"

func main() {
println("Hello CGO")
}

在 Go 语言程序中只要包含一个 ​​import "C"​​​ 语句就表示已经启用 CGO 。在上面的程序中,主函数通过调用 Go 内置的 ​​println​​​ 函数输出字符串 ​​Hello CGO​​​ ,虽然没有 CGO 相关的代码,但 ​​go build​​ 命令在编译和链接阶段会启动 gcc 编译器,这就是一个 CGO 程序了。运行该程序会输出字符串:

Hello CGO

Go 语言系列40:CGO 编程入门(一)_回收程序

基于 C 标准库函数的输出

Go 语言系列40:CGO 编程入门(一)_回收程序_02


接下来,我们改一改上面的例子,引入 C 语言标准库中的 ​​<stdio.h>​​​ 头文件,通过 CGO 包中的 ​​C.CString()​​​ 函数将 Go 语言字符串转化为 C 语言字符串,然后通过调用 CGO 包中的 ​​C.puts()​​​ 函数将字符串输出到标准输出窗口。记住一点,要在 ​​import "C"​​​ 之前添加 ​​//#include<stdio.h>​​ 语句。

package main

//#include<stdio.h>
import "C"

func main() {
C.puts(C.CString("Hello CGO\n"))
}

运行该程序同样会输出字符串:

Hello CGO

当然,这里没有释放使用 ​​C.CString()​​ 创建的 C 语言字符串会导致内存泄漏,关于这一点我们以后再谈,对于这个简单的程序来说不会有什么问题,因为程序结束后会自动回收程序的所有资源。


Go 语言系列40:CGO 编程入门(一)_回收程序

调用自定义 C 函数

Go 语言系列40:CGO 编程入门(一)_回收程序_02


我们同样可以在 ​​import "C"​​ 之前的注释里面添加自定义函数,例如:

package main

/*
#include<stdio.h>

static void MyPrint(const char* s) {
puts(s);
}
*/
import "C"

func main() {
C.MyPrint(C.CString("Hello CGO\n"))
}

运行该程序同样会输出字符串:

Hello CGO

我们也可以将 ​​MyPrint()​​​ 函数放到当前目录下的一个 C 语言源文件中(后缀名必须是 ​​.c​​​ )。因为是编写在独立的 C 文件中,为了允许外部引用,所以需要去掉函数的 ​​static​​ 修饰符。

// main.c

#include <stdio.h>

void MyPrint(const char* s) {
puts(s);
}
// main.go
package main

//void MyPrint(const char* s);
import "C"

func main() {
C.MyPrint(C.CString("Hello CGO\n"))
}

在包路径下使用 ​​go run .​​ 命令运行该程序同样会输出字符串:

Hello CGO

因为时间关系这一期我们先讲这么多,下一期再补充另外一些 CGO 编程入门基础。

参考文献:

[1] 柴树杉;曹春晖, Go 语言高级编程, 北京: 人民邮电出版社, 2019.