Go 语言系列42:CGO 编程入门(三)_#include

import "C"

Go 语言系列42:CGO 编程入门(三)_正则表达式_02


接着上一期的内容,要使用 CGO 则需要编写导入伪包 ​​"C"​​​ 的 Go 语言代码,在 Go 语言中出现 ​​import "C"​​ 语句即表明使用了 CGO ,在这个导入语句之前的注释就是一种特殊的代码块,里面就是 C 语言的代码,这个注释我们一般称为 前导 。所以, ​​import "C"​​ 语句实际上就是导入 C 伪包,并且 C 伪包的代码就是该语句上方注释里的 C 代码。

通过使用 ​​import "C"​​​ 语句,我们就能在 Go 语言代码中引用像 ​​C.size_t​​​ 的类型、或者 ​​C.stdout​​​ 之类的变量、或者 ​​C.putchar​​ 之类的函数。

例如前面几期经常提到的这个例子:

package main

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

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

通过前面几期的介绍,相信你也一定知道前导可以包含任何的 C 代码,包括函数以及变量的声明和定义,你可以在 Go 代码中引用它们。但是注意一点,前导中的静态变量可能不被 Go 代码引用。


Go 语言系列42:CGO 编程入门(三)_#include

#cgo

Go 语言系列42:CGO 编程入门(三)_正则表达式_02


在前导中可以通过使用 ​​#cgo​​​ 语句设置编译阶段和链接阶段的相关参数。例如以下的几个指令:​​CFLAGS​​​ 、 ​​CPPFLAGS​​​ 、 ​​CXXFLAGS​​​ 、 ​​FFLAGS​​​ 、 ​​LDFLAGS​​ 。

这些指令可以包含一个构建的约束列表,限制其对满足其中一个约束的系统的影响,编译阶段的参数主要用于定义相关宏和指定头文件检索路径。链接阶段的参数主要是指定库文件检索路径和要链接的库文件。例如:

// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo amd64 386 CFLAGS: -DX86=1
// #cgo LDFLAGS: -lpng
// #include <png.h>
import "C"

或者, ​​CPPFLAGS​​​ 和 ​​LDFLAGS​​​ 可以通过 ​​pkg-config​​​ 工具使用 ​​#cgo pkg-config:​​ 指令后跟包名称来获得。例如:

// #cgo pkg-config: png cairo
// #include <png.h>
import "C"

可以通过设置 ​​PKG_CONFIG​​​ 环境变量来更改默认的 ​​pkg-config​​ 工具。

出于安全原因,只允许使用一组有限的标志,特别是 ​​-D​​​ 、 ​​-U​​​ 、 ​​-I​​​ 和 ​​-l​​​ 。要允许附加标志,要将 ​​CGO_CFLAGS_ALLOW​​​ 设置为匹配新标志的正则表达式。要禁止原本允许的标志,要将 ​​CGO_CFLAGS_DISALLOW​​ 设置为匹配必须禁止的参数的正则表达式。

同样出于安全原因,只允许使用有限的字符集,尤其是一些符号,例如 ​​.​​​ ,它们可能会以其他方式解释。使用禁止字符会报 ​​malformed #cgo argument​​ 错误。

解析 CGO 指令时,如果出现字符串 ​​${SRCDIR}​​​ ,它们都将替换为包含源文件目录的绝对路径。这允许将预编译的静态库包含在包目录中并正确的链接。例如,如果包 ​​foo​​​ 在目录 ​​/go/src/foo​​ 中:

// #cgo LDFLAGS: -L${SRCDIR}/libs -lfoo

将扩展为:

// #cgo LDFLAGS: -L/go/src/foo/libs -lfoo

参考文献:

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

[2] https://pkg.go.dev/cmd/cgo