Go语言调用C语言代码

Go 语言除了语法精炼、并发支持好外,还有一个优点就是可以调用 C 代码。可以直接在 Go 源代码里写 C 代码,也可以引 C 语言的外部库。这样在性能遇到瓶颈的地方可以重写,或者某些功能 Go 和第三方还缺失,但 C 语言有现成的库就可以直接用了。官方 Cgo 这块目前有一篇博客 https://blog.golang.org/c-go-cgo 和 命令行文档 https://golang.org/cmd/cgo/ 对 Cgo 进行了说明,其中某些地方还不够明确或者没有提到的地方。

Section1:Go语言内联C语言代码

下面例子是直接将 C 代码内嵌在 Go 源代码里,引入了一个不存在的包 “C”, 然后将 C 代码写在了引入上面,注意只能写在 “C” 包上面。这里定义了一个sum函数, 然后通过 C.sum 在 Go 代码里使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

/* 内联C语言代码 Start */

// // sum calc a and b sum value.
// int sum(int a, int b) {
// 		return a + b;
// }
import "C"

/* 内联C语言代码 End */

import (
	"fmt"
)

func main() {
	cA := C.int(1)
	cB := C.int(2)
	sum := C.sum(cA, cB)
	fmt.Printf("sum value: %+v\n", sum)
}

运行一下这个简单的程序,会得到如下执行结果:

1
2
$ go run main.go                                                    
sum value: 3

Section2:Go语言调用独立的C语言源代码

小巧的 C 语言代码可以方便的通过内联使用,但是代码比较庞大的话可以将 C 语言代码独立编码为包。下面将上面内嵌的C语言代码定义为相应的源文件,当前目录如下:

1
2
3
4
5
$ ll example                                                                               
total 24
-rw-r--r--  1 helloshaohua  staff   229B Jun 21 18:11 main.go
-rw-r--r--  1 helloshaohua  staff    64B Jun 21 16:33 simple_sum.c
-rw-r--r--  1 helloshaohua  staff    39B Jun 21 18:13 simple_sum.h

C语言simple_sum.h头文件

simple_sum.h文件,具体代码如下:

1
2
// sum 函数签名.
int sum(int, int);

C语言simple_sum.c源文件

simple_sum.c文件,具体代码如下:

1
2
3
4
// sum 函数定义.
int sum(int a, int b) {
    return a + b;
}

Go语言main.go源文件

main.go文件,具体代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

/* 调用独立的C语言源代码 Start */

// #include "simple_sum.h"
import "C"

/* 调用独立的C语言源代码 End */

import "fmt"

func main() {
	cA := C.int(1)
	cB := C.int(2)
	sum := C.sum(cA, cB)
	fmt.Printf("sum value: %v\n", sum)
}

当前示例将 Section1:Go语言内联C语言代码 部分中内嵌C语言代码,拆分为独立的C语言文件 simple_sum.hsimple_sum.c 。Go语言源文件代码只需要将 simple_sum.h 头文件引入,即可调用C语言定义的 sum 函数。需要注意的是,main包引用多个文件后就不能再直接使用go run命令直接运行了,需要使用go build命令先对其编译,然后通过执行编译后的可执行文件来运行。go build会检测cgo引用的文件,以 .h.c 为扩展名的C语言源文件也会一起被编译。

1
2
$ go build -o ./example/simple_sum ./example && ./example/simple_sum
sum value: 3

Section3:Go语言调用C语言外部库

Section2:Go语言调用独立的C语言源代码 部分的C语言源代码重新组织为C语言功能库,具体目录结构如下:

1
2
3
4
5
6
$ tree -L 2 example
example
├── main.go
└── simple
    ├── sum.c
    └── sum.h

其中simple目录就一个整合后的C语言外部库,C语言simple/sum.h、simple/sum.c源文件分别对应 Section2:Go语言调用独立的C语言源代码 部分中的 simple_sum.hsimple_sum.c 源文件,代码和之前一样,只是整合到了simple目录,目的是强制让Go语言不编译它。

假定simple目录就是Go程序需要用到的C语言外部库,先编译静态库,在simple目录下生成libsimplesum.a静态链接库文件。

编译C语言外部库simple

1
$ gcc -c example/simple/sum.c -o example/simple/sum.o

生成C语言动态链接库。

1
$ ar -rv example/simple/libsimplesum.a example/simple/sum.o

生成静态链接库后的目录结构如下:

1
2
3
4
5
6
7
8
$ tree -L 2 example
example
├── main.go
└── simple
    ├── libsimplesum.a
    ├── sum.c
    ├── sum.h
    └── sum.o

具体的main.go文件,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

/* Go语言调用C语言外部库 Start */

// #cgo CFLAGS: -I./simple
// #cgo LDFLAGS: -L./simple -lsimplesum
// #include "sum.h"
import "C"

/* Go语言调用C语言外部库 End */

import "fmt"

func main() {
	cA := C.int(1)
	cB := C.int(2)
	sum := C.sum(cA, cB)
	fmt.Printf("sum value: %v\n", sum)
}

执行go build编译这个example程序:

1
2
$ CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -o ./example/simple_sum ./example && ./example/simple_sum
sum value: 3

调用外部库需要用到 #cgo 伪指令,他可以指定编译和链接参数,如 CFLAGS, CPPFLAGS, CXXFLAGS, FFLAGS and LDFLAGS。CFLAGSS可以配置C语言编译器参数,其中 -I 可以指定头文件目录,例如 -I/path/to/include 。LDFLAGS 可以配置引用库的参数,其中 -L 指定引用库的目录,-l指定库名称。如果引用的头文件或者库在系统默认的目录下(例如 /usr/include, /usr/local/include 和 /usr/lib, /usr/local/lib)则可以不用指定目录。

#cgo指令中可以添加限制平台的参数,例如只针对 linux 或者 darwin 平台,详情参考 https://golang.org/pkg/go/build/#hdr-Build_Constraints 。同时可以使用${SRCDIR}代替源代码目录。

1
2
3
// #cgo linux  CFLAGS: -I$./simple
// #cgo darwin CFLAGS: -I$./another_simple
// #cgo LDFLAGS: -L${SRCDIR}/simple -lsimplesum
comments powered by Disqus