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),这样就可以根据机器实际安装情况来编译了