go 里面的 cgo 包可以直接调用 c 代码,只需要遵守一定的使用规则即可,对于那些熟悉 c/c++ 的开发者来说,不失为一个福音。
三个代表
cgo 会从 go 代码中去寻找 c/c++ 代码,约定:
紧挨在
import "C"
上面的所有 c 风格的注释语句都可以被 go 编译。
1.所有代码都写在一个 go 文件中
1 | //foo.go |
这是最简便的方式,适用于 c 代码逻辑比较简单的情况。
1 | hxzdeMac-mini:~ $ tree |
2.c 代码和 go 代码写在独立文件
1 | //foo.h |
像这种直接编译 c 源码和 go 源码的方式,只需执行 go build
,cgo 会在当前目录或 c 语言 -I
编译参数指定的目录寻找需要的.h
,.c
文件。
1 | hxzdeMac-mini:~ $ tree |
3.go 代码调用动态链接库
如果只提供了 c 的动态链接库和头文件,那么需要在 go 文件中指定 cgo 的 CFLAGS, CPPFLAGS, CXXFLAGS, LDFLAGS
等编译选项:
1 | //foo.h |
1 | hxzdeMac-mini:~ $ tree |
go 引用 c
C 里面的标准类型可以在 go 里面直接引用:
1 | C.char, C.schar (signed char), C.uchar (unsigned char) |
引用 struct,union,enum:
1 | C.struct_Foo == struct Foo |
Go 里面的 struct 不能包含 C 类型。
sizeof:1
C.sizeof_struct_Foo == sizeof(struct Foo)
c 中结构体字段如果是 go 里面的关键字,那么可以在该结构体字段名前面加个 _
来引用,比如:
1 | typedef struct Foo { |
Cgo 将 C 类型转译成对应的 go 类型是不可导出的,因此,go 包导出的 API 里面不能含有 C 类型。
C 里面的函数执行结果可以赋给 go 里面的多返回值(执行结果和可能产生的错误值),即使是没有返回值的 void
类型函数。
1 | n, err = C.sqrt(-1) |
cgo 目前还不支持直接调用 C 里面的函数指针,你可以声明一个包含 C 函数指针的 go 变量(如下的 f
),这样就可以在 go 和 C 之间传递了。
1 | package main |
在 C 里面, 如果一个函数接受一个数组参数,只需要传递数组名即可,但在 go 里面不行,你必须显示指定数组首元素的地址:
1 | void f(int[] arr) {} |
下面是几组 c 和 go 之间拷贝数据的语法:
1 | // Go string to C string; result must be freed with C.free |
c 引用 go
- 使用
//export <function-name>
导出 Go 代码 - 在 C 代码里面使用
extern
关键字引用 go 代码
1 | package main |
传递指针
go 是一门垃圾回收语言,垃圾回收器需要知道每个指针所指向的内存位置,正因为如此,在 go 和 c 之间传递指针有些许限制。
- go 指针:使用
&
操作符或new
函数返回的指针 - c 指针: 使用
C.malloc
返回的指针
首先,我们需要明白:
将指针传递给 struct 某个字段时,所涉及的 Go 内存是该字段占用的内存,而不是整个结构体占有的内存;将指针传递给数组或切片中的元素时,所涉及的 Go 内存是整个数组或整个切片的底层数组。
go 代码可以传递 go 指针给 c, 前提是这个指针指向的内存不包含任何 go 指针。c 代码必须保留这个属性:
它不能在 go 内存中存储任何 go 指针(即使是临时变量也不行)。
C 代码调用的 Go 函数可能会将 C 指针作为参数,并且可能通过这些指针存储非指针或 C 指针数据,但是它可能不会将 Go 指针存储在由C 指针指向的内存中;C 代码调用的 Go 函数可能会将 Go 指针作为参数,但它必须保留“指向的 Go 内存不包含任何 Go 指针”的属性。
Go 代码可能不会在 C 内存中存储 Go 指针,C 代码可能会在 C 内存中存储 Go 指针,这取决于上面的规则:当 C 函数返回时,它必须停止存储 Go 指针。