cgo代码编译

Posted by Chen Quan on August 20, 2019

1.cgo语句

在使用gccC语言进行编译时,我们会经常一些参数用于设置编译阶段和链接阶段的相关参数。

import "C"语句前的注释中可以通过#cgo语句设置编译阶段和链接阶段的相关参数。编译阶段的参数主要用于定义相关宏和指定头文件检索路径。链接阶段的参数主要是指定库文件检索路径和要链接的库文件。

// #cgo CFLAGS: -DPNG_DEBUG=1 -I./include
// #cgo LDFLAGS: -L/usr/local/lib -lpng
// #include <png.h>
import "C"

上面的代码中,CFLAGS部分,-D部分定义了宏PNG_DEBUG,值为1;-I定义了头文件包含的检索目录。LDFLAGS部分,-L指定了链接时库文件检索目录,-l指定了链接时需要链接png库。

因为C/C++遗留的问题,C头文件检索目录可以是相对目录,但是库文件检索目录则需要绝对路径。在库文件的检索目录中可以通过${SRCDIR}变量表示当前包目录的绝对路径:

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

上面的代码在链接时将被展开为:

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

#cgo语句主要影响CFLAGS、CPPFLAGS、CXXFLAGS、FFLAGS和LDFLAGS几个编译器环境变量。LDFLAGS用于设置链接时的参数,除此之外的几个变量用于改变编译阶段的构建参数(CFLAGS用于针对C语言代码设置编译参数)。

对于在cgo环境混合使用C和C++的用户来说,可能有三种不同的编译选项:其中CFLAGS对应C语言特有的编译选项、CXXFLAGS对应是C++特有的编译选项、CPPFLAGS则对应C和C++共有的编译选项。但是在链接阶段,C和C++的链接选项是通用的,因此这个时候已经不再有C和C++语言的区别,它们的目标文件的类型是相同的。

#cgo指令还支持条件选择,当满足某个操作系统或某个CPU架构类型时后面的编译或链接选项生效。比如下面是分别针对windows和非windows下平台的编译和链接选项:

// #cgo windows CFLAGS: -DX86=1
// #cgo !windows LDFLAGS: -lm

其中在windows平台下,编译前会预定义X86宏为1;在非widnows平台下,在链接阶段会要求链接math数学库。这种用法对于在不同平台下只有少数编译选项差异的场景比较适用。

如果在不同的系统下cgo对应着不同的c代码,我们可以先使用#cgo指令定义不同的C语言的宏,然后通过宏来区分不同的代码:

package main

/*
#cgo windows CFLAGS: -DCGO_OS_WINDOWS=1
#cgo darwin CFLAGS: -DCGO_OS_DARWIN=1
#cgo linux CFLAGS: -DCGO_OS_LINUX=1

#if defined(CGO_OS_WINDOWS)
    const char* os = "windows";
#elif defined(CGO_OS_DARWIN)
    static const char* os = "darwin";
#elif defined(CGO_OS_LINUX)
    static const char* os = "linux";
#else
#    error(unknown os)
#endif
*/
import "C"

func main() {
    print(C.GoString(C.os))
}

这样我们就可以用C语言中常用的技术来处理不同平台之间的差异代码。

2.build tag 条件编译

build tag 是在Go或cgo环境下的C/C++文件开头的一种特殊的注释。条件编译类似于前面通过#cgo指令针对不同平台定义的宏,只有在对应平台的宏被定义之后才会构建对应的代码。但是通过#cgo指令定义宏有个限制,它只能是基于Go语言支持的windows、darwin和linux等已经支持的操作系统。如果我们希望定义一个DEBUG标志的宏,#cgo指令就无能为力了。而Go语言提供的build tag 条件编译特性则可以简单做到。

比如下面的源文件只有在设置debug构建标志时才会被构建:

// +build debug

package main

var buildMode = "debug"

可以用以下命令构建:

go build -tags="debug"
go build -tags="windows debug"

我们可以通过-tags命令行参数同时指定多个build标志,它们之间用空格分隔。

当有多个build tag时,我们将多个标志通过逻辑操作的规则来组合使用。比如以下的构建标志表示只有在”linux/386“或”darwin平台下非cgo环境“才进行构建。

// +build linux,386 darwin,!cgo

其中linux,386中linux和386用逗号链接表示AND的意思;而linux,386darwin,!cgo之间通过空白分割来表示OR的意思。