golang —— 语言交互性
为了能够重用已有的 C 语言库,我们在使用 Golang 开发项目或系统的时候难免会遇到 Go 和 C语言 混合编程,这时很多人都会选择使用 cgo 。
1 Cgo
话说 cgo 这个东西可算得上是让人又爱又恨,好处在于它可以让你快速重用已有的 C语言 库,无需再用 Gg 重造一遍轮子,而 坏处就在于它会在一定程度上削弱你的系统性能。关于cgo 的种种劣迹,Dave Cheney 大神在他的博客上有一篇专门的文章《cgo is not Go》。
但话说回来,有时候为了快速开发满足项目需求,使用 cgo 也实在是不得已而为之。
官方示例:
package main
import "fmt"
/*
#include <stdlib.h>
*/
import "C"
func Random() int {
return int(C.random())
}
func Seed(i int) {
C.srandom(C.uint(i))
}
func main() {
Seed(100)
fmt.Println("Random:", Random())
}
原理:
import "C" 是一个标记,告诉 Cgo 他应该开始工作了。Cgo 做的事情就是,对应这条 import 语句之前的块注释中的 C 源代码自动生成包装性质的 Go 代码。函数调用从汇编角度看,就是一个将参数按顺序压栈( push ),然后进行函数调用( call )的过程。Cgo 的代码只不过是帮你封装了这个压栈和调用的过程,从外面看起来就是一个普通的 Go 调用。
语法:
import"C" 前面需要紧贴在注释的下面。块注释还是行注释无所谓。注释中的任意合法的 C 源代码,Cgo 都给会进行相应的处理并生成对应的 Go 代码,如下所示:
package hello
/*
#include <stdlib.h>
void hello() {
printf("Hello, Cgo! -- Frin C world.\n");
}
*/
import "C"
func Hello() int {
C.hello()
}
2 关键难点
跨语言交互中,比较复杂的问题有两个:类型映射 以及 **跨越调用边界传递指针 **所带来的对象生命周期和内存管理的问题。
2.1 类型映射
基础数据类型
| C语言 | Go语言 |
|---|---|
| signed char | C.char / C.schar |
| unsigned char | C.uchar |
| unsigned short | C.short / C.ushort |
| unsigned int | C.int / C.uint |
| unsigned long | C.long / C.ulong |
| long long | C.longlong |
| unsigned long long | C.ulonglong |
| float | C.float |
| double | C.double |
| void*(指针) | unsafe.Pointer |
数据结构类型
| C语言 | Go语言 |
|---|---|
| struct | struct_(前缀) |
| union | union_(前缀) |
| enum | enum_(前缀) |
2.2 字符串映射
Cgo 提供了一系列函数来提供支持:C.CString 、C.GoString 、C.GoStringN 。
注意:
- 每次转换都将导致一次内存复制
- 无论是
C语言还是Go语言都不允许对字符串的内容进行修改
内存问题
由于 C.CString 的内存管理方式与 Go 语言自身的内存管理方式不兼容,所以再使用完之后,必需要现实的释放调用 C.CSting 所产生的内存快,否则将会造成内存泄漏。示例如下:
2.3 函数调用
对于常规返回了一个值的函数,调用者可以用以下方式,顺便得到错误码:
n, err := C.atoi("1234");
在传递数组类型的参数时,在 Go 中,将第一个元素的地址作为整个数组的起始地址传入,示例如下:
n, err := C.func(&array[0]); // 需要显示指定第一个元素的地址
2.4 C代码中依赖第三方库
当需要依赖非 C 标准的第三方库时,Cgo 提供了 #cgo 这样的伪 C 文法,让开发者有机会指定依赖的第三方库和编译选项。示例如下:
// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo linux CFLAGS: -DLINUX=1
// #cgo LDFLAGS: -lpng
// #include <png.h>
import "C"
以上代码中示范了如何通过 CFLAGS 来传入编译选项,使用 LDFLAGS 来传入链接选项。更简便的 #cgo 用法如下所示:
// #cgo pkg-config: png cairo
// #include <png.h>
import "C"
在 Go 中使用 cgo 调用 C 库的时候,如果需要引用很多不同的第三方库,那么使用 #cgo CFLAGS: 和 #cgo LDFLAGS: 的方式会引入很多行代码。首先这会导致代码很丑陋,最重要的是如果引用的不是标准库,头文件路径和库文件路径写死的话就会很麻烦,一旦第三方库的安装路径变化了,Go 的代码也要跟着变化。所以使用 pkg-config 无疑是一种更为优雅的方法,不管库的安装路径有何变化,都不需要修改 Go 代码。
3 编译 Cgo
Go 安装后,自带一个 cgo 命令行工具,它用于处理所有带有 Cgo 代码的 Go 文件,生成 Go 语言版本的调用封装代码。而 Go 工具集又对 cgo 再次进行了良好的封装,所以我们不需要做任何特殊的处理。

浙公网安备 33010602011771号