Go 用 CGO 调用 C 函数的两种姿势:静态编译和动态链接
在 Go 里可以通过 CGO 调用 C 函数,最简单的写法就是直接在 import "C"
前一行写 C 函数:
package main
/*
int sum(int a, int b) { return a + b; }
*/
import "C"
import "fmt"
func main() {
fmt.Println(C.sum(C.int(1), C.int(2)))
}
但,如果是那么简单的函数,也不需要用到 CGO,直接 Go 实现就行了,真要用到 CGO 了,大概率就是你需要调用一些比较复杂的 C 代码,可以用下面两种方式实现:
静态编译
上面的例子其实就是静态编译,编译器会把 C 代码跟 Go 代码一起编译进可执行文件里。
像刚刚说的,实际项目开发中要调用 C 函数不会那么简单,一般都是会有一个或多个 .c 文件,然后 Go 怎么调用呢?其实不难,只需要在 Go 里把对应的头文件(如果没有,就需要我们自己定义一个了)引入进来就行:
继续拿上面的 sum()
举例,首先我们需要一个 sum.h
:
int sum(int a, int b);
然后需要有 sum.c
:
#include "sum.h"
int sum(int a, int b)
{
return a + b;
}
把 sum.h
和 sum.c
放在跟调用它的 Go 代码文件同一个目录,然后 Go 代码里只需要:
package main
/*
#include "sum.h"
*/
import "C"
import "fmt"
func main() {
fmt.Println(C.sum(C.int(1), C.int(2)))
}
执行 CGO_ENABLED=1 go build -o cgo_example .
就可以把 C 代码静态编译到可执行程序里去了。
当然,上面这种写法可能不太优雅,你可以把 CGO 相关的 C 和 Go 代码放到一个目录(例如 /cgo
)里去,然后对入口做封装:
package cgo
/*
#include "sum.h"
*/
import "C"
func Sum(a, b int) int {
return int(C.sum(C.int(a), C.int(b)))
}
动态链接
静态链接有一个要求就是你必须有 C 代码,如果你只有头文件但没有 C 代码、或者不方便拷贝 C 代码到项目目录里,那么你可以通过动态链接的形式来调用 C 函数,只需要加上 cgo LDFLAGS
即可,具体实现:
把前面的 sum.h
和 sum.c
放到 ./dylib
里,然后编译成动态链接库:
gcc -shared -o libsum.so sum.c
然后在 Go 代码里:
package main
/*
#cgo LDFLAGS: -L./dylib/ -lsum
#include "sum.h"
*/
import "C"
import "fmt"
func main() {
fmt.Println(Sum(1, 2))
}
func Sum(a, b int) int {
return int(C.sum(C.int(a), C.int(b)))
}
然后执行 CGO_ENABLED=1 go build -o cgo_example .
编译,最后把 cgo_example
和 libsum.so
放到同一个目录即可运行。
上面这个例子不够优雅,假如我们要调用某些开源库的函数,它在我们本地开发环境可能是 /usr/local/lib/libsum.dylib
,而到了服务器上可能是 /usr/lib/libsum.so
,那么在代码里 hardcode
就会出现问题。
其实可以不用在代码里写 #cgo ...
那行,我们可以通过 CGO_LDFLAGS
环境变量来指定要在哪找动态链接库(-L./dylib/
)和链接库的名称(-lsum
),这样就可以根据机器实际安装情况来编译了